fix: remove whatsapp bridge node_modules from git, add to gitignore

This commit is contained in:
Jon Saad-Falcon
2026-03-28 17:18:13 -07:00
parent bc31cce732
commit fa87bbb2d6
1971 changed files with 1 additions and 849056 deletions
+1
View File
@@ -89,3 +89,4 @@ CLAUDE.md
# Research output
research_mining_*
.python-version
src/openjarvis/channels/whatsapp_baileys_bridge/node_modules/
@@ -1 +0,0 @@
../pino/bin.js
@@ -1 +0,0 @@
../qrcode-terminal/bin/qrcode-terminal.js
@@ -1 +0,0 @@
../semver/bin/semver.js
@@ -1 +0,0 @@
../typescript/bin/tsc
@@ -1 +0,0 @@
../typescript/bin/tsserver
File diff suppressed because it is too large Load Diff
@@ -1,9 +0,0 @@
The MIT License (MIT)
Copyright © 2025 Borewit
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -1,87 +0,0 @@
[![CI](https://github.com/Borewit/text-codec/actions/workflows/ci.yml/badge.svg)](https://github.com/Borewit/text-codec/actions/workflows/ci.yml)
[![npm version](https://img.shields.io/npm/v/%40borewit%2Ftext-codec.svg)](https://www.npmjs.com/package/@borewit/text-codec)
[![npm downloads](http://img.shields.io/npm/dm/@borewit/text-codec.svg)](https://npmcharts.com/compare/@borewit/text-codec?interval=30)
![bundlejs](https://deno.bundlejs.com/?q=@borewit/text-codec&badge)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg?logo=open-source-initiative&logoColor=white)](LICENSE.txt)
# `@borewit/text-codec`
A **lightweight polyfill for text encoders and decoders** covering a small set of commonly used encodings.
Some JavaScript runtimes provide limited or inconsistent encoding support through `TextEncoder` and `TextDecoder`.
Examples include environments like **Hermes (React Native)** or certain **Node.js builds with limited ICU support**.
This module provides **reliable encode/decode support for a small set of encodings that may be missing or unreliable in those environments**.
- If a native UTF-8 `TextEncoder` / `TextDecoder` is available, it is used.
- All other encodings are implemented by this library.
## Supported encodings
- `utf-8` / `utf8`
- `utf-16le`
- `ascii`
- `latin1` / `iso-8859-1`
- `windows-1252`
These encodings are commonly encountered in metadata formats and legacy text data.
## ✨ Features
- Encoding and decoding utilities
- Lightweight
- Typed API
## 📦 Installation
```sh
npm install @borewit/text-codec
```
# 📚 API Documentation
## `textDecode(bytes, encoding): string`
Decodes binary data into a JavaScript string.
**Parameters**
- `bytes` (`Uint8Array`) — The binary data to decode.
- `encoding` (`SupportedEncoding`, optional) — Encoding type. Defaults to `"utf-8"`.
**Returns**
- `string` — The decoded text.
**Example**
```js
import { textDecode } from "@borewit/text-codec";
const bytes = new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]);
const text = textDecode(bytes, "ascii");
console.log(text); // "Hello"
```
## `textEncode(input, encoding): Uint8Array`
Encodes a JavaScript string into binary form using the specified encoding.
**Parameters**
- `input` (`string`) — The string to encode.
- `encoding` (`SupportedEncoding`, optional) — Encoding type. Defaults to `"utf-8"`.
**Returns**
`Uint8Array` — The encoded binary data.
Example:
```js
import { textEncode } from "@borewit/text-codec";
const bytes = textEncode("Hello", "utf-16le");
console.log(bytes); // Uint8Array([...])
```
## 📜 Licence
This project is licensed under the [MIT License](LICENSE.txt). Feel free to use, modify, and distribute as needed.
@@ -1,6 +0,0 @@
export type SupportedEncoding = "utf-8" | "utf8" | "utf-16le" | "us-ascii" | "ascii" | "latin1" | "iso-8859-1" | "windows-1252";
/**
* Decode text from binary data
*/
export declare function textDecode(bytes: Uint8Array, encoding?: SupportedEncoding): string;
export declare function textEncode(input?: string, encoding?: SupportedEncoding): Uint8Array;
@@ -1,380 +0,0 @@
const WINDOWS_1252_EXTRA = {
0x80: "€", 0x82: "", 0x83: "ƒ", 0x84: "„", 0x85: "…", 0x86: "†",
0x87: "‡", 0x88: "ˆ", 0x89: "‰", 0x8a: "Š", 0x8b: "", 0x8c: "Œ",
0x8e: "Ž", 0x91: "", 0x92: "", 0x93: "“", 0x94: "”", 0x95: "•",
0x96: "", 0x97: "—", 0x98: "˜", 0x99: "™", 0x9a: "š", 0x9b: "",
0x9c: "œ", 0x9e: "ž", 0x9f: "Ÿ",
};
const WINDOWS_1252_REVERSE = {};
for (const [code, char] of Object.entries(WINDOWS_1252_EXTRA)) {
WINDOWS_1252_REVERSE[char] = Number.parseInt(code, 10);
}
let _utf8Decoder;
let _utf8Encoder;
function utf8Decoder() {
if (typeof globalThis.TextDecoder === "undefined")
return undefined;
return (_utf8Decoder !== null && _utf8Decoder !== void 0 ? _utf8Decoder : (_utf8Decoder = new globalThis.TextDecoder("utf-8")));
}
function utf8Encoder() {
if (typeof globalThis.TextEncoder === "undefined")
return undefined;
return (_utf8Encoder !== null && _utf8Encoder !== void 0 ? _utf8Encoder : (_utf8Encoder = new globalThis.TextEncoder()));
}
const CHUNK = 32 * 1024;
const REPLACEMENT = 0xfffd;
/**
* Decode text from binary data
*/
export function textDecode(bytes, encoding = "utf-8") {
switch (encoding.toLowerCase()) {
case "utf-8":
case "utf8": {
const dec = utf8Decoder();
return dec ? dec.decode(bytes) : decodeUTF8(bytes);
}
case "utf-16le":
return decodeUTF16LE(bytes);
case "us-ascii":
case "ascii":
return decodeASCII(bytes);
case "latin1":
case "iso-8859-1":
return decodeLatin1(bytes);
case "windows-1252":
return decodeWindows1252(bytes);
default:
throw new RangeError(`Encoding '${encoding}' not supported`);
}
}
export function textEncode(input = "", encoding = "utf-8") {
switch (encoding.toLowerCase()) {
case "utf-8":
case "utf8": {
const enc = utf8Encoder();
return enc ? enc.encode(input) : encodeUTF8(input);
}
case "utf-16le":
return encodeUTF16LE(input);
case "us-ascii":
case "ascii":
return encodeASCII(input);
case "latin1":
case "iso-8859-1":
return encodeLatin1(input);
case "windows-1252":
return encodeWindows1252(input);
default:
throw new RangeError(`Encoding '${encoding}' not supported`);
}
}
function appendCodePoint(out, cp) {
if (cp <= 0xffff) {
out.push(String.fromCharCode(cp));
return;
}
cp -= 0x10000;
out.push(String.fromCharCode(0xd800 + (cp >> 10), 0xdc00 + (cp & 0x3ff)));
}
function flushChunk(parts, chunk) {
if (chunk.length === 0)
return;
parts.push(String.fromCharCode.apply(null, chunk));
chunk.length = 0;
}
function pushCodeUnit(parts, chunk, codeUnit) {
chunk.push(codeUnit);
if (chunk.length >= CHUNK)
flushChunk(parts, chunk);
}
function pushCodePoint(parts, chunk, cp) {
if (cp <= 0xffff) {
pushCodeUnit(parts, chunk, cp);
return;
}
cp -= 0x10000;
pushCodeUnit(parts, chunk, 0xd800 + (cp >> 10));
pushCodeUnit(parts, chunk, 0xdc00 + (cp & 0x3ff));
}
function decodeUTF8(bytes) {
const parts = [];
const chunk = [];
let i = 0;
// Match TextDecoder("utf-8") default BOM handling
if (bytes.length >= 3 &&
bytes[0] === 0xef &&
bytes[1] === 0xbb &&
bytes[2] === 0xbf) {
i = 3;
}
while (i < bytes.length) {
const b1 = bytes[i];
if (b1 <= 0x7f) {
pushCodeUnit(parts, chunk, b1);
i++;
continue;
}
// Invalid leading bytes: continuation byte or impossible prefixes
if (b1 < 0xc2 || b1 > 0xf4) {
pushCodeUnit(parts, chunk, REPLACEMENT);
i++;
continue;
}
// 2-byte sequence
if (b1 <= 0xdf) {
if (i + 1 >= bytes.length) {
pushCodeUnit(parts, chunk, REPLACEMENT);
i++;
continue;
}
const b2 = bytes[i + 1];
if ((b2 & 0xc0) !== 0x80) {
pushCodeUnit(parts, chunk, REPLACEMENT);
i++;
continue;
}
const cp = ((b1 & 0x1f) << 6) | (b2 & 0x3f);
pushCodeUnit(parts, chunk, cp);
i += 2;
continue;
}
// 3-byte sequence
if (b1 <= 0xef) {
if (i + 2 >= bytes.length) {
pushCodeUnit(parts, chunk, REPLACEMENT);
i++;
continue;
}
const b2 = bytes[i + 1];
const b3 = bytes[i + 2];
const valid = (b2 & 0xc0) === 0x80 &&
(b3 & 0xc0) === 0x80 &&
!(b1 === 0xe0 && b2 < 0xa0) && // overlong
!(b1 === 0xed && b2 >= 0xa0); // surrogate range
if (!valid) {
pushCodeUnit(parts, chunk, REPLACEMENT);
i++;
continue;
}
const cp = ((b1 & 0x0f) << 12) |
((b2 & 0x3f) << 6) |
(b3 & 0x3f);
pushCodeUnit(parts, chunk, cp);
i += 3;
continue;
}
// 4-byte sequence
if (i + 3 >= bytes.length) {
pushCodeUnit(parts, chunk, REPLACEMENT);
i++;
continue;
}
const b2 = bytes[i + 1];
const b3 = bytes[i + 2];
const b4 = bytes[i + 3];
const valid = (b2 & 0xc0) === 0x80 &&
(b3 & 0xc0) === 0x80 &&
(b4 & 0xc0) === 0x80 &&
!(b1 === 0xf0 && b2 < 0x90) && // overlong
!(b1 === 0xf4 && b2 > 0x8f); // > U+10FFFF
if (!valid) {
pushCodeUnit(parts, chunk, REPLACEMENT);
i++;
continue;
}
const cp = ((b1 & 0x07) << 18) |
((b2 & 0x3f) << 12) |
((b3 & 0x3f) << 6) |
(b4 & 0x3f);
pushCodePoint(parts, chunk, cp);
i += 4;
}
flushChunk(parts, chunk);
return parts.join("");
}
function decodeUTF16LE(bytes) {
const parts = [];
const chunk = [];
const len = bytes.length;
let i = 0;
while (i + 1 < len) {
const u1 = bytes[i] | (bytes[i + 1] << 8);
i += 2;
// High surrogate
if (u1 >= 0xd800 && u1 <= 0xdbff) {
if (i + 1 < len) {
const u2 = bytes[i] | (bytes[i + 1] << 8);
if (u2 >= 0xdc00 && u2 <= 0xdfff) {
pushCodeUnit(parts, chunk, u1);
pushCodeUnit(parts, chunk, u2);
i += 2;
}
else {
pushCodeUnit(parts, chunk, REPLACEMENT);
}
}
else {
pushCodeUnit(parts, chunk, REPLACEMENT);
}
continue;
}
// Lone low surrogate
if (u1 >= 0xdc00 && u1 <= 0xdfff) {
pushCodeUnit(parts, chunk, REPLACEMENT);
continue;
}
pushCodeUnit(parts, chunk, u1);
}
// Odd trailing byte
if (i < len) {
pushCodeUnit(parts, chunk, REPLACEMENT);
}
flushChunk(parts, chunk);
return parts.join("");
}
function decodeASCII(bytes) {
const parts = [];
for (let i = 0; i < bytes.length; i += CHUNK) {
const end = Math.min(bytes.length, i + CHUNK);
const codes = new Array(end - i);
for (let j = i, k = 0; j < end; j++, k++) {
codes[k] = bytes[j] & 0x7f;
}
parts.push(String.fromCharCode.apply(null, codes));
}
return parts.join("");
}
function decodeLatin1(bytes) {
const parts = [];
for (let i = 0; i < bytes.length; i += CHUNK) {
const end = Math.min(bytes.length, i + CHUNK);
const codes = new Array(end - i);
for (let j = i, k = 0; j < end; j++, k++) {
codes[k] = bytes[j];
}
parts.push(String.fromCharCode.apply(null, codes));
}
return parts.join("");
}
function decodeWindows1252(bytes) {
const parts = [];
let out = "";
for (let i = 0; i < bytes.length; i++) {
const b = bytes[i];
const extra = b >= 0x80 && b <= 0x9f ? WINDOWS_1252_EXTRA[b] : undefined;
out += extra !== null && extra !== void 0 ? extra : String.fromCharCode(b);
if (out.length >= CHUNK) {
parts.push(out);
out = "";
}
}
if (out)
parts.push(out);
return parts.join("");
}
function encodeUTF8(str) {
const out = [];
for (let i = 0; i < str.length; i++) {
let cp = str.charCodeAt(i);
// Valid surrogate pair
if (cp >= 0xd800 && cp <= 0xdbff) {
if (i + 1 < str.length) {
const lo = str.charCodeAt(i + 1);
if (lo >= 0xdc00 && lo <= 0xdfff) {
cp = 0x10000 + ((cp - 0xd800) << 10) + (lo - 0xdc00);
i++;
}
else {
cp = REPLACEMENT;
}
}
else {
cp = REPLACEMENT;
}
}
else if (cp >= 0xdc00 && cp <= 0xdfff) {
// Lone low surrogate
cp = REPLACEMENT;
}
if (cp < 0x80) {
out.push(cp);
}
else if (cp < 0x800) {
out.push(0xc0 | (cp >> 6), 0x80 | (cp & 0x3f));
}
else if (cp < 0x10000) {
out.push(0xe0 | (cp >> 12), 0x80 | ((cp >> 6) & 0x3f), 0x80 | (cp & 0x3f));
}
else {
out.push(0xf0 | (cp >> 18), 0x80 | ((cp >> 12) & 0x3f), 0x80 | ((cp >> 6) & 0x3f), 0x80 | (cp & 0x3f));
}
}
return new Uint8Array(out);
}
function encodeUTF16LE(str) {
// Preserve JS string code units, but do not emit non-well-formed UTF-16.
// Replace lone surrogates with U+FFFD.
const units = [];
for (let i = 0; i < str.length; i++) {
const u = str.charCodeAt(i);
if (u >= 0xd800 && u <= 0xdbff) {
if (i + 1 < str.length) {
const lo = str.charCodeAt(i + 1);
if (lo >= 0xdc00 && lo <= 0xdfff) {
units.push(u, lo);
i++;
}
else {
units.push(REPLACEMENT);
}
}
else {
units.push(REPLACEMENT);
}
continue;
}
if (u >= 0xdc00 && u <= 0xdfff) {
units.push(REPLACEMENT);
continue;
}
units.push(u);
}
const out = new Uint8Array(units.length * 2);
for (let i = 0; i < units.length; i++) {
const code = units[i];
const o = i * 2;
out[o] = code & 0xff;
out[o + 1] = code >>> 8;
}
return out;
}
function encodeASCII(str) {
const out = new Uint8Array(str.length);
for (let i = 0; i < str.length; i++)
out[i] = str.charCodeAt(i) & 0x7f;
return out;
}
function encodeLatin1(str) {
const out = new Uint8Array(str.length);
for (let i = 0; i < str.length; i++)
out[i] = str.charCodeAt(i) & 0xff;
return out;
}
function encodeWindows1252(str) {
const out = new Uint8Array(str.length);
for (let i = 0; i < str.length; i++) {
const ch = str[i];
const code = ch.charCodeAt(0);
if (WINDOWS_1252_REVERSE[ch] !== undefined) {
out[i] = WINDOWS_1252_REVERSE[ch];
continue;
}
if ((code >= 0x00 && code <= 0x7f) ||
(code >= 0xa0 && code <= 0xff)) {
out[i] = code;
continue;
}
out[i] = 0x3f; // '?'
}
return out;
}
@@ -1,70 +0,0 @@
{
"name": "@borewit/text-codec",
"version": "0.2.2",
"description": "Text Decoder",
"type": "module",
"exports": "./lib/index.js",
"types": "lib/index.d.ts",
"files": [
"lib/index.js",
"lib/index.d.ts"
],
"scripts": {
"clean": "del-cli lib/**/*.js lib/***.js.map test/**/*.d.ts test/**/*.js test/**/*.js.map",
"build": "npm run compile",
"prepublishOnly": "npm run build",
"compile:src": "tsc --p lib --sourceMap false",
"compile:test": "tsc --p test",
"compile": "npm run compile:src && npm run compile:test",
"lint": "biome check",
"test": "mocha",
"update-biome": "npm install --save-dev --save-exact @biomejs/biome@latest && npx @biomejs/biome migrate --write"
},
"devDependencies": {
"@biomejs/biome": "2.4.6",
"@types/chai": "^5.2.2",
"@types/mocha": "^10.0.10",
"chai": "^6.2.2",
"mocha": "^11.7.5",
"ts-node": "^10.9.2",
"typescript": "^5.9.3"
},
"keywords": [
"TextDecoder",
"TextEncoder",
"decoder",
"decoding",
"encod",
"encoding",
"decode",
"text",
"ascii",
"utf-8",
"utf8",
"utf-16le",
"latin1",
"iso-8859-1",
"windows-1252",
"charset",
"encoding",
"decoding",
"polyfill",
"character-set",
"latin",
"hermes",
"react"
],
"repository": {
"type": "git",
"url": "git+https://github.com/Borewit/text-codec.git"
},
"author": {
"name": "Borewit",
"url": "https://github.com/Borewit"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
},
"license": "MIT"
}
@@ -1,19 +0,0 @@
MIT License & © Jared Wray
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
@@ -1,340 +0,0 @@
[<img align="center" src="https://cacheable.org/logo.svg" alt="Cacheable" />](https://github.com/jaredwray/cacheable)
> High Performance Layer 1 / Layer 2 Caching with Keyv Storage
[![codecov](https://codecov.io/gh/jaredwray/cacheable/branch/main/graph/badge.svg?token=lWZ9OBQ7GM)](https://codecov.io/gh/jaredwray/cacheable)
[![tests](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml/badge.svg)](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml)
[![npm](https://img.shields.io/npm/dm/@cacheable/memory.svg)](https://www.npmjs.com/package/@cacheable/memory)
[![npm](https://img.shields.io/npm/v/@cacheable/memory.svg)](https://www.npmjs.com/package/@cacheable/memory)
[![license](https://img.shields.io/github/license/jaredwray/cacheable)](https://github.com/jaredwray/cacheable/blob/main/LICENSE)
You can use `CacheableMemory` as a standalone cache or as a primary store for `cacheable`. You can also set the `useClones` property to `false` if you want to use the same reference for the values. This is useful if you are using large objects and want to save memory. The `lruSize` property is the size of the LRU cache and is set to `0` by default which is unlimited. When setting the `lruSize` property it will limit the number of keys in the cache.
This simple in-memory cache uses multiple Map objects and a with `expiration` and `lru` policies if set to manage the in memory cache at scale.
By default we use lazy expiration deletion which means on `get` and `getMany` type functions we look if it is expired and then delete it. If you want to have a more aggressive expiration policy you can set the `checkInterval` property to a value greater than `0` which will check for expired keys at the interval you set.
Here are some of the main features of `CacheableMemory`:
* High performance in-memory cache with a robust API and feature set. 🚀
* Can scale past the `16,777,216 (2^24) keys` limit of a single `Map` via `hashStoreSize`. Default is `16` Map objects.
* LRU (Least Recently Used) cache feature to limit the number of keys in the cache via `lruSize`. Limit to `16,777,216 (2^24) keys` total.
* Expiration policy to delete expired keys with lazy deletion or aggressive deletion via `checkInterval`.
* `Wrap` feature to memoize `sync` and `async` functions with stampede protection.
* Ability to do many operations at once such as `setMany`, `getMany`, `deleteMany`, and `takeMany`.
* Supports `raw` data retrieval with `getRaw` and `getManyRaw` methods to get the full metadata of the cache entry.
# Table of Contents
* [Getting Started](#getting-started)
* [CacheableMemory - In-Memory Cache](#cacheablememory---in-memory-cache)
* [CacheableMemory Store Hashing](#cacheablememory-store-hashing)
* [CacheableMemory LRU Feature](#cacheablememory-lru-feature)
* [CacheableMemory Performance](#cacheablememory-performance)
* [CacheableMemory Options](#cacheablememory-options)
* [CacheableMemory - API](#cacheablememory---api)
* [Keyv Storage Adapter - KeyvCacheableMemory](#keyv-storage-adapter---keyvcacheablememory)
* [Wrap / Memoization for Sync and Async Functions](#wrap--memoization-for-sync-and-async-functions)
* [Get Or Set Memoization Function](#get-or-set-memoization-function)
* [How to Contribute](#how-to-contribute)
* [License and Copyright](#license-and-copyright)
# Getting Started
```bash
npm install @cacheable/memory
```
# Basic Usage
```javascript
import { CacheableMemory } from '@cacheable/memory';
const cacheable = new CacheableMemory();
await cacheable.set('key', 'value', 1000);
const value = await cacheable.get('key');
```
In this example, the primary store we will use `lru-cache` and the secondary store is Redis. You can also set multiple stores in the options:
```javascript
import { CacheableMemory } from '@cacheable/memory';
// we set the storeHashSize to 1 so that we only use a single Map object as the lru is limited to a single Map size
const cache = new CacheableMemory({storeHashSize: 1, lruSize: 80000});
cache.set('key1', 'value1');
const result = cache.get('key1');
console.log(result); // 'value1'
```
This is a more advanced example and not needed for most use cases.
# Shorthand for Time to Live (ttl)
By default `Cacheable` and `CacheableMemory` the `ttl` is in milliseconds but you can use shorthand for the time to live. Here are the following shorthand values:
* `ms`: Milliseconds such as (1ms = 1)
* `s`: Seconds such as (1s = 1000)
* `m`: Minutes such as (1m = 60000)
* `h` or `hr`: Hours such as (1h = 3600000)
* `d`: Days such as (1d = 86400000)
Here is an example of how to use the shorthand for the `ttl`:
```javascript
import { CacheableMemory } from 'cacheable';
const cache = new CacheableMemory({ ttl: '15m' }); //sets the default ttl to 15 minutes (900000 ms)
cache.set('key', 'value', '1h'); //sets the ttl to 1 hour (3600000 ms) and overrides the default
```
if you want to disable the `ttl` you can set it to `0` or `undefined`:
```javascript
import { CacheableMemory } from 'cacheable';
const cache = new CacheableMemory({ ttl: 0 }); //sets the default ttl to 0 which is disabled
cache.set('key', 'value', 0); //sets the ttl to 0 which is disabled
```
If you set the ttl to anything below `0` or `undefined` it will disable the ttl for the cache and the value that returns will be `undefined`. With no ttl set the value will be stored `indefinitely`.
```javascript
import { CacheableMemory } from 'cacheable';
const cache = new CacheableMemory({ ttl: 0 }); //sets the default ttl to 0 which is disabled
console.log(cache.ttl); // undefined
cache.ttl = '1h'; // sets the default ttl to 1 hour (3600000 ms)
console.log(cache.ttl); // '1h'
cache.ttl = -1; // sets the default ttl to 0 which is disabled
console.log(cache.ttl); // undefined
```
## Retrieving raw cache entries
The `getRaw` and `getManyRaw` methods return the full stored metadata (`StoredDataRaw<T>`) instead of just the value:
```typescript
import { CacheableMemory } from 'cacheable';
const cache = new CacheableMemory();
// store a value
await cache.set('user:1', { name: 'Alice' }, '1h'); // 1 hour
// default: only the value
const user = await cache.get<{ name: string }>('user:1');
console.log(user); // { name: 'Alice' }
// with raw: full record including expiration
const raw = await cache.getRaw('user:1');
console.log(raw.value); // { name: 'Alice' }
console.log(raw.expires); // e.g. 1677628495000 or null
```
## CacheableMemory Store Hashing
`CacheableMemory` uses `Map` objects to store the keys and values. To make this scale past the `16,777,216 (2^24) keys` limit of a single `Map` we use a hash to balance the data across multiple `Map` objects. This is done by hashing the key and using the hash to determine which `Map` object to use. The default hashing algorithm is `djb2` but you can change it by setting the `storeHashAlgorithm` property in the options. Supported algorithms include DJB2, FNV1, MURMER, and CRC32. By default we set the amount of `Map` objects to `16`.
NOTE: if you are using the LRU cache feature the `lruSize` no matter how many `Map` objects you have it will be limited to the `16,777,216 (2^24) keys` limit of a single `Map` object. This is because we use a double linked list to manage the LRU cache and it is not possible to have more than `16,777,216 (2^24) keys` in a single `Map` object.
Here is an example of how to set the number of `Map` objects and the hashing algorithm:
```javascript
import { CacheableMemory } from '@cacheable/memory';
const cache = new CacheableMemory({
storeSize: 32, // set the number of Map objects to 32
});
cache.set('key', 'value');
const value = cache.get('key'); // value
```
Here is an example of how to use the `storeHashAlgorithm` property with supported algorithms:
```javascript
import { CacheableMemory, HashAlgorithm } from '@cacheable/memory';
// Using DJB2 (default)
const cache = new CacheableMemory({ storeHashAlgorithm: HashAlgorithm.DJB2 });
// Or other non-cryptographic algorithms for better performance
const cache2 = new CacheableMemory({ storeHashAlgorithm: HashAlgorithm.FNV1 });
const cache3 = new CacheableMemory({ storeHashAlgorithm: HashAlgorithm.MURMER });
const cache4 = new CacheableMemory({ storeHashAlgorithm: HashAlgorithm.CRC32 });
cache.set('key', 'value');
const value = cache.get('key'); // value
```
**Available algorithms:** DJB2 (default), FNV1, MURMER, CRC32. Note: Cryptographic algorithms (SHA-256, SHA-384, SHA-512) are not recommended for store hashing due to performance overhead.
If you want to provide your own hashing function you can set the `storeHashAlgorithm` property to a function that takes an object and returns a `number` that is in the range of the amount of `Map` stores you have.
```javascript
import { CacheableMemory } from 'cacheable';
/**
* Custom hash function that takes a key and the size of the store
* and returns a number between 0 and storeHashSize - 1.
* @param {string} key - The key to hash.
* @param {number} storeHashSize - The size of the store (number of Map objects).
* @returns {number} - A number between 0 and storeHashSize - 1.
*/
const customHash = (key, storeHashSize) => {
// custom hashing logic
return key.length % storeHashSize; // returns a number between 0 and 31 for 32 Map objects
};
const cache = new CacheableMemory({ storeHashAlgorithm: customHash, storeSize: 32 });
cache.set('key', 'value');
const value = cache.get('key'); // value
```
## CacheableMemory LRU Feature
You can enable the LRU (Least Recently Used) feature in `CacheableMemory` by setting the `lruSize` property in the options. This will limit the number of keys in the cache to the size you set. When the cache reaches the limit it will remove the least recently used keys from the cache. This is useful if you want to limit the memory usage of the cache.
When you set the `lruSize` we use a double linked list to manage the LRU cache and also set the `hashStoreSize` to `1` which means we will only use a single `Map` object for the LRU cache. This is because the LRU cache is managed by the double linked list and it is not possible to have more than `16,777,216 (2^24) keys` in a single `Map` object.
```javascript
import { CacheableMemory } from 'cacheable';
const cache = new CacheableMemory({ lruSize: 1 }); // sets the LRU cache size to 1000 keys and hashStoreSize to 1
cache.set('key1', 'value1');
cache.set('key2', 'value2');
const value1 = cache.get('key1');
console.log(value1); // undefined if the cache is full and key1 is the least recently used
const value2 = cache.get('key2');
console.log(value2); // value2 if key2 is still in the cache
console.log(cache.size()); // 1
```
NOTE: if you set the `lruSize` property to `0` after it was enabled it will disable the LRU cache feature and will not limit the number of keys in the cache. This will remove the `16,777,216 (2^24) keys` limit of a single `Map` object and will allow you to store more keys in the cache.
## CacheableMemory Performance
Our goal with `cacheable` and `CacheableMemory` is to provide a high performance caching engine that is simple to use and has a robust API. We test it against other cacheing engines such that are less feature rich to make sure there is little difference. Here are some of the benchmarks we have run:
*Memory Benchmark Results:*
| name | summary | ops/sec | time/op | margin | samples |
|------------------------------------------|:---------:|----------:|----------:|:--------:|----------:|
| Cacheable Memory (v1.10.0) - set / get | 🥇 | 152K | 7µs | ±0.94% | 147K |
| Map (v22) - set / get | -1.1% | 151K | 7µs | ±0.69% | 145K |
| Node Cache - set / get | -4.3% | 146K | 7µs | ±1.13% | 142K |
| bentocache (v1.4.0) - set / get | -20% | 121K | 8µs | ±0.40% | 119K |
*Memory LRU Benchmark Results:*
| name | summary | ops/sec | time/op | margin | samples |
|------------------------------------------|:---------:|----------:|----------:|:--------:|----------:|
| quick-lru (v7.0.1) - set / get | 🥇 | 118K | 9µs | ±0.85% | 112K |
| Map (v22) - set / get | -0.56% | 117K | 9µs | ±1.35% | 110K |
| lru.min (v1.1.2) - set / get | -1.7% | 116K | 9µs | ±0.90% | 110K |
| Cacheable Memory (v1.10.0) - set / get | -3.3% | 114K | 9µs | ±1.16% | 108K |
As you can see from the benchmarks `CacheableMemory` is on par with other caching engines such as `Map`, `Node Cache`, and `bentocache`. We have also tested it against other LRU caching engines such as `quick-lru` and `lru.min` and it performs well against them too.
## CacheableMemory Options
* `ttl`: The time to live for the cache in milliseconds. Default is `undefined` which is means indefinitely.
* `useClones`: If the cache should use clones for the values. Default is `true`.
* `lruSize`: The size of the LRU cache. Default is `0` which is unlimited.
* `checkInterval`: The interval to check for expired keys in milliseconds. Default is `0` which is disabled.
* `storeHashSize`: The number of `Map` objects to use for the cache. Default is `16`.
* `storeHashAlgorithm`: The hashing algorithm to use for the cache. Default is `djb2`. Supported: DJB2, FNV1, MURMER, CRC32.
## CacheableMemory - API
* `set(key, value, ttl?)`: Sets a value in the cache.
* `setMany([{key, value, ttl?}])`: Sets multiple values in the cache from `CacheableItem`.
* `get(key)`: Gets a value from the cache.
* `getMany([keys])`: Gets multiple values from the cache.
* `getRaw(key)`: Gets a value from the cache as `CacheableStoreItem`.
* `getManyRaw([keys])`: Gets multiple values from the cache as `CacheableStoreItem`.
* `has(key)`: Checks if a value exists in the cache.
* `hasMany([keys])`: Checks if multiple values exist in the cache.
* `delete(key)`: Deletes a value from the cache.
* `deleteMany([keys])`: Deletes multiple values from the cache.
* `take(key)`: Takes a value from the cache and deletes it.
* `takeMany([keys])`: Takes multiple values from the cache and deletes them.
* `wrap(function, WrapSyncOptions)`: Wraps a `sync` function in a cache.
* `clear()`: Clears the cache.
* `ttl`: The default time to live for the cache in milliseconds. Default is `undefined` which is disabled.
* `useClones`: If the cache should use clones for the values. Default is `true`.
* `lruSize`: The size of the LRU cache. Default is `0` which is unlimited.
* `size`: The number of keys in the cache.
* `checkInterval`: The interval to check for expired keys in milliseconds. Default is `0` which is disabled.
* `storeHashSize`: The number of `Map` objects to use for the cache. Default is `16`.
* `storeHashAlgorithm`: The hashing algorithm to use for the cache. Default is `djb2`. Supported: DJB2, FNV1, MURMER, CRC32.
* `keys`: Get the keys in the cache. Not able to be set.
* `items`: Get the items in the cache as `CacheableStoreItem` example `{ key, value, expires? }`.
* `store`: The hash store for the cache which is an array of `Map` objects.
* `checkExpired()`: Checks for expired keys in the cache. This is used by the `checkInterval` property.
* `startIntervalCheck()`: Starts the interval check for expired keys if `checkInterval` is above 0 ms.
* `stopIntervalCheck()`: Stops the interval check for expired keys.
# Keyv Storage Adapter - KeyvCacheableMemory
`cacheable` comes with a built-in storage adapter for Keyv called `KeyvCacheableMemory`. This takes `CacheableMemory` and creates a storage adapter for Keyv. This is useful if you want to use `CacheableMemory` as a storage adapter for Keyv. Here is an example of how to use `KeyvCacheableMemory`:
```javascript
import { Keyv } from 'keyv';
import { KeyvCacheableMemory } from 'cacheable';
const keyv = new Keyv({ store: new KeyvCacheableMemory() });
await keyv.set('foo', 'bar');
const value = await keyv.get('foo');
console.log(value); // bar
```
# Wrap / Memoization for Sync and Async Functions
`CacheableMemory` has a feature called `wrap` that allows you to wrap a function in a cache. This is useful for memoization and caching the results of a function. You can wrap a `sync` function in a cache. Here is an example of how to use the `wrap` function:
```javascript
import { CacheableMemory } from 'cacheable';
const syncFunction = (value: number) => {
return value * 2;
};
const cache = new CacheableMemory();
const wrappedFunction = cache.wrap(syncFunction, { ttl: '1h', key: 'syncFunction' });
console.log(wrappedFunction(2)); // 4
console.log(wrappedFunction(2)); // 4 from cache
```
In this example we are wrapping a `sync` function in a cache with a `ttl` of `1 hour`. This will cache the result of the function for `1 hour` and then expire the value. You can also set the `key` property in the `wrap()` options to set a custom key for the cache.
When an error occurs in the function it will not cache the value and will return the error. This is useful if you want to cache the results of a function but not cache the error. If you want it to cache the error you can set the `cacheError` property to `true` in the `wrap()` options. This is disabled by default.
```javascript
import { CacheableMemory } from 'cacheable';
const syncFunction = (value: number) => {
throw new Error('error');
};
const cache = new CacheableMemory();
const wrappedFunction = cache.wrap(syncFunction, { ttl: '1h', key: 'syncFunction', cacheError: true });
console.log(wrappedFunction()); // error
console.log(wrappedFunction()); // error from cache
```
If you would like to generate your own key for the wrapped function you can set the `createKey` property in the `wrap()` options. This is useful if you want to generate a key based on the arguments of the function or any other criteria.
```javascript
const cache = new CacheableMemory();
const options: WrapOptions = {
cache,
keyPrefix: 'test',
createKey: (function_, arguments_, options: WrapOptions) => `customKey:${options?.keyPrefix}:${arguments_[0]}`,
};
const wrapped = wrap((argument: string) => `Result for ${argument}`, options);
const result1 = wrapped('arg1');
const result2 = wrapped('arg1'); // Should hit the cache
console.log(result1); // Result for arg1
console.log(result2); // Result for arg1 (from cache)
```
We will pass in the `function` that is being wrapped, the `arguments` passed to the function, and the `options` used to wrap the function. You can then use these to generate a custom key for the cache.
# How to Contribute
You can contribute by forking the repo and submitting a pull request. Please make sure to add tests and update the documentation. To learn more about how to contribute go to our main README [https://github.com/jaredwray/cacheable](https://github.com/jaredwray/cacheable). This will talk about how to `Open a Pull Request`, `Ask a Question`, or `Post an Issue`.
# License and Copyright
[MIT © Jared Wray](./LICENSE)
@@ -1,69 +0,0 @@
{
"name": "@cacheable/memory",
"version": "2.0.8",
"description": "High Performance In-Memory Cache for Node.js",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
}
},
"repository": {
"type": "git",
"url": "git+https://github.com/jaredwray/cacheable.git",
"directory": "packages/cacheable"
},
"author": "Jared Wray <me@jaredwray.com>",
"license": "MIT",
"private": false,
"devDependencies": {
"@faker-js/faker": "^10.3.0",
"@types/node": "^25.3.0",
"rimraf": "^6.1.3",
"tsup": "^8.5.1",
"typescript": "^5.9.3"
},
"dependencies": {
"@keyv/bigmap": "^1.3.1",
"hookified": "^1.15.1",
"keyv": "^5.6.0",
"@cacheable/utils": "^2.4.0"
},
"keywords": [
"cacheable",
"high performance",
"distributed caching",
"Keyv storage engine",
"keyv",
"memory caching",
"LRU cache",
"memory",
"in-memory",
"scalable cache",
"in-memory cache",
"lruSize",
"lru"
],
"files": [
"dist",
"LICENSE"
],
"scripts": {
"build": "rimraf ./dist && tsup src/index.ts --format cjs,esm --dts --clean",
"prepublish": "pnpm build",
"lint": "biome check --write --error-on-warnings",
"test": "pnpm lint && vitest run --coverage",
"test:ci": "biome check --error-on-warnings && vitest run --coverage",
"clean": "rimraf ./dist ./coverage ./node_modules"
}
}
@@ -1,19 +0,0 @@
MIT License & © Jared Wray
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
@@ -1,352 +0,0 @@
[<img align="center" src="https://cacheable.org/symbol.svg" alt="Cacheable" />](https://github.com/jaredwray/cacheable)
# Node-Cache
> Simple and Maintained fast Node.js caching
[![codecov](https://codecov.io/gh/jaredwray/cacheable/graph/badge.svg?token=lWZ9OBQ7GM)](https://codecov.io/gh/jaredwray/cacheable)
[![tests](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml/badge.svg)](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml)
[![npm](https://img.shields.io/npm/dm/@cacheable/node-cache.svg)](https://www.npmjs.com/package/@cacheable/node-cache)
[![npm](https://img.shields.io/npm/v/@cacheable/node-cache)](https://www.npmjs.com/package/@cacheable/node-cache)
[![license](https://img.shields.io/github/license/jaredwray/cacheable)](https://github.com/jaredwray/cacheable/blob/main/LICENSE)
`@cacheable/node-cache` is compatible with the [node-cache](https://www.npmjs.com/package/node-cache) package with regular maintenance and additional functionality (async/await and storage adapters). The only thing not implemented is the `enableLegacyCallbacks` option and functions. If you need them we are happy to take a PR to add them.
* Fully Compatible with `node-cache` using `{NodeCache}`
* Faster than the original `node-cache` package 🚀
* Async/Await functionality with `{NodeCacheStore}`
* Storage Adapters via [Keyv](https://keyv.org) with `{NodeCacheStore}`
* Maintained and Updated Regularly! 🎉
# Table of Contents
* [Getting Started](#getting-started)
* [Basic Usage](#basic-usage)
* [NodeCache Performance](#nodecache-performance)
* [NodeCache API](#nodecache-api)
* [NodeCacheStore](#nodecachestore)
* [NodeCacheStore API](#nodecachestore-api)
* [How to Contribute](#how-to-contribute)
* [License and Copyright](#license-and-copyright)
# Getting Started
```bash
npm install @cacheable/node-cache --save
```
# Basic Usage
```javascript
import NodeCache from '@cacheable/node-cache';
const cache = new NodeCache();
cache.set('foo', 'bar');
cache.get('foo'); // 'bar'
cache.set('foo', 'bar', 10); // 10 seconds
cache.del('foo'); // true
cache.set('bar', 'baz', '35m'); // 35 minutes using shorthand
```
The `NodeCache` is not the default export, so you need to import it like this:
```javascript
import {NodeCache} from '@cacheable/node-cache';
const cache = new NodeCache();
cache.set('foo', 'bar');
cache.get('foo'); // 'bar'
```
`NodeCache` also offers the ability to set the type of values that can be cached in Typescript environments.
```typescript
import {NodeCache} from '@cacheable/node-cache';
const cache = new NodeCache<string>();
cache.set('foo', 'bar');
cache.get('foo'); // 'bar'
```
# NodeCache Performance
The performance is comparable if not faster to the original `node-cache` package, but with additional features and improvements.
| name | summary | ops/sec | time/op | margin | samples |
|-----------------------------------|:---------:|----------:|----------:|:--------:|----------:|
| Cacheable NodeCache - set / get | 🥇 | 117K | 9µs | ±1.01% | 111K |
| Node Cache - set / get | -4.6% | 112K | 9µs | ±1.31% | 106K |
# NodeCache API
## `constructor(options?: NodeCacheOptions)`
Create a new cache instance. You can pass in options to set the configuration:
```javascript
export type NodeCacheOptions = {
stdTTL?: number;
checkperiod?: number;
useClones?: boolean;
deleteOnExpire?: boolean;
maxKeys?: number;
};
```
Here is a description of the options:
| Option | Default Setting | Description |
|--------|----------------|-------------|
| `stdTTL` | `0` | The standard time to live (TTL) in seconds for every generated cache element. If set to `0`, it means unlimited. If a string is provided, it will be parsed as shorthand and default to milliseconds if it is a number as a string. |
| `checkperiod` | `600` | The interval in seconds to check for expired keys. If set to `0`, it means no periodic check will be performed. |
| `useClones` | `true` | If set to `true`, the cache will clone the returned items via `get()` functions. This means that every time you set a value into the cache, `node-cache` makes a deep clone of it. When you get that value back, you receive another deep clone. This mimics the behavior of an external cache like Redis or Memcached, meaning mutations to the returned object do not affect the cached copy (and vice versa). If set to `false`, the original object will be returned, and mutations will affect the cached copy. |
| `deleteOnExpire` | `true` | If set to `true`, the key will be deleted when it expires. If set to `false`, the key will remain in the cache, but the value returned by `get()` will be `undefined`. You can manage the key with the `on('expired')` event. |
| `maxKeys` | `-1` | If set to a positive number, it will limit the number of keys in the cache. If the number of keys exceeds this limit, it will throw an error when trying to set more keys than the maximum. If set to `-1`, it means unlimited keys are allowed. |
When initializing the cache you can pass in the options to set the configuration like the example below where we set the `stdTTL` to 10 seconds and `checkperiod` to 5 seconds.:
```javascript
const cache = new NodeCache({stdTTL: 10, checkperiod: 5});
```
When setting `deleteOnExpire` to `true` it will delete the key when it expires. If you set it to `false` it will keep the key but the value on `get()` will be `undefined`. You can manage the key with `on('expired')` event.
```javascript
const cache = new NodeCache({deleteOnExpire: false});
cache.on('expired', (key, value) => {
console.log(`Key ${key} has expired with value ${value}`);
});
```
## `set(key: string | number, value: any, ttl?: number): boolean`
Set a key value pair with an optional ttl (in seconds). Will return true on success. If the ttl is not set it will default to 0 (no ttl).
```javascript
cache.set('foo', 'bar', 10); // true
```
## `mset(data: Array<NodeCacheItem>): boolean`
Set multiple key value pairs at once. This will take an array of objects with the key, value, and optional ttl.
```javascript
cache.mset([{key: 'foo', value: 'bar', ttl: 10}, {key: 'bar', value: 'baz'}]); // true
```
the `NodeCacheItem` is defined as:
```javascript
export type NodeCacheItem = {
key: string;
value: any;
ttl?: number;
};
```
## `get<T>(key: string | number): T | undefined`
Get a value from the cache by key. If the key does not exist it will return `undefined`.
```javascript
cache.get('foo'); // 'bar'
```
## `mget<T>(keys: Array<string | number>): Record<string, T | undefined>`
Get multiple values from the cache by keys. This will return an object with the keys and values.
```javascript
const obj = { my: 'value', my2: 'value2' };
const obj2 = { special: 'value3', life: 'value4' };
cache.set('my', obj);
cache.set('my2', obj2);
cache.mget(['my', 'my2']); // { my: { my: 'value', my2: 'value2' }, my2: { special: 'value3', life: 'value4' } }
```
## `take<T>(key: string | number): T | undefined`
Get a value from the cache by key and delete it. If the key does not exist it will return `undefined`.
```javascript
cache.set('foo', 'bar');
cache.take('foo'); // 'bar'
cache.get('foo'); // undefined
```
## `del(key: string | number | Array<string | number>): number`
Delete a key from the cache. Will return the number of deleted entries and never fail. You can also pass in an array of keys to delete multiple keys. All examples assume that you have initialized the cache like `const cache = new NodeCache();`.
```javascript
cache.del('foo'); // true
```
passing in an array of keys:
```javascript
cache.del(['foo', 'bar']); // true
```
## `mdel(keys: Array<string | number>): number`
Delete multiple keys from the cache. Will return the number of deleted entries and never fail.
```javascript
cache.mdel(['foo', 'bar']); // true
```
## `ttl(key: string | number, ttl?: number): boolean`
Redefine the ttl of a key. Returns true if the key has been found and changed. Otherwise returns false. If the ttl-argument isn't passed the default-TTL will be used.
```javascript
cache.ttl('foo', 10); // true
```
## `getTtl(key: string | number): number | undefined`
Get the ttl expiration from `Date.now()` of a key. If the key does not exist it will return `undefined`.
```javascript
cache.getTtl('foo'); // 1725993344859
```
## `has(key: string | number): boolean`
Check if a key exists in the cache.
```javascript
cache.set('foo', 'bar');
cache.has('foo'); // true
```
## `keys(): string[]`
Get all keys from the cache.
```javascript
await cache.keys(); // ['foo', 'bar']
```
## `getStats(): NodeCacheStats`
Get the stats of the cache.
```javascript
cache.getStats(); // {hits: 1, misses: 1, keys: 1, ksize: 2, vsize: 3}
```
## `flushAll(): void`
Flush the cache. Will remove all keys and reset the stats.
```javascript
cache.flushAll();
await cache.keys(); // []
cache.getStats(); // {hits: 0, misses: 0, keys: 0, ksize: 0, vsize: 0}
```
## `flushStats(): void`
Flush the stats. Will reset the stats but keep the keys.
```javascript
await cache.set('foo', 'bar');
cache.flushStats();
cache.getStats(); // {hits: 0, misses: 0, keys: 0, ksize: 0, vsize: 0}
await cache.keys(); // ['foo']
```
## `on(event: string, callback: Function): void`
Listen to events. Here are the events that you can listen to:
* `set` - when a key is set and it will pass in the `key` and `value`.
* `expired` - when a key is expired and it will pass in the `key` and `value`.
* `flush` - when the cache is flushed
* `flush_stats` - when the stats are flushed
* `del` - when a key is deleted and it will pass in the `key` and `value`.
```javascript
cache.on('set', (key, value) => {
console.log(`Key ${key} has been set with value ${value}`);
});
```
# NodeCacheStore
`NodeCacheStore` has a similar API to `NodeCache` but it is using `async / await` as it uses the `Keyv` storage adapters under the hood. This means that you can use all the storage adapters that are available in `Keyv` and it will work seamlessly with the `NodeCacheStore`. To learn more about the `Keyv` storage adapters you can check out the [Keyv documentation](https://keyv.org).
```javascript
import {NodeCacheStore} from '@cacheable/node-cache';
const cache = new NodeCacheStore();
await cache.set('foo', 'bar');
await cache.get('foo'); // 'bar'
```
Here is an example of how to use the `NodeCacheStore` with a primary and secondary storage adapter:
```javascript
import {NodeCacheStore} from '@cacheable/node-cache';
import {Keyv} from 'keyv';
import {KeyvRedis} from '@keyv/redis';
const primary = new Keyv(); // In-memory storage as primary
const secondary = new Keyv({store: new KeyvRedis('redis://user:pass@localhost:6379')});
const cache = new NodeCacheStore({primary, secondary});
// with storage you have the same functionality as the NodeCache but will be using async/await
await cache.set('foo', 'bar');
await cache.get('foo'); // 'bar'
// if you call getStats() this will now only be for the single instance of the adapter as it is in memory
cache.getStats(); // {hits: 1, misses: 1, keys: 1, ksize: 2, vsize: 3}
```
When initializing the cache you can pass in the options below:
```javascript
export type NodeCacheStoreOptions = {
ttl?: number; // The standard ttl as number in milliseconds for every generated cache element. 0 = unlimited
primary?: Keyv; // The primary storage adapter
secondary?: Keyv; // The secondary storage adapter
maxKeys?: number; // Default is 0 (unlimited). If this is set it will throw and error if you try to set more keys than the max.
stats?: boolean; // Default is true, if this is set to false it will not track stats
};
```
Note: the `ttl` is now in milliseconds and not seconds like `stdTTL` in `NodeCache`. You can learn more about using shorthand also in the [cacheable documentation](https://github.com/jaredwray/cacheable/blob/main/packages/cacheable/README.md#shorthand-for-time-to-live-ttl) as it is fully supported. Here is an example:
```javascript
const cache = new NodeCacheStore({ttl: 60000 }); // 1 minute as it defaults to milliseconds
await cache.set('foo', 'bar', '1h'); // 1 hour
await cache.set('longfoo', 'bar', '1d'); // 1 day
```
## NodeCacheStore API
* `set(key: string | number, value: any, ttl?: number): Promise<boolean>` - Set a key value pair with an optional ttl (in milliseconds). Will return true on success. If the ttl is not set it will default to 0 (no ttl)
* `mset(data: Array<NodeCacheItem>): Promise<boolean>` - Set multiple key value pairs at once
* `get<T>(key: string | number): Promise<T>` - Get a value from the cache by key
* `mget(keys: Array<string | number>): Promise<Record<string, unknown>>` - Get multiple values from the cache by keys
* `take<T>(key: string | number): Promise<T>` - Get a value from the cache by key and delete it
* `del(key: string | number): Promise<boolean>` - Delete a key
* `mdel(keys: Array<string | number>): Promise<boolean>` - Delete multiple keys
* `clear(): Promise<void>` - Clear the cache
* `setTtl(key: string | number, ttl: number): Promise<boolean>` - Set the ttl of a key
* `disconnect(): Promise<void>` - Disconnect the storage adapters
* `stats`: `NodeCacheStats` - Get the stats of the cache
* `ttl`: `number` | `string` - The standard ttl as number in seconds for every generated cache element. `< 0` or `undefined` = unlimited
* `primary`: `Keyv` - The primary storage adapter
* `secondary`: `Keyv` - The secondary storage adapter
* `maxKeys`: `number` - If this is set it will throw and error if you try to set more keys than the max
# How to Contribute
You can contribute by forking the repo and submitting a pull request. Please make sure to add tests and update the documentation. To learn more about how to contribute go to our main README [https://github.com/jaredwray/cacheable](https://github.com/jaredwray/cacheable). This will talk about how to `Open a Pull Request`, `Ask a Question`, or `Post an Issue`.
# License and Copyright
[MIT © Jared Wray](./LICENSE)
@@ -1,69 +0,0 @@
{
"name": "@cacheable/node-cache",
"version": "1.7.6",
"description": "Simple and Maintained fast NodeJS internal caching",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
}
},
"engines": {
"node": ">=18"
},
"repository": {
"type": "git",
"url": "git+https://github.com/jaredwray/cacheable.git",
"directory": "packages/node-cache"
},
"author": "Jared Wray <me@jaredwray.com>",
"license": "MIT",
"private": false,
"keywords": [
"cache",
"caching",
"node",
"nodejs",
"cacheable",
"cacheable-node-cache",
"node-cache",
"cacheable-node"
],
"devDependencies": {
"@biomejs/biome": "^2.3.8",
"@faker-js/faker": "^10.1.0",
"@types/node": "^25.0.2",
"@vitest/coverage-v8": "^4.0.15",
"rimraf": "^6.1.2",
"tsup": "^8.5.1",
"typescript": "^5.9.3",
"vitest": "^4.0.15"
},
"dependencies": {
"hookified": "^1.14.0",
"keyv": "^5.5.5",
"cacheable": "^2.3.1"
},
"files": [
"dist",
"license"
],
"scripts": {
"build": "rimraf ./dist && tsup src/index.ts --format cjs,esm --dts --clean",
"prepublish": "pnpm build",
"lint": "biome check --write --error-on-warnings",
"test": "pnpm lint && vitest run --coverage",
"test:ci": "biome check --error-on-warnings && vitest run --coverage",
"clean": "rimraf ./dist ./coverage ./node_modules"
}
}
@@ -1,19 +0,0 @@
MIT License & © Jared Wray
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
@@ -1,520 +0,0 @@
[<img align="center" src="https://cacheable.org/logo.svg" alt="Cacheable" />](https://github.com/jaredwray/cacheable)
> Cacheble Utils
[![codecov](https://codecov.io/gh/jaredwray/cacheable/branch/main/graph/badge.svg?token=lWZ9OBQ7GM)](https://codecov.io/gh/jaredwray/cacheable)
[![tests](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml/badge.svg)](https://github.com/jaredwray/cacheable/actions/workflows/tests.yml)
[![npm](https://img.shields.io/npm/dm/@cacheable/utils.svg)](https://www.npmjs.com/package/@cacheable/utils)
[![npm](https://img.shields.io/npm/v/@cacheable/utils)](https://www.npmjs.com/package/@cacheable/utils)
[![license](https://img.shields.io/github/license/jaredwray/cacheable)](https://github.com/jaredwray/cacheable/blob/main/LICENSE)
`@cacheable/utils` is a collecton of utility functions, helpers, and types for `cacheable` and other caching libraries. It provides a robust set of features to enhance caching capabilities, including:
* Data Types for Caching Items
* Hash Functions for Key Generation
* Coalesce Async for Handling Multiple Promises
* Stats Helpers for Caching Statistics
* Sleep / Delay for Testing and Timing
* Memoization for wraping or get / set options
* Time to Live (TTL) Helpers
# Table of Contents
* [Getting Started](#getting-started)
* [Cacheable Types](#cacheable-types)
* [Coalesce Async](#coalesce-async)
* [Hash Functions](#hash-functions)
* [Shorthand Time Helpers](#shorthand-time-helpers)
* [Sleep Helper](#sleep-helper)
* [Stats Helpers](#stats-helpers)
* [Time to Live (TTL) Helpers](#time-to-live-ttl-helpers)
* [Run if Function Helper](#run-if-function-helper)
* [Less Than Helper](#less-than-helper)
* [Is Object Helper](#is-object-helper)
* [Wrap / Memoization for Sync and Async Functions](#wrap--memoization-for-sync-and-async-functions)
* [Get Or Set Memoization Function](#get-or-set-memoization-function)
* [How to Contribute](#how-to-contribute)
* [License and Copyright](#license-and-copyright)
# Getting Started
```bash
npm install @cacheable/utils --save
```
# Cacheable Types
The `@cacheable/utils` package provides various types that are used throughout the caching library. These types help in defining the structure of cached items, ensuring type safety and consistency across your caching operations.
```typescript
/**
* CacheableItem
* @typedef {Object} CacheableItem
* @property {string} key - The key of the cacheable item
* @property {any} value - The value of the cacheable item
* @property {number|string} [ttl] - Time to Live - If you set a number it is miliseconds, if you set a string it is a human-readable
* format such as `1s` for 1 second or `1h` for 1 hour. Setting undefined means that it will use the default time-to-live. If both are
* undefined then it will not have a time-to-live.
*/
export type CacheableItem = {
key: string;
value: any;
ttl?: number | string;
};
/**
* CacheableStoreItem
* @typedef {Object} CacheableStoreItem
* @property {string} key - The key of the cacheable store item
* @property {any} value - The value of the cacheable store item
* @property {number} [expires] - The expiration time in milliseconds since epoch. If not set, the item does not expire.
*/
export type CacheableStoreItem = {
key: string;
value: any;
expires?: number;
};
```
# Coalesce Async
The `coalesceAsync` function is a utility that allows you to handle multiple asynchronous operations efficiently. It was designed by `Douglas Cayers` https://github.com/douglascayers/promise-coalesce. It helps in coalescing multiple promises into a single promise, ensuring that only one operation is executed at a time for the same key.
```typescript
import { coalesceAsync } from '@cacheable/utils';
const fetchData = async (key: string) => {
// Simulate an asynchronous operation
return new Promise((resolve) => setTimeout(() => resolve(`Data for ${key}`), 1000));
};
const result = await Promise.all([
coalesceAsync('my-key', fetchData),
coalesceAsync('my-key', fetchData),
coalesceAsync('my-key', fetchData),
]);
console.log(result); // Data for my-key only executed once
```
# Hash Functions
The `@cacheable/utils` package provides hash functions that can be used to generate unique keys for caching operations. These functions are useful for creating consistent and unique identifiers for cached items.
The hashing API provides both **async** (for cryptographic algorithms) and **sync** (for non-cryptographic algorithms) methods.
## Async Hashing (Cryptographic Algorithms)
Use `hash()` and `hashToNumber()` for cryptographic algorithms like SHA-256, SHA-384, and SHA-512:
```typescript
import { hash, hashToNumber, HashAlgorithm } from '@cacheable/utils';
// Hash using SHA-256 (default)
const key = await hash('my-cache-key');
console.log(key); // Unique hash for 'my-cache-key'
// Hash with specific algorithm
const sha512Hash = await hash('my-data', { algorithm: HashAlgorithm.SHA512 });
// Convert hash to number within range
const min = 0;
const max = 10;
const result = await hashToNumber({foo: 'bar'}, { min, max, algorithm: HashAlgorithm.SHA256 });
console.log(result); // A number between 0 and 10 based on the hash value
```
## Sync Hashing (Non-Cryptographic Algorithms)
Use `hashSync()` and `hashToNumberSync()` for faster, non-cryptographic algorithms like DJB2, FNV1, MURMER, and CRC32:
```typescript
import { hashSync, hashToNumberSync, HashAlgorithm } from '@cacheable/utils';
// Hash using DJB2 (default for sync)
const key = hashSync('my-cache-key');
console.log(key); // Unique hash for 'my-cache-key'
// Hash with specific algorithm
const fnv1Hash = hashSync('my-data', { algorithm: HashAlgorithm.FNV1 });
// Convert hash to number within range
const min = 0;
const max = 10;
const result = hashToNumberSync({foo: 'bar'}, { min, max, algorithm: HashAlgorithm.DJB2 });
console.log(result); // A number between 0 and 10 based on the hash value
```
## Available Hash Algorithms
**Cryptographic (Async):**
- `HashAlgorithm.SHA256` - SHA-256 (default for async methods)
- `HashAlgorithm.SHA384` - SHA-384
- `HashAlgorithm.SHA512` - SHA-512
**Non-Cryptographic (Sync):**
- `HashAlgorithm.DJB2` - DJB2 (default for sync methods)
- `HashAlgorithm.FNV1` - FNV-1
- `HashAlgorithm.MURMER` - Murmur hash
- `HashAlgorithm.CRC32` - CRC32
# Shorthand Time Helpers
The `@cacheable/utils` package provides a shorthand function to convert human-readable time strings into milliseconds. This is useful for setting time-to-live (TTL) values in caching operations.
You can also use the `shorthandToMilliseconds` function:
```typescript
import { shorthandToMilliseconds } from '@cacheable/utils';
const milliseconds = shorthandToMilliseconds('1h');
console.log(milliseconds); // 3600000
```
You can also use the `shorthandToTime` function to get the current date plus the shorthand time:
```typescript
import { shorthandToTime } from '@cacheable/utils';
const currentDate = new Date();
const timeInMs = shorthandToTime('1h', currentDate);
console.log(timeInMs); // Current date + 1 hour in milliseconds since epoch
```
# Sleep Helper
The `sleep` function is a utility that allows you to pause execution for a specified duration. This can be useful in testing scenarios or when you need to introduce delays in your code.
```typescript
import { sleep } from '@cacheable/utils';
await sleep(1000); // Pause for 1 second
console.log('Execution resumed after 1 second');
```
# Stats Helpers
The `@cacheable/utils` package provides statistics helpers that can be used to track and analyze caching operations. These helpers can be used to gather metrics such as hit rates, miss rates, and other performance-related statistics.
```typescript
import { stats } from '@cacheable/utils';
const cacheStats = stats();
cacheStats.incrementHits();
console.log(cacheStats.hits); // Get the hit rate of the cache
```
# Time to Live (TTL) Helpers
The `@cacheable/utils` package provides helpers for managing time-to-live (TTL) values for cached items.
You can use the `calculateTtlFromExpiration` function to calculate the TTL based on an expiration date:
```typescript
import { calculateTtlFromExpiration } from '@cacheable/utils';
const expirationDate = new Date(Date.now() + 1000 * 60 * 5); // 5 minutes from now
const ttl = calculateTtlFromExpiration(Date.now(), expirationDate);
console.log(ttl); // 300000
```
You can also use `getTtlFromExpires` to get the TTL from an expiration date:
```typescript
import { getTtlFromExpires } from '@cacheable/utils';
const expirationDate = new Date(Date.now() + 1000 * 60 * 5); // 5 minutes from now
const ttl = getTtlFromExpires(expirationDate);
console.log(ttl); // 300000
```
You can use `getCascadingTtl` to get the TTL for cascading cache operations:
```typescript
import { getCascadingTtl } from '@cacheable/utils';
const cacheableTtl = 1000 * 60 * 5; // 5 minutes
const primaryTtl = 1000 * 60 * 2; // 2 minutes
const secondaryTtl = 1000 * 60; // 1 minute
const ttl = getCascadingTtl(cacheableTtl, primaryTtl, secondaryTtl);
```
# Run if Function Helper
The `runIfFn` utility function provides a convenient way to conditionally execute functions or return values based on whether the input is a function or not. This pattern is commonly used in UI libraries and configuration systems where values can be either static or computed.
```typescript
import { runIfFn } from '@cacheable/utils';
// Static value - returns the value as-is
const staticValue = runIfFn('hello world');
console.log(staticValue); // 'hello world'
// Function with no arguments - executes the function
const dynamicValue = runIfFn(() => new Date().toISOString());
console.log(dynamicValue); // Current timestamp
// Function with arguments - executes with provided arguments
const sum = runIfFn((a: number, b: number) => a + b, 5, 10);
console.log(sum); // 15
// Complex example with conditional logic
const getConfig = (isDevelopment: boolean) => ({
apiUrl: isDevelopment ? 'http://localhost:3000' : 'https://api.example.com',
timeout: isDevelopment ? 5000 : 30000
});
const config = runIfFn(getConfig, true);
console.log(config); // { apiUrl: 'http://localhost:3000', timeout: 5000 }
```
# Less Than Helper
The `lessThan` utility function provides a safe way to compare two values and determine if the first value is less than the second. It only performs the comparison if both values are valid numbers, returning `false` for any non-number inputs.
```typescript
import { lessThan } from '@cacheable/utils';
// Basic number comparisons
console.log(lessThan(1, 2)); // true
console.log(lessThan(2, 1)); // false
console.log(lessThan(1, 1)); // false
// Works with negative numbers
console.log(lessThan(-1, 0)); // true
console.log(lessThan(-2, -1)); // true
// Works with decimal numbers
console.log(lessThan(1.5, 2.5)); // true
console.log(lessThan(2.7, 2.7)); // false
// Safe handling of non-number values
console.log(lessThan("1", 2)); // false
console.log(lessThan(1, "2")); // false
console.log(lessThan(null, 1)); // false
console.log(lessThan(undefined, 1)); // false
console.log(lessThan(NaN, 1)); // false
// Useful in filtering and sorting operations
const numbers = [5, 2, 8, 1, 9];
const lessThanFive = numbers.filter(n => lessThan(n, 5));
console.log(lessThanFive); // [2, 1]
// Safe comparison in conditional logic
function processValue(a?: number, b?: number) {
if (lessThan(a, b)) {
return `${a} is less than ${b}`;
}
return 'Invalid comparison or a >= b';
}
```
This utility is particularly useful when dealing with potentially undefined or invalid numeric values, ensuring type safety in comparison operations.
# Is Object Helper
The `isObject` utility function provides a type-safe way to determine if a value is a plain object. It returns `true` for objects but `false` for arrays, `null`, functions, and primitive types. This function also serves as a TypeScript type guard.
```typescript
import { isObject } from '@cacheable/utils';
// Basic object detection
console.log(isObject({})); // true
console.log(isObject({ name: 'John', age: 30 })); // true
console.log(isObject(Object.create(null))); // true
// Arrays are not considered objects
console.log(isObject([])); // false
console.log(isObject([1, 2, 3])); // false
// null is not considered an object (despite typeof null === 'object')
console.log(isObject(null)); // false
// Primitive types return false
console.log(isObject('string')); // false
console.log(isObject(123)); // false
console.log(isObject(true)); // false
console.log(isObject(undefined)); // false
// Functions return false
console.log(isObject(() => {})); // false
console.log(isObject(Date)); // false
// Built-in object types return true
console.log(isObject(new Date())); // true
console.log(isObject(/regex/)); // true
console.log(isObject(new Error('test'))); // true
console.log(isObject(new Map())); // true
// TypeScript type guard usage
function processValue(value: unknown) {
if (isObject<{ name: string; age: number }>(value)) {
// TypeScript now knows value is an object with name and age properties
console.log(`Name: ${value.name}, Age: ${value.age}`);
}
}
// Useful for configuration validation
function validateConfig(config: unknown) {
if (!isObject(config)) {
throw new Error('Configuration must be an object');
}
// Safe to access object properties
return config;
}
// Filtering arrays for objects only
const mixedArray = [1, 'string', {}, [], null, { valid: true }];
const objectsOnly = mixedArray.filter(isObject);
console.log(objectsOnly); // [{}', { valid: true }]
```
This utility is particularly useful for:
- **Type validation** - Ensuring values are objects before accessing properties
- **TypeScript type guarding** - Narrowing types in conditional blocks
- **Configuration parsing** - Validating that configuration values are objects
- **Data filtering** - Separating objects from other data types
# Wrap / Memoization for Sync and Async Functions
The `@cacheable/utils` package provides two main functions: `wrap` and `wrapSync`. These functions are used to memoize asynchronous and synchronous functions, respectively.
```javascript
import { Cacheable } from 'cacheable';
const asyncFunction = async (value: number) => {
return Math.random() * value;
};
const cache = new Cacheable();
const options = {
ttl: '1h', // 1 hour
keyPrefix: 'p1', // key prefix. This is used if you have multiple functions and need to set a unique prefix.
cache,
}
const wrappedFunction = wrap(asyncFunction, options);
console.log(await wrappedFunction(2)); // 4
console.log(await wrappedFunction(2)); // 4 from cache
```
With `wrap` we have also included stampede protection so that a `Promise` based call will only be called once if multiple requests of the same are executed at the same time. Here is an example of how to test for stampede protection:
```javascript
import { Cacheable } from 'cacheable';
const asyncFunction = async (value: number) => {
return value;
};
const cache = new Cacheable();
const options = {
ttl: '1h', // 1 hour
keyPrefix: 'p1', // key prefix. This is used if you have multiple functions and need to set a unique prefix.
cache,
}
const wrappedFunction = wrap(asyncFunction, options);
const promises = [];
for (let i = 0; i < 10; i++) {
promises.push(wrappedFunction(i));
}
const results = await Promise.all(promises); // all results should be the same
console.log(results); // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
```
In this example we are wrapping an `async` function in a cache with a `ttl` of `1 hour`. This will cache the result of the function for `1 hour` and then expire the value. You can also wrap a `sync` function in a cache:
```javascript
import { CacheableMemory } from 'cacheable';
const syncFunction = (value: number) => {
return value * 2;
};
const cache = new CacheableMemory();
const wrappedFunction = wrap(syncFunction, { ttl: '1h', key: 'syncFunction', cache });
console.log(wrappedFunction(2)); // 4
console.log(wrappedFunction(2)); // 4 from cache
```
In this example we are wrapping a `sync` function in a cache with a `ttl` of `1 hour`. This will cache the result of the function for `1 hour` and then expire the value. You can also set the `key` property in the `wrap()` options to set a custom key for the cache.
When an error occurs in the function it will not cache the value and will return the error. This is useful if you want to cache the results of a function but not cache the error. If you want it to cache the error you can set the `cacheError` property to `true` in the `wrap()` options. This is disabled by default.
```javascript
import { CacheableMemory } from 'cacheable';
const syncFunction = (value: number) => {
throw new Error('error');
};
const cache = new CacheableMemory();
const wrappedFunction = wrap(syncFunction, { ttl: '1h', key: 'syncFunction', cacheError: true, cache });
console.log(wrappedFunction()); // error
console.log(wrappedFunction()); // error from cache
```
If you would like to generate your own key for the wrapped function you can set the `createKey` property in the `wrap()` options. This is useful if you want to generate a key based on the arguments of the function or any other criteria.
```javascript
const cache = new Cacheable();
const options: WrapOptions = {
cache,
keyPrefix: 'test',
createKey: (function_, arguments_, options: WrapOptions) => `customKey:${options?.keyPrefix}:${arguments_[0]}`,
};
const wrapped = wrap((argument: string) => `Result for ${argument}`, options);
const result1 = await wrapped('arg1');
const result2 = await wrapped('arg1'); // Should hit the cache
console.log(result1); // Result for arg1
console.log(result2); // Result for arg1 (from cache)
```
We will pass in the `function` that is being wrapped, the `arguments` passed to the function, and the `options` used to wrap the function. You can then use these to generate a custom key for the cache.
# Get Or Set Memoization Function
The `getOrSet` method provides a convenient way to implement the cache-aside pattern. It attempts to retrieve a value from cache, and if not found, calls the provided function to compute the value and store it in cache before returning it. Here are the options:
```typescript
export type GetOrSetFunctionOptions = {
ttl?: number | string;
cacheErrors?: boolean;
throwErrors?: boolean;
nonBlocking?: boolean;
};
```
The `nonBlocking` option allows you to override the instance-level `nonBlocking` setting for the `get` call within `getOrSet`. When set to `false`, the `get` will block and wait for a response from the secondary store before deciding whether to call the provided function. When set to `true`, the primary store returns immediately and syncs from secondary in the background.
Here is an example of how to use the `getOrSet` method:
```javascript
import { Cacheable } from 'cacheable';
const cache = new Cacheable();
// Use getOrSet to fetch user data
const function_ = async () => Math.random() * 100;
const value = await getOrSet('randomValue', function_, { ttl: '1h', cache });
console.log(value); // e.g. 42.123456789
```
You can also use a function to compute the key for the function:
```javascript
import { Cacheable, GetOrSetOptions } from 'cacheable';
const cache = new Cacheable();
// Function to generate a key based on options
const generateKey = (options?: GetOrSetOptions) => {
return `custom_key_:${options?.cacheId || 'default'}`;
};
const function_ = async () => Math.random() * 100;
const value = await getOrSet(generateKey(), function_, { ttl: '1h', cache });
```
# How to Contribute
You can contribute by forking the repo and submitting a pull request. Please make sure to add tests and update the documentation. To learn more about how to contribute go to our main README [https://github.com/jaredwray/cacheable](https://github.com/jaredwray/cacheable). This will talk about how to `Open a Pull Request`, `Ask a Question`, or `Post an Issue`.
# License and Copyright
[MIT © Jared Wray](./LICENSE)
@@ -1,56 +0,0 @@
{
"name": "@cacheable/utils",
"version": "2.4.1",
"description": "Cacheable Utilities for Caching Libraries",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
}
},
"repository": {
"type": "git",
"url": "git+https://github.com/jaredwray/cacheable.git",
"directory": "packages/utils"
},
"author": "Jared Wray <me@jaredwray.com>",
"license": "MIT",
"private": false,
"dependencies": {
"hashery": "^1.5.1",
"keyv": "^5.6.0"
},
"devDependencies": {
"tsup": "^8.5.1",
"typescript": "^5.9.3"
},
"keywords": [
"cacheable",
"caching",
"utilities",
"hashing",
"keyv",
"cache utils"
],
"files": [
"dist",
"LICENSE"
],
"scripts": {
"build": "rimraf ./dist && tsup src/index.ts --format cjs,esm --dts --clean",
"lint": "biome check --write --error-on-warnings",
"test": "pnpm lint && vitest run --coverage",
"test:ci": "biome check --error-on-warnings && vitest run --coverage",
"clean": "rimraf ./dist ./coverage ./node_modules"
}
}
@@ -1,10 +0,0 @@
Copyright (c) 2012-2020, Sideway Inc, and project contributors
Copyright (c) 2012-2014, Walmart.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* The names of any contributors may not be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS OFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -1,17 +0,0 @@
<a href="https://hapi.dev"><img src="https://raw.githubusercontent.com/hapijs/assets/master/images/family.png" width="180px" align="right" /></a>
# @hapi/boom
#### HTTP-friendly error objects.
**boom** is part of the **hapi** ecosystem and was designed to work seamlessly with the [hapi web framework](https://hapi.dev) and its other components (but works great on its own or with other frameworks). If you are using a different web framework and find this module useful, check out [hapi](https://hapi.dev) they work even better together.
### Visit the [hapi.dev](https://hapi.dev) Developer Portal for tutorials, documentation, and support
## Useful resources
- [Documentation and API](https://hapi.dev/family/boom/)
- [Version status](https://hapi.dev/resources/status/#boom) (builds, dependencies, node versions, licenses, eol)
- [Changelog](https://hapi.dev/family/boom/changelog/)
- [Project policies](https://hapi.dev/policies/)
- [Free and commercial support options](https://hapi.dev/support/)
@@ -1,549 +0,0 @@
/**
* An Error object used to return an HTTP response error (4xx, 5xx)
*/
export class Boom<Data = any> extends Error {
/**
* Creates a new Boom object using the provided message
*/
constructor(message?: string | Error, options?: Options<Data>);
/**
* Custom error data with additional information specific to the error type
*/
data?: Data;
/**
* isBoom - if true, indicates this is a Boom object instance.
*/
isBoom: boolean;
/**
* Convenience boolean indicating status code >= 500
*/
isServer: boolean;
/**
* The error message
*/
message: string;
/**
* The formatted response
*/
output: Output;
/**
* The constructor used to create the error
*/
typeof: Function;
/**
* Specifies if an error object is a valid boom object
*
* @param debug - A boolean that, when true, does not hide the original 500 error message. Defaults to false.
*/
reformat(debug?: boolean): string;
}
export interface Options<Data> {
/**
* The HTTP status code
*
* @default 500
*/
statusCode?: number;
/**
* Additional error information
*/
data?: Data;
/**
* Constructor reference used to crop the exception call stack output
*/
ctor?: Function;
/**
* Error message string
*
* @default none
*/
message?: string;
/**
* If false, the err provided is a Boom object, and a statusCode or message are provided, the values are ignored
*
* @default true
*/
override?: boolean;
}
export interface Decorate<Decoration> {
/**
* An option with extra properties to set on the error object
*/
decorate?: Decoration;
}
export interface Payload {
/**
* The HTTP status code derived from error.output.statusCode
*/
statusCode: number;
/**
* The HTTP status message derived from statusCode
*/
error: string;
/**
* The error message derived from error.message
*/
message: string;
/**
* Custom properties
*/
[key: string]: unknown;
}
export interface Output {
/**
* The HTTP status code
*/
statusCode: number;
/**
* An object containing any HTTP headers where each key is a header name and value is the header content
*/
headers: { [header: string]: string | string[] | number | undefined };
/**
* The formatted object used as the response payload (stringified)
*/
payload: Payload;
}
/**
* Specifies if an object is a valid boom object
*
* @param obj - The object to assess
* @param statusCode - Optional status code
*
* @returns Returns a boolean stating if the error object is a valid boom object and it has the provided statusCode (if present)
*/
export function isBoom(obj: unknown, statusCode?: number): obj is Boom;
/**
* Specifies if an error object is a valid boom object
*
* @param err - The error object to decorate
* @param options - Options object
*
* @returns A decorated boom object
*/
export function boomify<Data, Decoration>(err: Error, options?: Options<Data> & Decorate<Decoration>): Boom<Data> & Decoration;
// 4xx Errors
/**
* Returns a 400 Bad Request error
*
* @param message - Optional message
* @param data - Optional additional error data
*
* @returns A 400 bad request error
*/
export function badRequest<Data>(message?: string, data?: Data): Boom<Data>;
/**
* Returns a 401 Unauthorized error
*
* @param message - Optional message
*
* @returns A 401 Unauthorized error
*/
export function unauthorized<Data>(message?: string | null): Boom<Data>;
/**
* Returns a 401 Unauthorized error
*
* @param message - Optional message
* @param scheme - the authentication scheme name
* @param attributes - an object of values used to construct the 'WWW-Authenticate' header
*
* @returns A 401 Unauthorized error
*/
export function unauthorized<Data>(message: '' | null, scheme: string, attributes?: string | unauthorized.Attributes): Boom<Data> & unauthorized.MissingAuth;
export function unauthorized<Data>(message: string | null, scheme: string, attributes?: string | unauthorized.Attributes): Boom<Data>;
export namespace unauthorized {
interface Attributes {
[index: string]: number | string | null | undefined;
}
interface MissingAuth {
/**
* Indicate whether the 401 unauthorized error is due to missing credentials (vs. invalid)
*/
isMissing: boolean;
}
}
/**
* Returns a 401 Unauthorized error
*
* @param message - Optional message
* @param wwwAuthenticate - array of string values used to construct the wwwAuthenticate header
*
* @returns A 401 Unauthorized error
*/
export function unauthorized<Data>(message: string | null, wwwAuthenticate: string[]): Boom<Data>;
/**
* Returns a 402 Payment Required error
*
* @param message - Optional message
* @param data - Optional additional error data
*
* @returns A 402 Payment Required error
*/
export function paymentRequired<Data>(message?: string, data?: Data): Boom<Data>;
/**
* Returns a 403 Forbidden error
*
* @param message - Optional message
* @param data - Optional additional error data
*
* @returns A 403 Forbidden error
*/
export function forbidden<Data>(message?: string, data?: Data): Boom<Data>;
/**
* Returns a 404 Not Found error
*
* @param message - Optional message
* @param data - Optional additional error data
*
* @returns A 404 Not Found error
*/
export function notFound<Data>(message?: string, data?: Data): Boom<Data>;
/**
* Returns a 405 Method Not Allowed error
*
* @param message - Optional message
* @param data - Optional additional error data
* @param allow - Optional string or array of strings which is used to set the 'Allow' header
*
* @returns A 405 Method Not Allowed error
*/
export function methodNotAllowed<Data>(message?: string, data?: Data, allow?: string | string[]): Boom<Data>;
/**
* Returns a 406 Not Acceptable error
*
* @param message - Optional message
* @param data - Optional additional error data
*
* @returns A 406 Not Acceptable error
*/
export function notAcceptable<Data>(message?: string, data?: Data): Boom<Data>;
/**
* Returns a 407 Proxy Authentication error
*
* @param message - Optional message
* @param data - Optional additional error data
*
* @returns A 407 Proxy Authentication error
*/
export function proxyAuthRequired<Data>(message?: string, data?: Data): Boom<Data>;
/**
* Returns a 408 Request Time-out error
*
* @param message - Optional message
* @param data - Optional additional error data
*
* @returns A 408 Request Time-out error
*/
export function clientTimeout<Data>(message?: string, data?: Data): Boom<Data>;
/**
* Returns a 409 Conflict error
*
* @param message - Optional message
* @param data - Optional additional error data
*
* @returns A 409 Conflict error
*/
export function conflict<Data>(message?: string, data?: Data): Boom<Data>;
/**
* Returns a 410 Gone error
*
* @param message - Optional message
* @param data - Optional additional error data
*
* @returns A 410 gone error
*/
export function resourceGone<Data>(message?: string, data?: Data): Boom<Data>;
/**
* Returns a 411 Length Required error
*
* @param message - Optional message
* @param data - Optional additional error data
*
* @returns A 411 Length Required error
*/
export function lengthRequired<Data>(message?: string, data?: Data): Boom<Data>;
/**
* Returns a 412 Precondition Failed error
*
* @param message - Optional message
* @param data - Optional additional error data
*
* @returns A 412 Precondition Failed error
*/
export function preconditionFailed<Data>(message?: string, data?: Data): Boom<Data>;
/**
* Returns a 413 Request Entity Too Large error
*
* @param message - Optional message
* @param data - Optional additional error data
*
* @returns A 413 Request Entity Too Large error
*/
export function entityTooLarge<Data>(message?: string, data?: Data): Boom<Data>;
/**
* Returns a 414 Request-URI Too Large error
*
* @param message - Optional message
* @param data - Optional additional error data
*
* @returns A 414 Request-URI Too Large error
*/
export function uriTooLong<Data>(message?: string, data?: Data): Boom<Data>;
/**
* Returns a 415 Unsupported Media Type error
*
* @param message - Optional message
* @param data - Optional additional error data
*
* @returns A 415 Unsupported Media Type error
*/
export function unsupportedMediaType<Data>(message?: string, data?: Data): Boom<Data>;
/**
* Returns a 416 Request Range Not Satisfiable error
*
* @param message - Optional message
* @param data - Optional additional error data
*
* @returns A 416 Request Range Not Satisfiable error
*/
export function rangeNotSatisfiable<Data>(message?: string, data?: Data): Boom<Data>;
/**
* Returns a 417 Expectation Failed error
*
* @param message - Optional message
* @param data - Optional additional error data
*
* @returns A 417 Expectation Failed error
*/
export function expectationFailed<Data>(message?: string, data?: Data): Boom<Data>;
/**
* Returns a 418 I'm a Teapot error
*
* @param message - Optional message
* @param data - Optional additional error data
*
* @returns A 418 I'm a Teapot error
*/
export function teapot<Data>(message?: string, data?: Data): Boom<Data>;
/**
* Returns a 422 Unprocessable Entity error
*
* @param message - Optional message
* @param data - Optional additional error data
*
* @returns A 422 Unprocessable Entity error
*/
export function badData<Data>(message?: string, data?: Data): Boom<Data>;
/**
* Returns a 423 Locked error
*
* @param message - Optional message
* @param data - Optional additional error data
*
* @returns A 423 Locked error
*/
export function locked<Data>(message?: string, data?: Data): Boom<Data>;
/**
* Returns a 424 Failed Dependency error
*
* @param message - Optional message
* @param data - Optional additional error data
*
* @returns A 424 Failed Dependency error
*/
export function failedDependency<Data>(message?: string, data?: Data): Boom<Data>;
/**
* Returns a 425 Too Early error
*
* @param message - Optional message
* @param data - Optional additional error data
*
* @returns A 425 Too Early error
*/
export function tooEarly<Data>(message?: string, data?: Data): Boom<Data>;
/**
* Returns a 428 Precondition Required error
*
* @param message - Optional message
* @param data - Optional additional error data
*
* @returns A 428 Precondition Required error
*/
export function preconditionRequired<Data>(message?: string, data?: Data): Boom<Data>;
/**
* Returns a 429 Too Many Requests error
*
* @param message - Optional message
* @param data - Optional additional error data
*
* @returns A 429 Too Many Requests error
*/
export function tooManyRequests<Data>(message?: string, data?: Data): Boom<Data>;
/**
* Returns a 451 Unavailable For Legal Reasons error
*
* @param message - Optional message
* @param data - Optional additional error data
*
* @returns A 451 Unavailable for Legal Reasons error
*/
export function illegal<Data>(message?: string, data?: Data): Boom<Data>;
// 5xx Errors
/**
* Returns a internal error (defaults to 500)
*
* @param message - Optional message
* @param data - Optional additional error data
* @param statusCode - Optional status code override. Defaults to 500.
*
* @returns A 500 Internal Server error
*/
export function internal<Data>(message?: string, data?: Data, statusCode?: number): Boom<Data>;
/**
* Returns a 500 Internal Server Error error
*
* @param message - Optional message
* @param data - Optional additional error data
*
* @returns A 500 Internal Server error
*/
export function badImplementation<Data>(message?: string, data?: Data): Boom<Data>;
/**
* Returns a 501 Not Implemented error
*
* @param message - Optional message
* @param data - Optional additional error data
*
* @returns A 501 Not Implemented error
*/
export function notImplemented<Data>(message?: string, data?: Data): Boom<Data>;
/**
* Returns a 502 Bad Gateway error
*
* @param message - Optional message
* @param data - Optional additional error data
*
* @returns A 502 Bad Gateway error
*/
export function badGateway<Data>(message?: string, data?: Data): Boom<Data>;
/**
* Returns a 503 Service Unavailable error
*
* @param message - Optional message
* @param data - Optional additional error data
*
* @returns A 503 Service Unavailable error
*/
export function serverUnavailable<Data>(message?: string, data?: Data): Boom<Data>;
/**
* Returns a 504 Gateway Time-out error
*
* @param message - Optional message
* @param data - Optional additional error data
*
* @returns A 504 Gateway Time-out error
*/
export function gatewayTimeout<Data>(message?: string, data?: Data): Boom<Data>;
@@ -1,469 +0,0 @@
'use strict';
const Hoek = require('@hapi/hoek');
const internals = {
codes: new Map([
[100, 'Continue'],
[101, 'Switching Protocols'],
[102, 'Processing'],
[200, 'OK'],
[201, 'Created'],
[202, 'Accepted'],
[203, 'Non-Authoritative Information'],
[204, 'No Content'],
[205, 'Reset Content'],
[206, 'Partial Content'],
[207, 'Multi-Status'],
[300, 'Multiple Choices'],
[301, 'Moved Permanently'],
[302, 'Moved Temporarily'],
[303, 'See Other'],
[304, 'Not Modified'],
[305, 'Use Proxy'],
[307, 'Temporary Redirect'],
[400, 'Bad Request'],
[401, 'Unauthorized'],
[402, 'Payment Required'],
[403, 'Forbidden'],
[404, 'Not Found'],
[405, 'Method Not Allowed'],
[406, 'Not Acceptable'],
[407, 'Proxy Authentication Required'],
[408, 'Request Time-out'],
[409, 'Conflict'],
[410, 'Gone'],
[411, 'Length Required'],
[412, 'Precondition Failed'],
[413, 'Request Entity Too Large'],
[414, 'Request-URI Too Large'],
[415, 'Unsupported Media Type'],
[416, 'Requested Range Not Satisfiable'],
[417, 'Expectation Failed'],
[418, 'I\'m a teapot'],
[422, 'Unprocessable Entity'],
[423, 'Locked'],
[424, 'Failed Dependency'],
[425, 'Too Early'],
[426, 'Upgrade Required'],
[428, 'Precondition Required'],
[429, 'Too Many Requests'],
[431, 'Request Header Fields Too Large'],
[451, 'Unavailable For Legal Reasons'],
[500, 'Internal Server Error'],
[501, 'Not Implemented'],
[502, 'Bad Gateway'],
[503, 'Service Unavailable'],
[504, 'Gateway Time-out'],
[505, 'HTTP Version Not Supported'],
[506, 'Variant Also Negotiates'],
[507, 'Insufficient Storage'],
[509, 'Bandwidth Limit Exceeded'],
[510, 'Not Extended'],
[511, 'Network Authentication Required']
])
};
exports.Boom = class extends Error {
constructor(message, options = {}) {
if (message instanceof Error) {
return exports.boomify(Hoek.clone(message), options);
}
const { statusCode = 500, data = null, ctor = exports.Boom } = options;
const error = new Error(message ? message : undefined); // Avoids settings null message
Error.captureStackTrace(error, ctor); // Filter the stack to our external API
error.data = data;
const boom = internals.initialize(error, statusCode);
Object.defineProperty(boom, 'typeof', { value: ctor });
if (options.decorate) {
Object.assign(boom, options.decorate);
}
return boom;
}
static [Symbol.hasInstance](instance) {
if (this === exports.Boom) {
return exports.isBoom(instance);
}
// Cannot use 'instanceof' as it creates infinite recursion
return this.prototype.isPrototypeOf(instance);
}
};
exports.isBoom = function (err, statusCode) {
return err instanceof Error && !!err.isBoom && (!statusCode || err.output.statusCode === statusCode);
};
exports.boomify = function (err, options) {
Hoek.assert(err instanceof Error, 'Cannot wrap non-Error object');
options = options || {};
if (options.data !== undefined) {
err.data = options.data;
}
if (options.decorate) {
Object.assign(err, options.decorate);
}
if (!err.isBoom) {
return internals.initialize(err, options.statusCode || 500, options.message);
}
if (options.override === false || // Defaults to true
!options.statusCode && !options.message) {
return err;
}
return internals.initialize(err, options.statusCode || err.output.statusCode, options.message);
};
// 4xx Client Errors
exports.badRequest = function (message, data) {
return new exports.Boom(message, { statusCode: 400, data, ctor: exports.badRequest });
};
exports.unauthorized = function (message, scheme, attributes) { // Or (message, wwwAuthenticate[])
const err = new exports.Boom(message, { statusCode: 401, ctor: exports.unauthorized });
// function (message)
if (!scheme) {
return err;
}
// function (message, wwwAuthenticate[])
if (typeof scheme !== 'string') {
err.output.headers['WWW-Authenticate'] = scheme.join(', ');
return err;
}
// function (message, scheme, attributes)
let wwwAuthenticate = `${scheme}`;
if (attributes ||
message) {
err.output.payload.attributes = {};
}
if (attributes) {
if (typeof attributes === 'string') {
wwwAuthenticate += ' ' + Hoek.escapeHeaderAttribute(attributes);
err.output.payload.attributes = attributes;
}
else {
wwwAuthenticate += ' ' + Object.keys(attributes).map((name) => {
let value = attributes[name];
if (value === null ||
value === undefined) {
value = '';
}
err.output.payload.attributes[name] = value;
return `${name}="${Hoek.escapeHeaderAttribute(value.toString())}"`;
})
.join(', ');
}
}
if (message) {
if (attributes) {
wwwAuthenticate += ',';
}
wwwAuthenticate += ` error="${Hoek.escapeHeaderAttribute(message)}"`;
err.output.payload.attributes.error = message;
}
else {
err.isMissing = true;
}
err.output.headers['WWW-Authenticate'] = wwwAuthenticate;
return err;
};
exports.paymentRequired = function (message, data) {
return new exports.Boom(message, { statusCode: 402, data, ctor: exports.paymentRequired });
};
exports.forbidden = function (message, data) {
return new exports.Boom(message, { statusCode: 403, data, ctor: exports.forbidden });
};
exports.notFound = function (message, data) {
return new exports.Boom(message, { statusCode: 404, data, ctor: exports.notFound });
};
exports.methodNotAllowed = function (message, data, allow) {
const err = new exports.Boom(message, { statusCode: 405, data, ctor: exports.methodNotAllowed });
if (typeof allow === 'string') {
allow = [allow];
}
if (Array.isArray(allow)) {
err.output.headers.Allow = allow.join(', ');
}
return err;
};
exports.notAcceptable = function (message, data) {
return new exports.Boom(message, { statusCode: 406, data, ctor: exports.notAcceptable });
};
exports.proxyAuthRequired = function (message, data) {
return new exports.Boom(message, { statusCode: 407, data, ctor: exports.proxyAuthRequired });
};
exports.clientTimeout = function (message, data) {
return new exports.Boom(message, { statusCode: 408, data, ctor: exports.clientTimeout });
};
exports.conflict = function (message, data) {
return new exports.Boom(message, { statusCode: 409, data, ctor: exports.conflict });
};
exports.resourceGone = function (message, data) {
return new exports.Boom(message, { statusCode: 410, data, ctor: exports.resourceGone });
};
exports.lengthRequired = function (message, data) {
return new exports.Boom(message, { statusCode: 411, data, ctor: exports.lengthRequired });
};
exports.preconditionFailed = function (message, data) {
return new exports.Boom(message, { statusCode: 412, data, ctor: exports.preconditionFailed });
};
exports.entityTooLarge = function (message, data) {
return new exports.Boom(message, { statusCode: 413, data, ctor: exports.entityTooLarge });
};
exports.uriTooLong = function (message, data) {
return new exports.Boom(message, { statusCode: 414, data, ctor: exports.uriTooLong });
};
exports.unsupportedMediaType = function (message, data) {
return new exports.Boom(message, { statusCode: 415, data, ctor: exports.unsupportedMediaType });
};
exports.rangeNotSatisfiable = function (message, data) {
return new exports.Boom(message, { statusCode: 416, data, ctor: exports.rangeNotSatisfiable });
};
exports.expectationFailed = function (message, data) {
return new exports.Boom(message, { statusCode: 417, data, ctor: exports.expectationFailed });
};
exports.teapot = function (message, data) {
return new exports.Boom(message, { statusCode: 418, data, ctor: exports.teapot });
};
exports.badData = function (message, data) {
return new exports.Boom(message, { statusCode: 422, data, ctor: exports.badData });
};
exports.locked = function (message, data) {
return new exports.Boom(message, { statusCode: 423, data, ctor: exports.locked });
};
exports.failedDependency = function (message, data) {
return new exports.Boom(message, { statusCode: 424, data, ctor: exports.failedDependency });
};
exports.tooEarly = function (message, data) {
return new exports.Boom(message, { statusCode: 425, data, ctor: exports.tooEarly });
};
exports.preconditionRequired = function (message, data) {
return new exports.Boom(message, { statusCode: 428, data, ctor: exports.preconditionRequired });
};
exports.tooManyRequests = function (message, data) {
return new exports.Boom(message, { statusCode: 429, data, ctor: exports.tooManyRequests });
};
exports.illegal = function (message, data) {
return new exports.Boom(message, { statusCode: 451, data, ctor: exports.illegal });
};
// 5xx Server Errors
exports.internal = function (message, data, statusCode = 500) {
return internals.serverError(message, data, statusCode, exports.internal);
};
exports.notImplemented = function (message, data) {
return internals.serverError(message, data, 501, exports.notImplemented);
};
exports.badGateway = function (message, data) {
return internals.serverError(message, data, 502, exports.badGateway);
};
exports.serverUnavailable = function (message, data) {
return internals.serverError(message, data, 503, exports.serverUnavailable);
};
exports.gatewayTimeout = function (message, data) {
return internals.serverError(message, data, 504, exports.gatewayTimeout);
};
exports.badImplementation = function (message, data) {
const err = internals.serverError(message, data, 500, exports.badImplementation);
err.isDeveloperError = true;
return err;
};
internals.initialize = function (err, statusCode, message) {
const numberCode = parseInt(statusCode, 10);
Hoek.assert(!isNaN(numberCode) && numberCode >= 400, 'First argument must be a number (400+):', statusCode);
err.isBoom = true;
err.isServer = numberCode >= 500;
if (!err.hasOwnProperty('data')) {
err.data = null;
}
err.output = {
statusCode: numberCode,
payload: {},
headers: {}
};
Object.defineProperty(err, 'reformat', { value: internals.reformat, configurable: true });
if (!message &&
!err.message) {
err.reformat();
message = err.output.payload.error;
}
if (message) {
const props = Object.getOwnPropertyDescriptor(err, 'message') || Object.getOwnPropertyDescriptor(Object.getPrototypeOf(err), 'message');
Hoek.assert(!props || props.configurable && !props.get, 'The error is not compatible with boom');
err.message = message + (err.message ? ': ' + err.message : '');
err.output.payload.message = err.message;
}
err.reformat();
return err;
};
internals.reformat = function (debug = false) {
this.output.payload.statusCode = this.output.statusCode;
this.output.payload.error = internals.codes.get(this.output.statusCode) || 'Unknown';
if (this.output.statusCode === 500 && debug !== true) {
this.output.payload.message = 'An internal server error occurred'; // Hide actual error from user
}
else if (this.message) {
this.output.payload.message = this.message;
}
};
internals.serverError = function (message, data, statusCode, ctor) {
if (data instanceof Error &&
!data.isBoom) {
return exports.boomify(data, { statusCode, message });
}
return new exports.Boom(message, { statusCode, data, ctor });
};
@@ -1,28 +0,0 @@
{
"name": "@hapi/boom",
"description": "HTTP-friendly error objects",
"version": "9.1.4",
"repository": "git://github.com/hapijs/boom",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"keywords": [
"error",
"http"
],
"files": [
"lib"
],
"dependencies": {
"@hapi/hoek": "9.x.x"
},
"devDependencies": {
"@hapi/code": "8.x.x",
"@hapi/lab": "24.x.x",
"typescript": "~4.0.2"
},
"scripts": {
"test": "lab -a @hapi/code -t 100 -L -Y",
"test-cov-html": "lab -a @hapi/code -t 100 -L -r html -o coverage.html"
},
"license": "BSD-3-Clause"
}
@@ -1,12 +0,0 @@
Copyright (c) 2011-2020, Sideway Inc, and project contributors
Copyright (c) 2011-2014, Walmart
Copyright (c) 2011, Yahoo Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* The names of any contributors may not be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS OFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -1,19 +0,0 @@
<a href="https://hapi.dev"><img src="https://raw.githubusercontent.com/hapijs/assets/master/images/family.png" width="180px" align="right" /></a>
# @hapi/hoek
#### Utility methods for the hapi ecosystem.
**hoek** is part of the **hapi** ecosystem and was designed to work seamlessly with the [hapi web framework](https://hapi.dev) and its other components (but works great on its own or with other frameworks). If you are using a different web framework and find this module useful, check out [hapi](https://hapi.dev) they work even better together.
This module is not intended to solve every problem for everyone, but rather as a central place to store hapi-specific methods. If you're looking for a general purpose utility module, check out [lodash](https://github.com/lodash/lodash).
### Visit the [hapi.dev](https://hapi.dev) Developer Portal for tutorials, documentation, and support
## Useful resources
- [Documentation and API](https://hapi.dev/family/hoek/)
- [Version status](https://hapi.dev/resources/status/#hoek) (builds, dependencies, node versions, licenses, eol)
- [Changelog](https://hapi.dev/family/hoek/changelog/)
- [Project policies](https://hapi.dev/policies/)
- [Free and commercial support options](https://hapi.dev/support/)
@@ -1,102 +0,0 @@
'use strict';
const Assert = require('./assert');
const Clone = require('./clone');
const Merge = require('./merge');
const Reach = require('./reach');
const internals = {};
module.exports = function (defaults, source, options = {}) {
Assert(defaults && typeof defaults === 'object', 'Invalid defaults value: must be an object');
Assert(!source || source === true || typeof source === 'object', 'Invalid source value: must be true, falsy or an object');
Assert(typeof options === 'object', 'Invalid options: must be an object');
if (!source) { // If no source, return null
return null;
}
if (options.shallow) {
return internals.applyToDefaultsWithShallow(defaults, source, options);
}
const copy = Clone(defaults);
if (source === true) { // If source is set to true, use defaults
return copy;
}
const nullOverride = options.nullOverride !== undefined ? options.nullOverride : false;
return Merge(copy, source, { nullOverride, mergeArrays: false });
};
internals.applyToDefaultsWithShallow = function (defaults, source, options) {
const keys = options.shallow;
Assert(Array.isArray(keys), 'Invalid keys');
const seen = new Map();
const merge = source === true ? null : new Set();
for (let key of keys) {
key = Array.isArray(key) ? key : key.split('.'); // Pre-split optimization
const ref = Reach(defaults, key);
if (ref &&
typeof ref === 'object') {
seen.set(ref, merge && Reach(source, key) || ref);
}
else if (merge) {
merge.add(key);
}
}
const copy = Clone(defaults, {}, seen);
if (!merge) {
return copy;
}
for (const key of merge) {
internals.reachCopy(copy, source, key);
}
const nullOverride = options.nullOverride !== undefined ? options.nullOverride : false;
return Merge(copy, source, { nullOverride, mergeArrays: false });
};
internals.reachCopy = function (dst, src, path) {
for (const segment of path) {
if (!(segment in src)) {
return;
}
const val = src[segment];
if (typeof val !== 'object' || val === null) {
return;
}
src = val;
}
const value = src;
let ref = dst;
for (let i = 0; i < path.length - 1; ++i) {
const segment = path[i];
if (typeof ref[segment] !== 'object') {
ref[segment] = {};
}
ref = ref[segment];
}
ref[path[path.length - 1]] = value;
};
@@ -1,22 +0,0 @@
'use strict';
const AssertError = require('./error');
const internals = {};
module.exports = function (condition, ...args) {
if (condition) {
return;
}
if (args.length === 1 &&
args[0] instanceof Error) {
throw args[0];
}
throw new AssertError(args);
};
@@ -1,29 +0,0 @@
'use strict';
const internals = {};
module.exports = internals.Bench = class {
constructor() {
this.ts = 0;
this.reset();
}
reset() {
this.ts = internals.Bench.now();
}
elapsed() {
return internals.Bench.now() - this.ts;
}
static now() {
const ts = process.hrtime();
return (ts[0] * 1e3) + (ts[1] / 1e6);
}
};
@@ -1,12 +0,0 @@
'use strict';
const Ignore = require('./ignore');
const internals = {};
module.exports = function () {
return new Promise(Ignore);
};
@@ -1,176 +0,0 @@
'use strict';
const Reach = require('./reach');
const Types = require('./types');
const Utils = require('./utils');
const internals = {
needsProtoHack: new Set([Types.set, Types.map, Types.weakSet, Types.weakMap])
};
module.exports = internals.clone = function (obj, options = {}, _seen = null) {
if (typeof obj !== 'object' ||
obj === null) {
return obj;
}
let clone = internals.clone;
let seen = _seen;
if (options.shallow) {
if (options.shallow !== true) {
return internals.cloneWithShallow(obj, options);
}
clone = (value) => value;
}
else if (seen) {
const lookup = seen.get(obj);
if (lookup) {
return lookup;
}
}
else {
seen = new Map();
}
// Built-in object types
const baseProto = Types.getInternalProto(obj);
if (baseProto === Types.buffer) {
return Buffer && Buffer.from(obj); // $lab:coverage:ignore$
}
if (baseProto === Types.date) {
return new Date(obj.getTime());
}
if (baseProto === Types.regex) {
return new RegExp(obj);
}
// Generic objects
const newObj = internals.base(obj, baseProto, options);
if (newObj === obj) {
return obj;
}
if (seen) {
seen.set(obj, newObj); // Set seen, since obj could recurse
}
if (baseProto === Types.set) {
for (const value of obj) {
newObj.add(clone(value, options, seen));
}
}
else if (baseProto === Types.map) {
for (const [key, value] of obj) {
newObj.set(key, clone(value, options, seen));
}
}
const keys = Utils.keys(obj, options);
for (const key of keys) {
if (key === '__proto__') {
continue;
}
if (baseProto === Types.array &&
key === 'length') {
newObj.length = obj.length;
continue;
}
const descriptor = Object.getOwnPropertyDescriptor(obj, key);
if (descriptor) {
if (descriptor.get ||
descriptor.set) {
Object.defineProperty(newObj, key, descriptor);
}
else if (descriptor.enumerable) {
newObj[key] = clone(obj[key], options, seen);
}
else {
Object.defineProperty(newObj, key, { enumerable: false, writable: true, configurable: true, value: clone(obj[key], options, seen) });
}
}
else {
Object.defineProperty(newObj, key, {
enumerable: true,
writable: true,
configurable: true,
value: clone(obj[key], options, seen)
});
}
}
return newObj;
};
internals.cloneWithShallow = function (source, options) {
const keys = options.shallow;
options = Object.assign({}, options);
options.shallow = false;
const seen = new Map();
for (const key of keys) {
const ref = Reach(source, key);
if (typeof ref === 'object' ||
typeof ref === 'function') {
seen.set(ref, ref);
}
}
return internals.clone(source, options, seen);
};
internals.base = function (obj, baseProto, options) {
if (options.prototype === false) { // Defaults to true
if (internals.needsProtoHack.has(baseProto)) {
return new baseProto.constructor();
}
return baseProto === Types.array ? [] : {};
}
const proto = Object.getPrototypeOf(obj);
if (proto &&
proto.isImmutable) {
return obj;
}
if (baseProto === Types.array) {
const newObj = [];
if (proto !== baseProto) {
Object.setPrototypeOf(newObj, proto);
}
return newObj;
}
if (internals.needsProtoHack.has(baseProto)) {
const newObj = new proto.constructor();
if (proto !== baseProto) {
Object.setPrototypeOf(newObj, proto);
}
return newObj;
}
return Object.create(proto);
};
@@ -1,307 +0,0 @@
'use strict';
const Assert = require('./assert');
const DeepEqual = require('./deepEqual');
const EscapeRegex = require('./escapeRegex');
const Utils = require('./utils');
const internals = {};
module.exports = function (ref, values, options = {}) { // options: { deep, once, only, part, symbols }
/*
string -> string(s)
array -> item(s)
object -> key(s)
object -> object (key:value)
*/
if (typeof values !== 'object') {
values = [values];
}
Assert(!Array.isArray(values) || values.length, 'Values array cannot be empty');
// String
if (typeof ref === 'string') {
return internals.string(ref, values, options);
}
// Array
if (Array.isArray(ref)) {
return internals.array(ref, values, options);
}
// Object
Assert(typeof ref === 'object', 'Reference must be string or an object');
return internals.object(ref, values, options);
};
internals.array = function (ref, values, options) {
if (!Array.isArray(values)) {
values = [values];
}
if (!ref.length) {
return false;
}
if (options.only &&
options.once &&
ref.length !== values.length) {
return false;
}
let compare;
// Map values
const map = new Map();
for (const value of values) {
if (!options.deep ||
!value ||
typeof value !== 'object') {
const existing = map.get(value);
if (existing) {
++existing.allowed;
}
else {
map.set(value, { allowed: 1, hits: 0 });
}
}
else {
compare = compare || internals.compare(options);
let found = false;
for (const [key, existing] of map.entries()) {
if (compare(key, value)) {
++existing.allowed;
found = true;
break;
}
}
if (!found) {
map.set(value, { allowed: 1, hits: 0 });
}
}
}
// Lookup values
let hits = 0;
for (const item of ref) {
let match;
if (!options.deep ||
!item ||
typeof item !== 'object') {
match = map.get(item);
}
else {
compare = compare || internals.compare(options);
for (const [key, existing] of map.entries()) {
if (compare(key, item)) {
match = existing;
break;
}
}
}
if (match) {
++match.hits;
++hits;
if (options.once &&
match.hits > match.allowed) {
return false;
}
}
}
// Validate results
if (options.only &&
hits !== ref.length) {
return false;
}
for (const match of map.values()) {
if (match.hits === match.allowed) {
continue;
}
if (match.hits < match.allowed &&
!options.part) {
return false;
}
}
return !!hits;
};
internals.object = function (ref, values, options) {
Assert(options.once === undefined, 'Cannot use option once with object');
const keys = Utils.keys(ref, options);
if (!keys.length) {
return false;
}
// Keys list
if (Array.isArray(values)) {
return internals.array(keys, values, options);
}
// Key value pairs
const symbols = Object.getOwnPropertySymbols(values).filter((sym) => values.propertyIsEnumerable(sym));
const targets = [...Object.keys(values), ...symbols];
const compare = internals.compare(options);
const set = new Set(targets);
for (const key of keys) {
if (!set.has(key)) {
if (options.only) {
return false;
}
continue;
}
if (!compare(values[key], ref[key])) {
return false;
}
set.delete(key);
}
if (set.size) {
return options.part ? set.size < targets.length : false;
}
return true;
};
internals.string = function (ref, values, options) {
// Empty string
if (ref === '') {
return values.length === 1 && values[0] === '' || // '' contains ''
!options.once && !values.some((v) => v !== ''); // '' contains multiple '' if !once
}
// Map values
const map = new Map();
const patterns = [];
for (const value of values) {
Assert(typeof value === 'string', 'Cannot compare string reference to non-string value');
if (value) {
const existing = map.get(value);
if (existing) {
++existing.allowed;
}
else {
map.set(value, { allowed: 1, hits: 0 });
patterns.push(EscapeRegex(value));
}
}
else if (options.once ||
options.only) {
return false;
}
}
if (!patterns.length) { // Non-empty string contains unlimited empty string
return true;
}
// Match patterns
const regex = new RegExp(`(${patterns.join('|')})`, 'g');
const leftovers = ref.replace(regex, ($0, $1) => {
++map.get($1).hits;
return ''; // Remove from string
});
// Validate results
if (options.only &&
leftovers) {
return false;
}
let any = false;
for (const match of map.values()) {
if (match.hits) {
any = true;
}
if (match.hits === match.allowed) {
continue;
}
if (match.hits < match.allowed &&
!options.part) {
return false;
}
// match.hits > match.allowed
if (options.once) {
return false;
}
}
return !!any;
};
internals.compare = function (options) {
if (!options.deep) {
return internals.shallow;
}
const hasOnly = options.only !== undefined;
const hasPart = options.part !== undefined;
const flags = {
prototype: hasOnly ? options.only : hasPart ? !options.part : false,
part: hasOnly ? !options.only : hasPart ? options.part : false
};
return (a, b) => DeepEqual(a, b, flags);
};
internals.shallow = function (a, b) {
return a === b;
};
@@ -1,317 +0,0 @@
'use strict';
const Types = require('./types');
const internals = {
mismatched: null
};
module.exports = function (obj, ref, options) {
options = Object.assign({ prototype: true }, options);
return !!internals.isDeepEqual(obj, ref, options, []);
};
internals.isDeepEqual = function (obj, ref, options, seen) {
if (obj === ref) { // Copied from Deep-eql, copyright(c) 2013 Jake Luer, jake@alogicalparadox.com, MIT Licensed, https://github.com/chaijs/deep-eql
return obj !== 0 || 1 / obj === 1 / ref;
}
const type = typeof obj;
if (type !== typeof ref) {
return false;
}
if (obj === null ||
ref === null) {
return false;
}
if (type === 'function') {
if (!options.deepFunction ||
obj.toString() !== ref.toString()) {
return false;
}
// Continue as object
}
else if (type !== 'object') {
return obj !== obj && ref !== ref; // NaN
}
const instanceType = internals.getSharedType(obj, ref, !!options.prototype);
switch (instanceType) {
case Types.buffer:
return Buffer && Buffer.prototype.equals.call(obj, ref); // $lab:coverage:ignore$
case Types.promise:
return obj === ref;
case Types.regex:
return obj.toString() === ref.toString();
case internals.mismatched:
return false;
}
for (let i = seen.length - 1; i >= 0; --i) {
if (seen[i].isSame(obj, ref)) {
return true; // If previous comparison failed, it would have stopped execution
}
}
seen.push(new internals.SeenEntry(obj, ref));
try {
return !!internals.isDeepEqualObj(instanceType, obj, ref, options, seen);
}
finally {
seen.pop();
}
};
internals.getSharedType = function (obj, ref, checkPrototype) {
if (checkPrototype) {
if (Object.getPrototypeOf(obj) !== Object.getPrototypeOf(ref)) {
return internals.mismatched;
}
return Types.getInternalProto(obj);
}
const type = Types.getInternalProto(obj);
if (type !== Types.getInternalProto(ref)) {
return internals.mismatched;
}
return type;
};
internals.valueOf = function (obj) {
const objValueOf = obj.valueOf;
if (objValueOf === undefined) {
return obj;
}
try {
return objValueOf.call(obj);
}
catch (err) {
return err;
}
};
internals.hasOwnEnumerableProperty = function (obj, key) {
return Object.prototype.propertyIsEnumerable.call(obj, key);
};
internals.isSetSimpleEqual = function (obj, ref) {
for (const entry of Set.prototype.values.call(obj)) {
if (!Set.prototype.has.call(ref, entry)) {
return false;
}
}
return true;
};
internals.isDeepEqualObj = function (instanceType, obj, ref, options, seen) {
const { isDeepEqual, valueOf, hasOwnEnumerableProperty } = internals;
const { keys, getOwnPropertySymbols } = Object;
if (instanceType === Types.array) {
if (options.part) {
// Check if any index match any other index
for (const objValue of obj) {
for (const refValue of ref) {
if (isDeepEqual(objValue, refValue, options, seen)) {
return true;
}
}
}
}
else {
if (obj.length !== ref.length) {
return false;
}
for (let i = 0; i < obj.length; ++i) {
if (!isDeepEqual(obj[i], ref[i], options, seen)) {
return false;
}
}
return true;
}
}
else if (instanceType === Types.set) {
if (obj.size !== ref.size) {
return false;
}
if (!internals.isSetSimpleEqual(obj, ref)) {
// Check for deep equality
const ref2 = new Set(Set.prototype.values.call(ref));
for (const objEntry of Set.prototype.values.call(obj)) {
if (ref2.delete(objEntry)) {
continue;
}
let found = false;
for (const refEntry of ref2) {
if (isDeepEqual(objEntry, refEntry, options, seen)) {
ref2.delete(refEntry);
found = true;
break;
}
}
if (!found) {
return false;
}
}
}
}
else if (instanceType === Types.map) {
if (obj.size !== ref.size) {
return false;
}
for (const [key, value] of Map.prototype.entries.call(obj)) {
if (value === undefined && !Map.prototype.has.call(ref, key)) {
return false;
}
if (!isDeepEqual(value, Map.prototype.get.call(ref, key), options, seen)) {
return false;
}
}
}
else if (instanceType === Types.error) {
// Always check name and message
if (obj.name !== ref.name ||
obj.message !== ref.message) {
return false;
}
}
// Check .valueOf()
const valueOfObj = valueOf(obj);
const valueOfRef = valueOf(ref);
if ((obj !== valueOfObj || ref !== valueOfRef) &&
!isDeepEqual(valueOfObj, valueOfRef, options, seen)) {
return false;
}
// Check properties
const objKeys = keys(obj);
if (!options.part &&
objKeys.length !== keys(ref).length &&
!options.skip) {
return false;
}
let skipped = 0;
for (const key of objKeys) {
if (options.skip &&
options.skip.includes(key)) {
if (ref[key] === undefined) {
++skipped;
}
continue;
}
if (!hasOwnEnumerableProperty(ref, key)) {
return false;
}
if (!isDeepEqual(obj[key], ref[key], options, seen)) {
return false;
}
}
if (!options.part &&
objKeys.length - skipped !== keys(ref).length) {
return false;
}
// Check symbols
if (options.symbols !== false) { // Defaults to true
const objSymbols = getOwnPropertySymbols(obj);
const refSymbols = new Set(getOwnPropertySymbols(ref));
for (const key of objSymbols) {
if (!options.skip ||
!options.skip.includes(key)) {
if (hasOwnEnumerableProperty(obj, key)) {
if (!hasOwnEnumerableProperty(ref, key)) {
return false;
}
if (!isDeepEqual(obj[key], ref[key], options, seen)) {
return false;
}
}
else if (hasOwnEnumerableProperty(ref, key)) {
return false;
}
}
refSymbols.delete(key);
}
for (const key of refSymbols) {
if (hasOwnEnumerableProperty(ref, key)) {
return false;
}
}
}
return true;
};
internals.SeenEntry = class {
constructor(obj, ref) {
this.obj = obj;
this.ref = ref;
}
isSame(obj, ref) {
return this.obj === obj && this.ref === ref;
}
};
@@ -1,26 +0,0 @@
'use strict';
const Stringify = require('./stringify');
const internals = {};
module.exports = class extends Error {
constructor(args) {
const msgs = args
.filter((arg) => arg !== '')
.map((arg) => {
return typeof arg === 'string' ? arg : arg instanceof Error ? arg.message : Stringify(arg);
});
super(msgs.join(' ') || 'Unknown error');
if (typeof Error.captureStackTrace === 'function') { // $lab:coverage:ignore$
Error.captureStackTrace(this, exports.assert);
}
}
};
@@ -1,16 +0,0 @@
'use strict';
const Assert = require('./assert');
const internals = {};
module.exports = function (attribute) {
// Allowed value characters: !#$%&'()*+,-./:;<=>?@[]^_`{|}~ and space, a-z, A-Z, 0-9, \, "
Assert(/^[ \w\!#\$%&'\(\)\*\+,\-\.\/\:;<\=>\?@\[\]\^`\{\|\}~\"\\]*$/.test(attribute), 'Bad attribute value (' + attribute + ')');
return attribute.replace(/\\/g, '\\\\').replace(/\"/g, '\\"'); // Escape quotes and slash
};
@@ -1,87 +0,0 @@
'use strict';
const internals = {};
module.exports = function (input) {
if (!input) {
return '';
}
let escaped = '';
for (let i = 0; i < input.length; ++i) {
const charCode = input.charCodeAt(i);
if (internals.isSafe(charCode)) {
escaped += input[i];
}
else {
escaped += internals.escapeHtmlChar(charCode);
}
}
return escaped;
};
internals.escapeHtmlChar = function (charCode) {
const namedEscape = internals.namedHtml.get(charCode);
if (namedEscape) {
return namedEscape;
}
if (charCode >= 256) {
return '&#' + charCode + ';';
}
const hexValue = charCode.toString(16).padStart(2, '0');
return `&#x${hexValue};`;
};
internals.isSafe = function (charCode) {
return internals.safeCharCodes.has(charCode);
};
internals.namedHtml = new Map([
[38, '&amp;'],
[60, '&lt;'],
[62, '&gt;'],
[34, '&quot;'],
[160, '&nbsp;'],
[162, '&cent;'],
[163, '&pound;'],
[164, '&curren;'],
[169, '&copy;'],
[174, '&reg;']
]);
internals.safeCharCodes = (function () {
const safe = new Set();
for (let i = 32; i < 123; ++i) {
if ((i >= 97) || // a-z
(i >= 65 && i <= 90) || // A-Z
(i >= 48 && i <= 57) || // 0-9
i === 32 || // space
i === 46 || // .
i === 44 || // ,
i === 45 || // -
i === 58 || // :
i === 95) { // _
safe.add(i);
}
}
return safe;
}());
@@ -1,28 +0,0 @@
'use strict';
const internals = {};
module.exports = function (input) {
if (!input) {
return '';
}
return input.replace(/[<>&\u2028\u2029]/g, internals.escape);
};
internals.escape = function (char) {
return internals.replacements.get(char);
};
internals.replacements = new Map([
['<', '\\u003c'],
['>', '\\u003e'],
['&', '\\u0026'],
['\u2028', '\\u2028'],
['\u2029', '\\u2029']
]);
@@ -1,11 +0,0 @@
'use strict';
const internals = {};
module.exports = function (string) {
// Escape ^$.*+-?=!:|\/()[]{},
return string.replace(/[\^\$\.\*\+\-\?\=\!\:\|\\\/\(\)\[\]\{\}\,]/g, '\\$&');
};
@@ -1,20 +0,0 @@
'use strict';
const internals = {};
module.exports = internals.flatten = function (array, target) {
const result = target || [];
for (const entry of array) {
if (Array.isArray(entry)) {
internals.flatten(entry, result);
}
else {
result.push(entry);
}
}
return result;
};
@@ -1,6 +0,0 @@
'use strict';
const internals = {};
module.exports = function () { };
@@ -1,471 +0,0 @@
/// <reference types="node" />
/**
* Performs a deep comparison of the two values including support for circular dependencies, prototype, and enumerable properties.
*
* @param obj - The value being compared.
* @param ref - The reference value used for comparison.
*
* @return true when the two values are equal, otherwise false.
*/
export function deepEqual(obj: any, ref: any, options?: deepEqual.Options): boolean;
export namespace deepEqual {
interface Options {
/**
* Compare functions with difference references by comparing their internal code and properties.
*
* @default false
*/
readonly deepFunction?: boolean;
/**
* Allow partial match.
*
* @default false
*/
readonly part?: boolean;
/**
* Compare the objects' prototypes.
*
* @default true
*/
readonly prototype?: boolean;
/**
* List of object keys to ignore different values of.
*
* @default null
*/
readonly skip?: (string | symbol)[];
/**
* Compare symbol properties.
*
* @default true
*/
readonly symbols?: boolean;
}
}
/**
* Clone any value, object, or array.
*
* @param obj - The value being cloned.
* @param options - Optional settings.
*
* @returns A deep clone of `obj`.
*/
export function clone<T>(obj: T, options?: clone.Options): T;
export namespace clone {
interface Options {
/**
* Clone the object's prototype.
*
* @default true
*/
readonly prototype?: boolean;
/**
* Include symbol properties.
*
* @default true
*/
readonly symbols?: boolean;
/**
* Shallow clone the specified keys.
*
* @default undefined
*/
readonly shallow?: string[] | string[][] | boolean;
}
}
/**
* Merge all the properties of source into target.
*
* @param target - The object being modified.
* @param source - The object used to copy properties from.
* @param options - Optional settings.
*
* @returns The `target` object.
*/
export function merge<T1 extends object, T2 extends object>(target: T1, source: T2, options?: merge.Options): T1 & T2;
export namespace merge {
interface Options {
/**
* When true, null value from `source` overrides existing value in `target`.
*
* @default true
*/
readonly nullOverride?: boolean;
/**
* When true, array value from `source` is merged with the existing value in `target`.
*
* @default false
*/
readonly mergeArrays?: boolean;
/**
* Compare symbol properties.
*
* @default true
*/
readonly symbols?: boolean;
}
}
/**
* Apply source to a copy of the defaults.
*
* @param defaults - An object with the default values to use of `options` does not contain the same keys.
* @param source - The source used to override the `defaults`.
* @param options - Optional settings.
*
* @returns A copy of `defaults` with `source` keys overriding any conflicts.
*/
export function applyToDefaults<T extends object>(defaults: Partial<T>, source: Partial<T> | boolean | null, options?: applyToDefaults.Options): Partial<T>;
export namespace applyToDefaults {
interface Options {
/**
* When true, null value from `source` overrides existing value in `target`.
*
* @default true
*/
readonly nullOverride?: boolean;
/**
* Shallow clone the specified keys.
*
* @default undefined
*/
readonly shallow?: string[] | string[][];
}
}
/**
* Find the common unique items in two arrays.
*
* @param array1 - The first array to compare.
* @param array2 - The second array to compare.
* @param options - Optional settings.
*
* @return - An array of the common items. If `justFirst` is true, returns the first common item.
*/
export function intersect<T1, T2>(array1: intersect.Array<T1>, array2: intersect.Array<T2>, options?: intersect.Options): Array<T1 | T2>;
export function intersect<T1, T2>(array1: intersect.Array<T1>, array2: intersect.Array<T2>, options?: intersect.Options): T1 | T2;
export namespace intersect {
type Array<T> = ArrayLike<T> | Set<T> | null;
interface Options {
/**
* When true, return the first overlapping value.
*
* @default false
*/
readonly first?: boolean;
}
}
/**
* Checks if the reference value contains the provided values.
*
* @param ref - The reference string, array, or object.
* @param values - A single or array of values to find within `ref`. If `ref` is an object, `values` can be a key name, an array of key names, or an object with key-value pairs to compare.
*
* @return true if the value contains the provided values, otherwise false.
*/
export function contain(ref: string, values: string | string[], options?: contain.Options): boolean;
export function contain(ref: any[], values: any, options?: contain.Options): boolean;
export function contain(ref: object, values: string | string[] | object, options?: Omit<contain.Options, 'once'>): boolean;
export namespace contain {
interface Options {
/**
* Perform a deep comparison.
*
* @default false
*/
readonly deep?: boolean;
/**
* Allow only one occurrence of each value.
*
* @default false
*/
readonly once?: boolean;
/**
* Allow only values explicitly listed.
*
* @default false
*/
readonly only?: boolean;
/**
* Allow partial match.
*
* @default false
*/
readonly part?: boolean;
/**
* Include symbol properties.
*
* @default true
*/
readonly symbols?: boolean;
}
}
/**
* Flatten an array with sub arrays
*
* @param array - an array of items or other arrays to flatten.
* @param target - if provided, an array to shallow copy the flattened `array` items to
*
* @return a flat array of the provided values (appended to `target` is provided).
*/
export function flatten<T>(array: ArrayLike<T | ReadonlyArray<T>>, target?: ArrayLike<T | ReadonlyArray<T>>): T[];
/**
* Convert an object key chain string to reference.
*
* @param obj - the object from which to look up the value.
* @param chain - the string path of the requested value. The chain string is split into key names using `options.separator`, or an array containing each individual key name. A chain including negative numbers will work like a negative index on an array.
*
* @return The value referenced by the chain if found, otherwise undefined. If chain is null, undefined, or false, the object itself will be returned.
*/
export function reach(obj: object | null, chain: string | (string | number)[] | false | null | undefined, options?: reach.Options): any;
export namespace reach {
interface Options {
/**
* String to split chain path on. Defaults to '.'.
*
* @default false
*/
readonly separator?: string;
/**
* Value to return if the path or value is not present. No default value.
*
* @default false
*/
readonly default?: any;
/**
* If true, will throw an error on missing member in the chain. Default to false.
*
* @default false
*/
readonly strict?: boolean;
/**
* If true, allows traversing functions for properties. false will throw an error if a function is part of the chain.
*
* @default true
*/
readonly functions?: boolean;
/**
* If true, allows traversing Set and Map objects for properties. false will return undefined regardless of the Set or Map passed.
*
* @default false
*/
readonly iterables?: boolean;
}
}
/**
* Replace string parameters (using format "{path.to.key}") with their corresponding object key values using `Hoek.reach()`.
*
* @param obj - the object from which to look up the value.
* @param template - the string containing {} enclosed key paths to be replaced.
*
* @return The template string with the {} enclosed keys replaced with looked-up values.
*/
export function reachTemplate(obj: object | null, template: string, options?: reach.Options): string;
/**
* Throw an error if condition is falsy.
*
* @param condition - If `condition` is not truthy, an exception is thrown.
* @param error - The error thrown if the condition fails.
*
* @return Does not return a value but throws if the `condition` is falsy.
*/
export function assert(condition: any, error: Error): void;
/**
* Throw an error if condition is falsy.
*
* @param condition - If `condition` is not truthy, an exception is thrown.
* @param args - Any number of values, concatenated together (space separated) to create the error message.
*
* @return Does not return a value but throws if the `condition` is falsy.
*/
export function assert(condition: any, ...args: any): void;
/**
* A benchmarking timer, using the internal node clock for maximum accuracy.
*/
export class Bench {
constructor();
/** The starting timestamp expressed in the number of milliseconds since the epoch. */
ts: number;
/** The time in milliseconds since the object was created. */
elapsed(): number;
/** Reset the `ts` value to now. */
reset(): void;
/** The current time in milliseconds since the epoch. */
static now(): number;
}
/**
* Escape string for Regex construction by prefixing all reserved characters with a backslash.
*
* @param string - The string to be escaped.
*
* @return The escaped string.
*/
export function escapeRegex(string: string): string;
/**
* Escape string for usage as an attribute value in HTTP headers.
*
* @param attribute - The string to be escaped.
*
* @return The escaped string. Will throw on invalid characters that are not supported to be escaped.
*/
export function escapeHeaderAttribute(attribute: string): string;
/**
* Escape string for usage in HTML.
*
* @param string - The string to be escaped.
*
* @return The escaped string.
*/
export function escapeHtml(string: string): string;
/**
* Escape string for usage in JSON.
*
* @param string - The string to be escaped.
*
* @return The escaped string.
*/
export function escapeJson(string: string): string;
/**
* Wraps a function to ensure it can only execute once.
*
* @param method - The function to be wrapped.
*
* @return The wrapped function.
*/
export function once<T extends Function>(method: T): T;
/**
* A reusable no-op function.
*/
export function ignore(...ignore: any): void;
/**
* Converts a JavaScript value to a JavaScript Object Notation (JSON) string with protection against thrown errors.
*
* @param value A JavaScript value, usually an object or array, to be converted.
* @param replacer The JSON.stringify() `replacer` argument.
* @param space Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.
*
* @return The JSON string. If the operation fails, an error string value is returned (no exception thrown).
*/
export function stringify(value: any, replacer?: any, space?: string | number): string;
/**
* Returns a Promise that resolves after the requested timeout.
*
* @param timeout - The number of milliseconds to wait before resolving the Promise.
* @param returnValue - The value that the Promise will resolve to.
*
* @return A Promise that resolves with `returnValue`.
*/
export function wait<T>(timeout?: number, returnValue?: T): Promise<T>;
/**
* Returns a Promise that never resolves.
*/
export function block(): Promise<void>;
/**
* Determines if an object is a promise.
*
* @param promise - the object tested.
*
* @returns true if the object is a promise, otherwise false.
*/
export function isPromise(promise: any): boolean;
export namespace ts {
/**
* Defines a type that can must be one of T or U but not both.
*/
type XOR<T, U> = (T | U) extends object ? (internals.Without<T, U> & U) | (internals.Without<U, T> & T) : T | U;
}
declare namespace internals {
type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
}
@@ -1,45 +0,0 @@
'use strict';
exports.applyToDefaults = require('./applyToDefaults');
exports.assert = require('./assert');
exports.Bench = require('./bench');
exports.block = require('./block');
exports.clone = require('./clone');
exports.contain = require('./contain');
exports.deepEqual = require('./deepEqual');
exports.Error = require('./error');
exports.escapeHeaderAttribute = require('./escapeHeaderAttribute');
exports.escapeHtml = require('./escapeHtml');
exports.escapeJson = require('./escapeJson');
exports.escapeRegex = require('./escapeRegex');
exports.flatten = require('./flatten');
exports.ignore = require('./ignore');
exports.intersect = require('./intersect');
exports.isPromise = require('./isPromise');
exports.merge = require('./merge');
exports.once = require('./once');
exports.reach = require('./reach');
exports.reachTemplate = require('./reachTemplate');
exports.stringify = require('./stringify');
exports.wait = require('./wait');
@@ -1,41 +0,0 @@
'use strict';
const internals = {};
module.exports = function (array1, array2, options = {}) {
if (!array1 ||
!array2) {
return (options.first ? null : []);
}
const common = [];
const hash = (Array.isArray(array1) ? new Set(array1) : array1);
const found = new Set();
for (const value of array2) {
if (internals.has(hash, value) &&
!found.has(value)) {
if (options.first) {
return value;
}
common.push(value);
found.add(value);
}
}
return (options.first ? null : common);
};
internals.has = function (ref, key) {
if (typeof ref.has === 'function') {
return ref.has(key);
}
return ref[key] !== undefined;
};
@@ -1,9 +0,0 @@
'use strict';
const internals = {};
module.exports = function (promise) {
return !!promise && typeof promise.then === 'function';
};
@@ -1,78 +0,0 @@
'use strict';
const Assert = require('./assert');
const Clone = require('./clone');
const Utils = require('./utils');
const internals = {};
module.exports = internals.merge = function (target, source, options) {
Assert(target && typeof target === 'object', 'Invalid target value: must be an object');
Assert(source === null || source === undefined || typeof source === 'object', 'Invalid source value: must be null, undefined, or an object');
if (!source) {
return target;
}
options = Object.assign({ nullOverride: true, mergeArrays: true }, options);
if (Array.isArray(source)) {
Assert(Array.isArray(target), 'Cannot merge array onto an object');
if (!options.mergeArrays) {
target.length = 0; // Must not change target assignment
}
for (let i = 0; i < source.length; ++i) {
target.push(Clone(source[i], { symbols: options.symbols }));
}
return target;
}
const keys = Utils.keys(source, options);
for (let i = 0; i < keys.length; ++i) {
const key = keys[i];
if (key === '__proto__' ||
!Object.prototype.propertyIsEnumerable.call(source, key)) {
continue;
}
const value = source[key];
if (value &&
typeof value === 'object') {
if (target[key] === value) {
continue; // Can occur for shallow merges
}
if (!target[key] ||
typeof target[key] !== 'object' ||
(Array.isArray(target[key]) !== Array.isArray(value)) ||
value instanceof Date ||
(Buffer && Buffer.isBuffer(value)) || // $lab:coverage:ignore$
value instanceof RegExp) {
target[key] = Clone(value, { symbols: options.symbols });
}
else {
internals.merge(target[key], value, options);
}
}
else {
if (value !== null &&
value !== undefined) { // Explicit to preserve empty strings
target[key] = value;
}
else if (options.nullOverride) {
target[key] = value;
}
}
}
return target;
};
@@ -1,25 +0,0 @@
'use strict';
const internals = {
wrapped: Symbol('wrapped')
};
module.exports = function (method) {
if (method[internals.wrapped]) {
return method;
}
let once = false;
const wrappedFn = function (...args) {
if (!once) {
once = true;
method(...args);
}
};
wrappedFn[internals.wrapped] = true;
return wrappedFn;
};
@@ -1,76 +0,0 @@
'use strict';
const Assert = require('./assert');
const internals = {};
module.exports = function (obj, chain, options) {
if (chain === false ||
chain === null ||
chain === undefined) {
return obj;
}
options = options || {};
if (typeof options === 'string') {
options = { separator: options };
}
const isChainArray = Array.isArray(chain);
Assert(!isChainArray || !options.separator, 'Separator option is not valid for array-based chain');
const path = isChainArray ? chain : chain.split(options.separator || '.');
let ref = obj;
for (let i = 0; i < path.length; ++i) {
let key = path[i];
const type = options.iterables && internals.iterables(ref);
if (Array.isArray(ref) ||
type === 'set') {
const number = Number(key);
if (Number.isInteger(number)) {
key = number < 0 ? ref.length + number : number;
}
}
if (!ref ||
typeof ref === 'function' && options.functions === false || // Defaults to true
!type && ref[key] === undefined) {
Assert(!options.strict || i + 1 === path.length, 'Missing segment', key, 'in reach path ', chain);
Assert(typeof ref === 'object' || options.functions === true || typeof ref !== 'function', 'Invalid segment', key, 'in reach path ', chain);
ref = options.default;
break;
}
if (!type) {
ref = ref[key];
}
else if (type === 'set') {
ref = [...ref][key];
}
else { // type === 'map'
ref = ref.get(key);
}
}
return ref;
};
internals.iterables = function (ref) {
if (ref instanceof Set) {
return 'set';
}
if (ref instanceof Map) {
return 'map';
}
};
@@ -1,16 +0,0 @@
'use strict';
const Reach = require('./reach');
const internals = {};
module.exports = function (obj, template, options) {
return template.replace(/{([^{}]+)}/g, ($0, chain) => {
const value = Reach(obj, chain, options);
return (value === undefined || value === null ? '' : value);
});
};
@@ -1,14 +0,0 @@
'use strict';
const internals = {};
module.exports = function (...args) {
try {
return JSON.stringify(...args);
}
catch (err) {
return '[Cannot display object: ' + err.message + ']';
}
};
@@ -1,55 +0,0 @@
'use strict';
const internals = {};
exports = module.exports = {
array: Array.prototype,
buffer: Buffer && Buffer.prototype, // $lab:coverage:ignore$
date: Date.prototype,
error: Error.prototype,
generic: Object.prototype,
map: Map.prototype,
promise: Promise.prototype,
regex: RegExp.prototype,
set: Set.prototype,
weakMap: WeakMap.prototype,
weakSet: WeakSet.prototype
};
internals.typeMap = new Map([
['[object Error]', exports.error],
['[object Map]', exports.map],
['[object Promise]', exports.promise],
['[object Set]', exports.set],
['[object WeakMap]', exports.weakMap],
['[object WeakSet]', exports.weakSet]
]);
exports.getInternalProto = function (obj) {
if (Array.isArray(obj)) {
return exports.array;
}
if (Buffer && obj instanceof Buffer) { // $lab:coverage:ignore$
return exports.buffer;
}
if (obj instanceof Date) {
return exports.date;
}
if (obj instanceof RegExp) {
return exports.regex;
}
if (obj instanceof Error) {
return exports.error;
}
const objName = Object.prototype.toString.call(obj);
return internals.typeMap.get(objName) || exports.generic;
};
@@ -1,9 +0,0 @@
'use strict';
const internals = {};
exports.keys = function (obj, options = {}) {
return options.symbols !== false ? Reflect.ownKeys(obj) : Object.getOwnPropertyNames(obj); // Defaults to true
};
@@ -1,37 +0,0 @@
'use strict';
const internals = {
maxTimer: 2 ** 31 - 1 // ~25 days
};
module.exports = function (timeout, returnValue, options) {
if (typeof timeout === 'bigint') {
timeout = Number(timeout);
}
if (timeout >= Number.MAX_SAFE_INTEGER) { // Thousands of years
timeout = Infinity;
}
if (typeof timeout !== 'number' && timeout !== undefined) {
throw new TypeError('Timeout must be a number or bigint');
}
return new Promise((resolve) => {
const _setTimeout = options ? options.setTimeout : setTimeout;
const activate = () => {
const time = Math.min(timeout, internals.maxTimer);
timeout -= time;
_setTimeout(() => (timeout > 0 ? activate() : resolve(returnValue)), time);
};
if (timeout !== Infinity) {
activate();
}
});
};
@@ -1,31 +0,0 @@
{
"name": "@hapi/hoek",
"description": "General purpose node utilities",
"version": "9.3.0",
"repository": "git://github.com/hapijs/hoek",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"keywords": [
"utilities"
],
"files": [
"lib"
],
"eslintConfig": {
"extends": [
"plugin:@hapi/module"
]
},
"dependencies": {},
"devDependencies": {
"@hapi/code": "8.x.x",
"@hapi/eslint-plugin": "*",
"@hapi/lab": "^24.0.0",
"typescript": "~4.0.2"
},
"scripts": {
"test": "lab -a @hapi/code -t 100 -L -Y",
"test-cov-html": "lab -a @hapi/code -t 100 -L -r html -o coverage.html"
},
"license": "BSD-3-Clause"
}
@@ -1,82 +0,0 @@
# Licensing
## color
Copyright (c) 2012 Heather Arthur
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
## color-convert
Copyright (c) 2011-2016 Heather Arthur <fayearthur@gmail.com>.
Copyright (c) 2016-2021 Josh Junon <josh@junon.me>.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
## color-string
Copyright (c) 2011 Heather Arthur <fayearthur@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
## color-name
The MIT License (MIT)
Copyright (c) 2015 Dmitry Ivanov
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -1,15 +0,0 @@
# `@img/colour`
The latest version of the
[color](https://www.npmjs.com/package/color)
package is now ESM-only,
however some JavaScript runtimes do not yet support this,
which includes versions of Node.js prior to 20.19.0.
This package converts the `color` package and its dependencies,
all of which are MIT-licensed, to CommonJS.
- [color](https://www.npmjs.com/package/color)
- [color-convert](https://www.npmjs.com/package/color-convert)
- [color-string](https://www.npmjs.com/package/color-string)
- [color-name](https://www.npmjs.com/package/color-name)
File diff suppressed because it is too large Load Diff
@@ -1 +0,0 @@
module.exports = require("./color.cjs").default;
@@ -1,929 +0,0 @@
// Generated by dts-bundle-generator v9.5.1
type Channels = number;
type RGB = [
r: number,
g: number,
b: number
];
type HSL = [
h: number,
s: number,
l: number
];
type HSV = [
h: number,
s: number,
v: number
];
type CMYK = [
c: number,
m: number,
y: number,
k: number
];
type LAB = [
l: number,
a: number,
b: number
];
type LCH = [
l: number,
c: number,
h: number
];
type HCG = [
h: number,
c: number,
g: number
];
type HWB = [
h: number,
w: number,
b: number
];
type XYZ = [
x: number,
y: number,
z: number
];
type Apple = [
r16: number,
g16: number,
b16: number
];
type Gray = [
gray: number
];
type ANSI16 = number;
type ANSI256 = number;
type Keyword = string;
type HEX = string;
declare namespace route {
type rgb = {
hsl(from: RGB): HSL;
hsl(...from: RGB): HSL;
hsl(from: RGB): HSL;
hsl(...from: RGB): HSL;
hsv(from: RGB): HSV;
hsv(...from: RGB): HSV;
hwb(from: RGB): HWB;
hwb(...from: RGB): HWB;
cmyk(from: RGB): CMYK;
cmyk(...from: RGB): CMYK;
xyz(from: RGB): XYZ;
xyz(...from: RGB): XYZ;
lab(from: RGB): LAB;
lab(...from: RGB): LAB;
lch(from: RGB): LCH;
lch(...from: RGB): LCH;
hex(from: RGB): HEX;
hex(...from: RGB): HEX;
keyword(from: RGB): Keyword;
keyword(...from: RGB): Keyword;
ansi16(from: RGB): ANSI16;
ansi16(...from: RGB): ANSI16;
ansi256(from: RGB): ANSI256;
ansi256(...from: RGB): ANSI256;
hcg(from: RGB): HCG;
hcg(...from: RGB): HCG;
apple(from: RGB): Apple;
apple(...from: RGB): Apple;
gray(from: RGB): Gray;
gray(...from: RGB): Gray;
};
type hsl = {
rgb(from: HSL): RGB;
rgb(...from: HSL): RGB;
hsv(from: HSL): HSV;
hsv(...from: HSL): HSV;
hwb(from: HSL): HWB;
hwb(...from: HSL): HWB;
cmyk(from: HSL): CMYK;
cmyk(...from: HSL): CMYK;
xyz(from: HSL): XYZ;
xyz(...from: HSL): XYZ;
lab(from: HSL): LAB;
lab(...from: HSL): LAB;
lch(from: HSL): LCH;
lch(...from: HSL): LCH;
hex(from: HSL): HEX;
hex(...from: HSL): HEX;
keyword(from: HSL): Keyword;
keyword(...from: HSL): Keyword;
ansi16(from: HSL): ANSI16;
ansi16(...from: HSL): ANSI16;
ansi256(from: HSL): ANSI256;
ansi256(...from: HSL): ANSI256;
hcg(from: HSL): HCG;
hcg(...from: HSL): HCG;
apple(from: HSL): Apple;
apple(...from: HSL): Apple;
gray(from: HSL): Gray;
gray(...from: HSL): Gray;
};
type hsv = {
rgb(from: HSV): RGB;
rgb(...from: HSV): RGB;
hsl(from: HSV): HSL;
hsl(...from: HSV): HSL;
hwb(from: HSV): HWB;
hwb(...from: HSV): HWB;
cmyk(from: HSV): CMYK;
cmyk(...from: HSV): CMYK;
xyz(from: HSV): XYZ;
xyz(...from: HSV): XYZ;
lab(from: HSV): LAB;
lab(...from: HSV): LAB;
lch(from: HSV): LCH;
lch(...from: HSV): LCH;
hex(from: HSV): HEX;
hex(...from: HSV): HEX;
keyword(from: HSV): Keyword;
keyword(...from: HSV): Keyword;
ansi16(from: HSV): ANSI16;
ansi16(...from: HSV): ANSI16;
ansi256(from: HSV): ANSI256;
ansi256(...from: HSV): ANSI256;
hcg(from: HSV): HCG;
hcg(...from: HSV): HCG;
apple(from: HSV): Apple;
apple(...from: HSV): Apple;
gray(from: HSV): Gray;
gray(...from: HSV): Gray;
};
type hwb = {
rgb(from: HWB): RGB;
rgb(...from: HWB): RGB;
hsl(from: HWB): HSL;
hsl(...from: HWB): HSL;
hsv(from: HWB): HSV;
hsv(...from: HWB): HSV;
cmyk(from: HWB): CMYK;
cmyk(...from: HWB): CMYK;
xyz(from: HWB): XYZ;
xyz(...from: HWB): XYZ;
lab(from: HWB): LAB;
lab(...from: HWB): LAB;
lch(from: HWB): LCH;
lch(...from: HWB): LCH;
hex(from: HWB): HEX;
hex(...from: HWB): HEX;
keyword(from: HWB): Keyword;
keyword(...from: HWB): Keyword;
ansi16(from: HWB): ANSI16;
ansi16(...from: HWB): ANSI16;
ansi256(from: HWB): ANSI256;
ansi256(...from: HWB): ANSI256;
hcg(from: HWB): HCG;
hcg(...from: HWB): HCG;
apple(from: HWB): Apple;
apple(...from: HWB): Apple;
gray(from: HWB): Gray;
gray(...from: HWB): Gray;
};
type cmyk = {
rgb(from: CMYK): RGB;
rgb(...from: CMYK): RGB;
hsl(from: CMYK): HSL;
hsl(...from: CMYK): HSL;
hsv(from: CMYK): HSV;
hsv(...from: CMYK): HSV;
hwb(from: CMYK): HWB;
hwb(...from: CMYK): HWB;
xyz(from: CMYK): XYZ;
xyz(...from: CMYK): XYZ;
lab(from: CMYK): LAB;
lab(...from: CMYK): LAB;
lch(from: CMYK): LCH;
lch(...from: CMYK): LCH;
hex(from: CMYK): HEX;
hex(...from: CMYK): HEX;
keyword(from: CMYK): Keyword;
keyword(...from: CMYK): Keyword;
ansi16(from: CMYK): ANSI16;
ansi16(...from: CMYK): ANSI16;
ansi256(from: CMYK): ANSI256;
ansi256(...from: CMYK): ANSI256;
hcg(from: CMYK): HCG;
hcg(...from: CMYK): HCG;
apple(from: CMYK): Apple;
apple(...from: CMYK): Apple;
gray(from: CMYK): Gray;
gray(...from: CMYK): Gray;
};
type xyz = {
rgb(from: XYZ): RGB;
rgb(...from: XYZ): RGB;
hsl(from: XYZ): HSL;
hsl(...from: XYZ): HSL;
hsv(from: XYZ): HSV;
hsv(...from: XYZ): HSV;
hwb(from: XYZ): HWB;
hwb(...from: XYZ): HWB;
cmyk(from: XYZ): CMYK;
cmyk(...from: XYZ): CMYK;
lab(from: XYZ): LAB;
lab(...from: XYZ): LAB;
lch(from: XYZ): LCH;
lch(...from: XYZ): LCH;
hex(from: XYZ): HEX;
hex(...from: XYZ): HEX;
keyword(from: XYZ): Keyword;
keyword(...from: XYZ): Keyword;
ansi16(from: XYZ): ANSI16;
ansi16(...from: XYZ): ANSI16;
ansi256(from: XYZ): ANSI256;
ansi256(...from: XYZ): ANSI256;
hcg(from: XYZ): HCG;
hcg(...from: XYZ): HCG;
apple(from: XYZ): Apple;
apple(...from: XYZ): Apple;
gray(from: XYZ): Gray;
gray(...from: XYZ): Gray;
};
type lab = {
rgb(from: LAB): RGB;
rgb(...from: LAB): RGB;
hsl(from: LAB): HSL;
hsl(...from: LAB): HSL;
hsv(from: LAB): HSV;
hsv(...from: LAB): HSV;
hwb(from: LAB): HWB;
hwb(...from: LAB): HWB;
cmyk(from: LAB): CMYK;
cmyk(...from: LAB): CMYK;
xyz(from: LAB): XYZ;
xyz(...from: LAB): XYZ;
lch(from: LAB): LCH;
lch(...from: LAB): LCH;
hex(from: LAB): HEX;
hex(...from: LAB): HEX;
keyword(from: LAB): Keyword;
keyword(...from: LAB): Keyword;
ansi16(from: LAB): ANSI16;
ansi16(...from: LAB): ANSI16;
ansi256(from: LAB): ANSI256;
ansi256(...from: LAB): ANSI256;
hcg(from: LAB): HCG;
hcg(...from: LAB): HCG;
apple(from: LAB): Apple;
apple(...from: LAB): Apple;
gray(from: LAB): Gray;
gray(...from: LAB): Gray;
};
type lch = {
rgb(from: LCH): RGB;
rgb(...from: LCH): RGB;
hsl(from: LCH): HSL;
hsl(...from: LCH): HSL;
hsv(from: LCH): HSV;
hsv(...from: LCH): HSV;
hwb(from: LCH): HWB;
hwb(...from: LCH): HWB;
cmyk(from: LCH): CMYK;
cmyk(...from: LCH): CMYK;
xyz(from: LCH): XYZ;
xyz(...from: LCH): XYZ;
lab(from: LCH): LAB;
lab(...from: LCH): LAB;
hex(from: LCH): HEX;
hex(...from: LCH): HEX;
keyword(from: LCH): Keyword;
keyword(...from: LCH): Keyword;
ansi16(from: LCH): ANSI16;
ansi16(...from: LCH): ANSI16;
ansi256(from: LCH): ANSI256;
ansi256(...from: LCH): ANSI256;
hcg(from: LCH): HCG;
hcg(...from: LCH): HCG;
apple(from: LCH): Apple;
apple(...from: LCH): Apple;
gray(from: LCH): Gray;
gray(...from: LCH): Gray;
};
type hex = {
rgb(from: HEX): RGB;
hsl(from: HEX): HSL;
hsv(from: HEX): HSV;
hwb(from: HEX): HWB;
cmyk(from: HEX): CMYK;
xyz(from: HEX): XYZ;
lab(from: HEX): LAB;
lch(from: HEX): LCH;
keyword(from: HEX): Keyword;
ansi16(from: HEX): ANSI16;
ansi256(from: HEX): ANSI256;
hcg(from: HEX): HCG;
apple(from: HEX): Apple;
gray(from: HEX): Gray;
};
type keyword = {
rgb(from: Keyword): RGB;
hsl(from: Keyword): HSL;
hsv(from: Keyword): HSV;
hwb(from: Keyword): HWB;
cmyk(from: Keyword): CMYK;
xyz(from: Keyword): XYZ;
lab(from: Keyword): LAB;
lch(from: Keyword): LCH;
hex(from: Keyword): HEX;
ansi16(from: Keyword): ANSI16;
ansi256(from: Keyword): ANSI256;
hcg(from: Keyword): HCG;
apple(from: Keyword): Apple;
gray(from: Keyword): Gray;
};
type ansi16 = {
rgb(from: ANSI16): RGB;
hsl(from: ANSI16): HSL;
hsv(from: ANSI16): HSV;
hwb(from: ANSI16): HWB;
cmyk(from: ANSI16): CMYK;
xyz(from: ANSI16): XYZ;
lab(from: ANSI16): LAB;
lch(from: ANSI16): LCH;
hex(from: ANSI16): HEX;
keyword(from: ANSI16): Keyword;
ansi256(from: ANSI16): ANSI256;
hcg(from: ANSI16): HCG;
apple(from: ANSI16): Apple;
gray(from: ANSI16): Gray;
};
type ansi256 = {
rgb(from: ANSI256): RGB;
hsl(from: ANSI256): HSL;
hsv(from: ANSI256): HSV;
hwb(from: ANSI256): HWB;
cmyk(from: ANSI256): CMYK;
xyz(from: ANSI256): XYZ;
lab(from: ANSI256): LAB;
lch(from: ANSI256): LCH;
hex(from: ANSI256): HEX;
keyword(from: ANSI256): Keyword;
ansi16(from: ANSI256): ANSI16;
hcg(from: ANSI256): HCG;
apple(from: ANSI256): Apple;
gray(from: ANSI256): Gray;
};
type hcg = {
rgb(from: HCG): RGB;
rgb(...from: HCG): RGB;
hsl(from: HCG): HSL;
hsl(...from: HCG): HSL;
hsv(from: HCG): HSV;
hsv(...from: HCG): HSV;
hwb(from: HCG): HWB;
hwb(...from: HCG): HWB;
cmyk(from: HCG): CMYK;
cmyk(...from: HCG): CMYK;
xyz(from: HCG): XYZ;
xyz(...from: HCG): XYZ;
lab(from: HCG): LAB;
lab(...from: HCG): LAB;
lch(from: HCG): LCH;
lch(...from: HCG): LCH;
hex(from: HCG): HEX;
hex(...from: HCG): HEX;
keyword(from: HCG): Keyword;
keyword(...from: HCG): Keyword;
ansi16(from: HCG): ANSI16;
ansi16(...from: HCG): ANSI16;
ansi256(from: HCG): ANSI256;
ansi256(...from: HCG): ANSI256;
apple(from: HCG): Apple;
apple(...from: HCG): Apple;
gray(from: HCG): Gray;
gray(...from: HCG): Gray;
};
type apple = {
rgb(from: Apple): RGB;
rgb(...from: Apple): RGB;
hsl(from: Apple): HSL;
hsl(...from: Apple): HSL;
hsv(from: Apple): HSV;
hsv(...from: Apple): HSV;
hwb(from: Apple): HWB;
hwb(...from: Apple): HWB;
cmyk(from: Apple): CMYK;
cmyk(...from: Apple): CMYK;
xyz(from: Apple): XYZ;
xyz(...from: Apple): XYZ;
lab(from: Apple): LAB;
lab(...from: Apple): LAB;
lch(from: Apple): LCH;
lch(...from: Apple): LCH;
hex(from: Apple): HEX;
hex(...from: Apple): HEX;
keyword(from: Apple): Keyword;
keyword(...from: Apple): Keyword;
ansi16(from: Apple): ANSI16;
ansi16(...from: Apple): ANSI16;
ansi256(from: Apple): ANSI256;
ansi256(...from: Apple): ANSI256;
hcg(from: Apple): HCG;
hcg(...from: Apple): HCG;
gray(from: Apple): Gray;
gray(...from: Apple): Gray;
};
type gray = {
rgb(from: Gray): RGB;
rgb(...from: Gray): RGB;
hsl(from: Gray): HSL;
hsl(...from: Gray): HSL;
hsv(from: Gray): HSV;
hsv(...from: Gray): HSV;
hwb(from: Gray): HWB;
hwb(...from: Gray): HWB;
cmyk(from: Gray): CMYK;
cmyk(...from: Gray): CMYK;
xyz(from: Gray): XYZ;
xyz(...from: Gray): XYZ;
lab(from: Gray): LAB;
lab(...from: Gray): LAB;
lch(from: Gray): LCH;
lch(...from: Gray): LCH;
hex(from: Gray): HEX;
hex(...from: Gray): HEX;
keyword(from: Gray): Keyword;
keyword(...from: Gray): Keyword;
ansi16(from: Gray): ANSI16;
ansi16(...from: Gray): ANSI16;
ansi256(from: Gray): ANSI256;
ansi256(...from: Gray): ANSI256;
hcg(from: Gray): HCG;
hcg(...from: Gray): HCG;
apple(from: Gray): Apple;
apple(...from: Gray): Apple;
};
}
declare function route(fromModel: "rgb"): route.rgb;
declare function route(fromModel: "hsl"): route.hsl;
declare function route(fromModel: "hsv"): route.hsv;
declare function route(fromModel: "hwb"): route.hwb;
declare function route(fromModel: "cmyk"): route.cmyk;
declare function route(fromModel: "xyz"): route.xyz;
declare function route(fromModel: "lab"): route.lab;
declare function route(fromModel: "lch"): route.lch;
declare function route(fromModel: "hex"): route.hex;
declare function route(fromModel: "keyword"): route.keyword;
declare function route(fromModel: "ansi16"): route.ansi16;
declare function route(fromModel: "ansi256"): route.ansi256;
declare function route(fromModel: "hcg"): route.hcg;
declare function route(fromModel: "apple"): route.apple;
declare function route(fromModel: "gray"): route.gray;
type Convert = {
rgb: {
channels: Channels;
labels: "rgb";
hsl: {
(...rgb: RGB): HSL;
raw: (...rgb: RGB) => HSL;
};
hsv: {
(...rgb: RGB): HSV;
raw: (...rgb: RGB) => HSV;
};
hwb: {
(...rgb: RGB): HWB;
raw: (...rgb: RGB) => HWB;
};
hcg: {
(...rgb: RGB): HCG;
raw: (...rgb: RGB) => HCG;
};
cmyk: {
(...rgb: RGB): CMYK;
raw: (...rgb: RGB) => CMYK;
};
keyword: {
(...rgb: RGB): Keyword;
raw: (...rgb: RGB) => Keyword;
};
ansi16: {
(...rgb: RGB): ANSI16;
raw: (...rgb: RGB) => ANSI16;
};
ansi256: {
(...rgb: RGB): ANSI256;
raw: (...rgb: RGB) => ANSI256;
};
apple: {
(...rgb: RGB): Apple;
raw: (...rgb: RGB) => Apple;
};
hex: {
(...rgb: RGB): HEX;
raw: (...rgb: RGB) => HEX;
};
gray: {
(...rgb: RGB): Gray;
raw: (...rgb: RGB) => Gray;
};
} & route.rgb & {
[F in keyof route.rgb]: {
raw: route.rgb[F];
};
};
keyword: {
channels: Channels;
rgb: {
(keyword: Keyword): RGB;
raw: (keyword: Keyword) => RGB;
};
} & route.keyword & {
[F in keyof route.keyword]: {
raw: route.keyword[F];
};
};
hsl: {
channels: Channels;
labels: "hsl";
rgb: {
(...hsl: HSL): RGB;
raw: (...hsl: HSL) => RGB;
};
hsv: {
(...hsl: HSL): HSV;
raw: (...hsl: HSL) => HSV;
};
hcg: {
(...hsl: HSL): HCG;
raw: (...hsl: HSL) => HCG;
};
} & route.hsl & {
[F in keyof route.hsl]: {
raw: route.hsl[F];
};
};
hsv: {
channels: Channels;
labels: "hsv";
hcg: {
(...hsv: HSV): HCG;
raw: (...hsv: HSV) => HCG;
};
rgb: {
(...hsv: HSV): RGB;
raw: (...hsv: HSV) => RGB;
};
hsv: {
(...hsv: HSV): HSV;
raw: (...hsv: HSV) => HSV;
};
hsl: {
(...hsv: HSV): HSL;
raw: (...hsv: HSV) => HSL;
};
hwb: {
(...hsv: HSV): HWB;
raw: (...hsv: HSV) => HWB;
};
ansi16: {
(...hsv: HSV): ANSI16;
raw: (...hsv: HSV) => ANSI16;
};
} & route.hsv & {
[F in keyof route.hsv]: {
raw: route.hsv[F];
};
};
hwb: {
channels: Channels;
labels: "hwb";
hcg: {
(...hwb: HWB): HCG;
raw: (...hwb: HWB) => HCG;
};
rgb: {
(...hwb: HWB): RGB;
raw: (...hwb: HWB) => RGB;
};
} & route.hwb & {
[F in keyof route.hwb]: {
raw: route.hwb[F];
};
};
cmyk: {
channels: Channels;
labels: "cmyk";
rgb: {
(...cmyk: CMYK): RGB;
raw: (...cmyk: CMYK) => RGB;
};
} & route.cmyk & {
[F in keyof route.cmyk]: {
raw: route.cmyk[F];
};
};
xyz: {
channels: Channels;
labels: "xyz";
rgb: {
(...xyz: XYZ): RGB;
raw: (...xyz: XYZ) => RGB;
};
lab: {
(...xyz: XYZ): LAB;
raw: (...xyz: XYZ) => LAB;
};
} & route.xyz & {
[F in keyof route.xyz]: {
raw: route.xyz[F];
};
};
lab: {
channels: Channels;
labels: "lab";
xyz: {
(...lab: LAB): XYZ;
raw: (...lab: LAB) => XYZ;
};
lch: {
(...lab: LAB): LCH;
raw: (...lab: LAB) => LCH;
};
} & route.lab & {
[F in keyof route.lab]: {
raw: route.lab[F];
};
};
lch: {
channels: Channels;
labels: "lch";
lab: {
(...lch: LCH): LAB;
raw: (...lch: LCH) => LAB;
};
} & route.lch & {
[F in keyof route.lch]: {
raw: route.lch[F];
};
};
hex: {
channels: Channels;
labels: [
"hex"
];
rgb: {
(hex: HEX): RGB;
raw: (hex: HEX) => RGB;
};
} & route.hex & {
[F in keyof route.hex]: {
raw: route.hex[F];
};
};
ansi16: {
channels: Channels;
labels: [
"ansi16"
];
rgb: {
(ansi16: ANSI16): RGB;
raw: (ansi16: ANSI16) => RGB;
};
} & route.ansi16 & {
[F in keyof route.ansi16]: {
raw: route.ansi16[F];
};
};
ansi256: {
channels: Channels;
labels: [
"ansi256"
];
rgb: {
(ansi256: ANSI256): RGB;
raw: (ansi256: ANSI256) => RGB;
};
} & route.ansi256 & {
[F in keyof route.ansi256]: {
raw: route.ansi256[F];
};
};
hcg: {
channels: Channels;
labels: [
"h",
"c",
"g"
];
rgb: {
(...hcg: HCG): RGB;
raw: (...hcg: HCG) => RGB;
};
hsv: {
(...hcg: HCG): HSV;
raw: (...hcg: HCG) => HSV;
};
hwb: {
(...hcg: HCG): HWB;
raw: (...hcg: HCG) => HWB;
};
} & route.hcg & {
[F in keyof route.hcg]: {
raw: route.hcg[F];
};
};
apple: {
channels: Channels;
labels: [
"r16",
"g16",
"b16"
];
rgb: {
(...apple: Apple): RGB;
raw: (...apple: Apple) => RGB;
};
} & route.apple & {
[F in keyof route.apple]: {
raw: route.apple[F];
};
};
gray: {
channels: Channels;
labels: [
"gray"
];
rgb: {
(...gray: Gray): RGB;
raw: (...gray: Gray) => RGB;
};
hsl: {
(...gray: Gray): HSL;
raw: (...gray: Gray) => HSL;
};
hsv: {
(...gray: Gray): HSV;
raw: (...gray: Gray) => HSV;
};
hwb: {
(...gray: Gray): HWB;
raw: (...gray: Gray) => HWB;
};
cmyk: {
(...gray: Gray): CMYK;
raw: (...gray: Gray) => CMYK;
};
lab: {
(...gray: Gray): LAB;
raw: (...gray: Gray) => LAB;
};
hex: {
(...gray: Gray): HEX;
raw: (...gray: Gray) => HEX;
};
} & route.gray & {
[F in keyof route.gray]: {
raw: route.gray[F];
};
};
};
declare const convert: Convert;
export type ColorLike = ColorInstance | string | ArrayLike<number> | number | Record<string, any>;
export type ColorJson = {
model: string;
color: number[];
valpha: number;
};
export type ColorObject = {
alpha?: number | undefined;
} & Record<string, number>;
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
export interface ColorInstance {
toString(): string;
// eslint-disable-next-line @typescript-eslint/naming-convention
toJSON(): ColorJson;
string(places?: number): string;
percentString(places?: number): string;
array(): number[];
object(): ColorObject;
unitArray(): number[];
unitObject(): {
r: number;
g: number;
b: number;
alpha?: number | undefined;
};
round(places?: number): ColorInstance;
alpha(): number;
alpha(value: number): ColorInstance;
red(): number;
red(value: number): ColorInstance;
green(): number;
green(value: number): ColorInstance;
blue(): number;
blue(value: number): ColorInstance;
hue(): number;
hue(value: number): ColorInstance;
saturationl(): number;
saturationl(value: number): ColorInstance;
lightness(): number;
lightness(value: number): ColorInstance;
saturationv(): number;
saturationv(value: number): ColorInstance;
value(): number;
value(value: number): ColorInstance;
chroma(): number;
chroma(value: number): ColorInstance;
gray(): number;
gray(value: number): ColorInstance;
white(): number;
white(value: number): ColorInstance;
wblack(): number;
wblack(value: number): ColorInstance;
cyan(): number;
cyan(value: number): ColorInstance;
magenta(): number;
magenta(value: number): ColorInstance;
yellow(): number;
yellow(value: number): ColorInstance;
black(): number;
black(value: number): ColorInstance;
x(): number;
x(value: number): ColorInstance;
y(): number;
y(value: number): ColorInstance;
z(): number;
z(value: number): ColorInstance;
l(): number;
l(value: number): ColorInstance;
a(): number;
a(value: number): ColorInstance;
b(): number;
b(value: number): ColorInstance;
keyword(): string;
keyword<V extends string>(value: V): ColorInstance;
hex(): string;
hex<V extends string>(value: V): ColorInstance;
hexa(): string;
hexa<V extends string>(value: V): ColorInstance;
rgbNumber(): number;
luminosity(): number;
contrast(color2: ColorInstance): number;
level(color2: ColorInstance): "AAA" | "AA" | "";
isDark(): boolean;
isLight(): boolean;
negate(): ColorInstance;
lighten(ratio: number): ColorInstance;
darken(ratio: number): ColorInstance;
saturate(ratio: number): ColorInstance;
desaturate(ratio: number): ColorInstance;
whiten(ratio: number): ColorInstance;
blacken(ratio: number): ColorInstance;
grayscale(): ColorInstance;
fade(ratio: number): ColorInstance;
opaquer(ratio: number): ColorInstance;
rotate(degrees: number): ColorInstance;
mix(mixinColor: ColorInstance, weight?: number): ColorInstance;
rgb(...arguments_: number[]): ColorInstance;
hsl(...arguments_: number[]): ColorInstance;
hsv(...arguments_: number[]): ColorInstance;
hwb(...arguments_: number[]): ColorInstance;
cmyk(...arguments_: number[]): ColorInstance;
xyz(...arguments_: number[]): ColorInstance;
lab(...arguments_: number[]): ColorInstance;
lch(...arguments_: number[]): ColorInstance;
ansi16(...arguments_: number[]): ColorInstance;
ansi256(...arguments_: number[]): ColorInstance;
hcg(...arguments_: number[]): ColorInstance;
apple(...arguments_: number[]): ColorInstance;
}
export type ColorConstructor = {
(object?: ColorLike, model?: keyof (typeof convert)): ColorInstance;
new (object?: ColorLike, model?: keyof (typeof convert)): ColorInstance;
rgb(...value: number[]): ColorInstance;
rgb(color: ColorLike): ColorInstance;
hsl(...value: number[]): ColorInstance;
hsl(color: ColorLike): ColorInstance;
hsv(...value: number[]): ColorInstance;
hsv(color: ColorLike): ColorInstance;
hwb(...value: number[]): ColorInstance;
hwb(color: ColorLike): ColorInstance;
cmyk(...value: number[]): ColorInstance;
cmyk(color: ColorLike): ColorInstance;
xyz(...value: number[]): ColorInstance;
xyz(color: ColorLike): ColorInstance;
lab(...value: number[]): ColorInstance;
lab(color: ColorLike): ColorInstance;
lch(...value: number[]): ColorInstance;
lch(color: ColorLike): ColorInstance;
ansi16(...value: number[]): ColorInstance;
ansi16(color: ColorLike): ColorInstance;
ansi256(...value: number[]): ColorInstance;
ansi256(color: ColorLike): ColorInstance;
hcg(...value: number[]): ColorInstance;
hcg(color: ColorLike): ColorInstance;
apple(...value: number[]): ColorInstance;
apple(color: ColorLike): ColorInstance;
};
// eslint-disable-next-line @typescript-eslint/naming-convention
declare const Color: ColorConstructor;
export {
Color as default,
};
export {};
@@ -1,58 +0,0 @@
{
"name": "@img/colour",
"version": "1.1.0",
"description": "The ESM-only 'color' package made compatible for use with CommonJS runtimes",
"license": "MIT",
"main": "index.cjs",
"types": "index.d.ts",
"exports": {
".": {
"types": "./index.d.ts",
"require": "./index.cjs",
"default": "./index.cjs"
},
"./package.json": "./package.json"
},
"authors": [
"Heather Arthur <fayearthur@gmail.com>",
"Josh Junon <josh@junon.me>",
"Maxime Thirouin",
"Dyma Ywanov <dfcreative@gmail.com>",
"LitoMore (https://github.com/LitoMore)"
],
"engines": {
"node": ">=18"
},
"files": [
"color.cjs",
"index.d.ts"
],
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "git+https://github.com/lovell/colour.git"
},
"type": "commonjs",
"keywords": [
"color",
"colour",
"cjs",
"commonjs"
],
"scripts": {
"build:cjs": "esbuild node_modules/color/index.js --bundle --platform=node --outfile=color.cjs",
"build:dts": "dts-bundle-generator ./dts-src.ts -o index.d.ts --project tsconfig.build.json --external-inlines color --external-inlines color-convert --export-referenced-types=false",
"build": "npm run build:cjs && npm run build:dts",
"test": "node --test"
},
"devDependencies": {
"color": "5.0.3",
"color-convert": "3.1.3",
"color-name": "2.1.0",
"color-string": "2.1.4",
"dts-bundle-generator": "^9.5.1",
"esbuild": "^0.27.3"
}
}
@@ -1,191 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, "control" means (i) the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising
permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
"Object" form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
"submitted" means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
2. Grant of Copyright License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
3. Grant of Patent License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
4. Redistribution.
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
You must give any other recipients of the Work or Derivative Works a copy of
this License; and
You must cause any modified files to carry prominent notices stating that You
changed the files; and
You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
If the Work includes a "NOTICE" text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
5. Submission of Contributions.
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
6. Trademarks.
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
7. Disclaimer of Warranty.
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
8. Limitation of Liability.
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability.
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets "[]" replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same "printed page" as the copyright notice for easier identification within
third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -1,18 +0,0 @@
# `@img/sharp-darwin-arm64`
Prebuilt sharp for use with macOS 64-bit ARM.
## Licensing
Copyright 2013 Lovell Fuller and others.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
[https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -1,40 +0,0 @@
{
"name": "@img/sharp-darwin-arm64",
"version": "0.34.5",
"description": "Prebuilt sharp for use with macOS 64-bit ARM",
"author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com",
"repository": {
"type": "git",
"url": "git+https://github.com/lovell/sharp.git",
"directory": "npm/darwin-arm64"
},
"license": "Apache-2.0",
"funding": {
"url": "https://opencollective.com/libvips"
},
"preferUnplugged": true,
"optionalDependencies": {
"@img/sharp-libvips-darwin-arm64": "1.2.4"
},
"files": [
"lib"
],
"publishConfig": {
"access": "public"
},
"type": "commonjs",
"exports": {
"./sharp.node": "./lib/sharp-darwin-arm64.node",
"./package": "./package.json"
},
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"os": [
"darwin"
],
"cpu": [
"arm64"
]
}
@@ -1,46 +0,0 @@
# `@img/sharp-libvips-darwin-arm64`
Prebuilt libvips and dependencies for use with sharp on macOS 64-bit ARM.
## Licensing
This software contains third-party libraries
used under the terms of the following licences:
| Library | Used under the terms of |
|---------------|-----------------------------------------------------------------------------------------------------------|
| aom | BSD 2-Clause + [Alliance for Open Media Patent License 1.0](https://aomedia.org/license/patent-license/) |
| cairo | Mozilla Public License 2.0 |
| cgif | MIT Licence |
| expat | MIT Licence |
| fontconfig | [fontconfig Licence](https://gitlab.freedesktop.org/fontconfig/fontconfig/blob/main/COPYING) (BSD-like) |
| freetype | [freetype Licence](https://git.savannah.gnu.org/cgit/freetype/freetype2.git/tree/docs/FTL.TXT) (BSD-like) |
| fribidi | LGPLv3 |
| glib | LGPLv3 |
| harfbuzz | MIT Licence |
| highway | Apache-2.0 License, BSD 3-Clause |
| lcms | MIT Licence |
| libarchive | BSD 2-Clause |
| libexif | LGPLv3 |
| libffi | MIT Licence |
| libheif | LGPLv3 |
| libimagequant | [BSD 2-Clause](https://github.com/lovell/libimagequant/blob/main/COPYRIGHT) |
| libnsgif | MIT Licence |
| libpng | [libpng License](https://github.com/pnggroup/libpng/blob/master/LICENSE) |
| librsvg | LGPLv3 |
| libspng | [BSD 2-Clause, libpng License](https://github.com/randy408/libspng/blob/master/LICENSE) |
| libtiff | [libtiff License](https://gitlab.com/libtiff/libtiff/blob/master/LICENSE.md) (BSD-like) |
| libvips | LGPLv3 |
| libwebp | New BSD License |
| libxml2 | MIT Licence |
| mozjpeg | [zlib License, IJG License, BSD-3-Clause](https://github.com/mozilla/mozjpeg/blob/master/LICENSE.md) |
| pango | LGPLv3 |
| pixman | MIT Licence |
| proxy-libintl | LGPLv3 |
| zlib-ng | [zlib Licence](https://github.com/zlib-ng/zlib-ng/blob/develop/LICENSE.md) |
Use of libraries under the terms of the LGPLv3 is via the
"any later version" clause of the LGPLv2 or LGPLv2.1.
Please report any errors or omissions via
https://github.com/lovell/sharp-libvips/issues/new
@@ -1,220 +0,0 @@
/* glibconfig.h
*
* This is a generated file. Please modify 'glibconfig.h.in'
*/
#ifndef __GLIBCONFIG_H__
#define __GLIBCONFIG_H__
#include <glib/gmacros.h>
#include <limits.h>
#include <float.h>
#define GLIB_HAVE_ALLOCA_H
#define GLIB_STATIC_COMPILATION 1
#define GOBJECT_STATIC_COMPILATION 1
#define GIO_STATIC_COMPILATION 1
#define GMODULE_STATIC_COMPILATION 1
#define GI_STATIC_COMPILATION 1
#define G_INTL_STATIC_COMPILATION 1
#define FFI_STATIC_BUILD 1
/* Specifies that GLib's g_print*() functions wrap the
* system printf functions. This is useful to know, for example,
* when using glibc's register_printf_function().
*/
#define GLIB_USING_SYSTEM_PRINTF
G_BEGIN_DECLS
#define G_MINFLOAT FLT_MIN
#define G_MAXFLOAT FLT_MAX
#define G_MINDOUBLE DBL_MIN
#define G_MAXDOUBLE DBL_MAX
#define G_MINSHORT SHRT_MIN
#define G_MAXSHORT SHRT_MAX
#define G_MAXUSHORT USHRT_MAX
#define G_MININT INT_MIN
#define G_MAXINT INT_MAX
#define G_MAXUINT UINT_MAX
#define G_MINLONG LONG_MIN
#define G_MAXLONG LONG_MAX
#define G_MAXULONG ULONG_MAX
typedef signed char gint8;
typedef unsigned char guint8;
typedef signed short gint16;
typedef unsigned short guint16;
#define G_GINT16_MODIFIER "h"
#define G_GINT16_FORMAT "hi"
#define G_GUINT16_FORMAT "hu"
typedef signed int gint32;
typedef unsigned int guint32;
#define G_GINT32_MODIFIER ""
#define G_GINT32_FORMAT "i"
#define G_GUINT32_FORMAT "u"
#define G_HAVE_GINT64 1 /* deprecated, always true */
G_GNUC_EXTENSION typedef signed long long gint64;
G_GNUC_EXTENSION typedef unsigned long long guint64;
#define G_GINT64_CONSTANT(val) (G_GNUC_EXTENSION (val##LL))
#define G_GUINT64_CONSTANT(val) (G_GNUC_EXTENSION (val##ULL))
#define G_GINT64_MODIFIER "ll"
#define G_GINT64_FORMAT "lli"
#define G_GUINT64_FORMAT "llu"
#define GLIB_SIZEOF_VOID_P 8
#define GLIB_SIZEOF_LONG 8
#define GLIB_SIZEOF_SIZE_T 8
#define GLIB_SIZEOF_SSIZE_T 8
typedef signed long gssize;
typedef unsigned long gsize;
#define G_GSIZE_MODIFIER "l"
#define G_GSSIZE_MODIFIER "l"
#define G_GSIZE_FORMAT "lu"
#define G_GSSIZE_FORMAT "li"
#define G_MAXSIZE G_MAXULONG
#define G_MINSSIZE G_MINLONG
#define G_MAXSSIZE G_MAXLONG
typedef gint64 goffset;
#define G_MINOFFSET G_MININT64
#define G_MAXOFFSET G_MAXINT64
#define G_GOFFSET_MODIFIER G_GINT64_MODIFIER
#define G_GOFFSET_FORMAT G_GINT64_FORMAT
#define G_GOFFSET_CONSTANT(val) G_GINT64_CONSTANT(val)
#define G_POLLFD_FORMAT "%d"
#define GPOINTER_TO_INT(p) ((gint) (glong) (p))
#define GPOINTER_TO_UINT(p) ((guint) (gulong) (p))
#define GINT_TO_POINTER(i) ((gpointer) (glong) (i))
#define GUINT_TO_POINTER(u) ((gpointer) (gulong) (u))
typedef signed long gintptr;
typedef unsigned long guintptr;
#define G_GINTPTR_MODIFIER "l"
#define G_GINTPTR_FORMAT "li"
#define G_GUINTPTR_FORMAT "lu"
#define GLIB_MAJOR_VERSION 2
#define GLIB_MINOR_VERSION 86
#define GLIB_MICRO_VERSION 1
#define G_OS_UNIX
#define G_VA_COPY va_copy
#define G_HAVE_ISO_VARARGS 1
/* gcc-2.95.x supports both gnu style and ISO varargs, but if -ansi
* is passed ISO vararg support is turned off, and there is no work
* around to turn it on, so we unconditionally turn it off.
*/
#if __GNUC__ == 2 && __GNUC_MINOR__ == 95
# undef G_HAVE_ISO_VARARGS
#endif
#define G_HAVE_GROWING_STACK 0
#ifndef _MSC_VER
# define G_HAVE_GNUC_VARARGS 1
#endif
#if defined(__SUNPRO_C) && (__SUNPRO_C >= 0x590)
#define G_GNUC_INTERNAL __attribute__((visibility("hidden")))
#elif defined(__SUNPRO_C) && (__SUNPRO_C >= 0x550)
#define G_GNUC_INTERNAL __hidden
#elif defined (__GNUC__) && defined (G_HAVE_GNUC_VISIBILITY)
#define G_GNUC_INTERNAL __attribute__((visibility("hidden")))
#else
#define G_GNUC_INTERNAL
#endif
#define G_THREADS_ENABLED
#define G_THREADS_IMPL_POSIX
#define G_ATOMIC_LOCK_FREE
#define GINT16_TO_LE(val) ((gint16) (val))
#define GUINT16_TO_LE(val) ((guint16) (val))
#define GINT16_TO_BE(val) ((gint16) GUINT16_SWAP_LE_BE (val))
#define GUINT16_TO_BE(val) (GUINT16_SWAP_LE_BE (val))
#define GINT32_TO_LE(val) ((gint32) (val))
#define GUINT32_TO_LE(val) ((guint32) (val))
#define GINT32_TO_BE(val) ((gint32) GUINT32_SWAP_LE_BE (val))
#define GUINT32_TO_BE(val) (GUINT32_SWAP_LE_BE (val))
#define GINT64_TO_LE(val) ((gint64) (val))
#define GUINT64_TO_LE(val) ((guint64) (val))
#define GINT64_TO_BE(val) ((gint64) GUINT64_SWAP_LE_BE (val))
#define GUINT64_TO_BE(val) (GUINT64_SWAP_LE_BE (val))
#define GLONG_TO_LE(val) ((glong) GINT64_TO_LE (val))
#define GULONG_TO_LE(val) ((gulong) GUINT64_TO_LE (val))
#define GLONG_TO_BE(val) ((glong) GINT64_TO_BE (val))
#define GULONG_TO_BE(val) ((gulong) GUINT64_TO_BE (val))
#define GINT_TO_LE(val) ((gint) GINT32_TO_LE (val))
#define GUINT_TO_LE(val) ((guint) GUINT32_TO_LE (val))
#define GINT_TO_BE(val) ((gint) GINT32_TO_BE (val))
#define GUINT_TO_BE(val) ((guint) GUINT32_TO_BE (val))
#define GSIZE_TO_LE(val) ((gsize) GUINT64_TO_LE (val))
#define GSSIZE_TO_LE(val) ((gssize) GINT64_TO_LE (val))
#define GSIZE_TO_BE(val) ((gsize) GUINT64_TO_BE (val))
#define GSSIZE_TO_BE(val) ((gssize) GINT64_TO_BE (val))
#define G_BYTE_ORDER G_LITTLE_ENDIAN
#define GLIB_SYSDEF_POLLIN =1
#define GLIB_SYSDEF_POLLOUT =4
#define GLIB_SYSDEF_POLLPRI =2
#define GLIB_SYSDEF_POLLHUP =16
#define GLIB_SYSDEF_POLLERR =8
#define GLIB_SYSDEF_POLLNVAL =32
/* No way to disable deprecation warnings for macros, so only emit deprecation
* warnings on platforms where usage of this macro is broken */
#if defined(__APPLE__) || defined(_MSC_VER) || defined(__CYGWIN__)
#define G_MODULE_SUFFIX "so" GLIB_DEPRECATED_MACRO_IN_2_76
#else
#define G_MODULE_SUFFIX "so"
#endif
typedef int GPid;
#define G_PID_FORMAT "i"
#define GLIB_SYSDEF_AF_UNIX 1
#define GLIB_SYSDEF_AF_INET 2
#define GLIB_SYSDEF_AF_INET6 30
#define GLIB_SYSDEF_MSG_OOB 1
#define GLIB_SYSDEF_MSG_PEEK 2
#define GLIB_SYSDEF_MSG_DONTROUTE 4
#define G_DIR_SEPARATOR '/'
#define G_DIR_SEPARATOR_S "/"
#define G_SEARCHPATH_SEPARATOR ':'
#define G_SEARCHPATH_SEPARATOR_S ":"
#undef G_HAVE_FREE_SIZED
G_END_DECLS
#endif /* __GLIBCONFIG_H__ */
@@ -1 +0,0 @@
module.exports = __dirname;
@@ -1,36 +0,0 @@
{
"name": "@img/sharp-libvips-darwin-arm64",
"version": "1.2.4",
"description": "Prebuilt libvips and dependencies for use with sharp on macOS 64-bit ARM",
"author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com",
"repository": {
"type": "git",
"url": "git+https://github.com/lovell/sharp-libvips.git",
"directory": "npm/darwin-arm64"
},
"license": "LGPL-3.0-or-later",
"funding": {
"url": "https://opencollective.com/libvips"
},
"preferUnplugged": true,
"publishConfig": {
"access": "public"
},
"files": [
"lib",
"versions.json"
],
"type": "commonjs",
"exports": {
"./lib": "./lib/index.js",
"./package": "./package.json",
"./versions": "./versions.json"
},
"os": [
"darwin"
],
"cpu": [
"arm64"
]
}
@@ -1,30 +0,0 @@
{
"aom": "3.13.1",
"archive": "3.8.2",
"cairo": "1.18.4",
"cgif": "0.5.0",
"exif": "0.6.25",
"expat": "2.7.3",
"ffi": "3.5.2",
"fontconfig": "2.17.1",
"freetype": "2.14.1",
"fribidi": "1.0.16",
"glib": "2.86.1",
"harfbuzz": "12.1.0",
"heif": "1.20.2",
"highway": "1.3.0",
"imagequant": "2.4.1",
"lcms": "2.17",
"mozjpeg": "0826579",
"pango": "1.57.0",
"pixman": "0.46.4",
"png": "1.6.50",
"proxy-libintl": "0.5",
"rsvg": "2.61.2",
"spng": "0.7.4",
"tiff": "4.7.1",
"vips": "8.17.3",
"webp": "1.6.0",
"xml2": "2.15.1",
"zlib-ng": "2.2.5"
}
@@ -1,21 +0,0 @@
MIT License
Copyright (c) Jared Wray
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -1,624 +0,0 @@
# @keyv/bigmap [<img width="100" align="right" src="https://jaredwray.com/images/keyv-symbol.svg" alt="keyv">](https://github.com/jaredwra/keyv)
> Bigmap for Keyv
[![build](https://github.com/jaredwray/keyv/actions/workflows/tests.yaml/badge.svg)](https://github.com/jaredwray/keyv/actions/workflows/tests.yaml)
[![codecov](https://codecov.io/gh/jaredwray/keyv/branch/main/graph/badge.svg?token=bRzR3RyOXZ)](https://codecov.io/gh/jaredwray/keyv)
[![npm](https://img.shields.io/npm/v/@keyv/bigmap.svg)](https://www.npmjs.com/package/@keyv/bigmap)
[![npm](https://img.shields.io/npm/dm/@keyv/bigmap)](https://npmjs.com/package/@keyv/bigmap)
# Features
* Based on the Map interface and uses the same API.
* Lightweight with no dependencies.
* Scales to past the 17 million key limit of a regular Map.
* Uses a hash `djb2Hash` for fast key lookups.
* Ability to use your own hash function.
* Built in Typescript and Generics for type safety.
* Used in `@cacheable/memory` for scalable in-memory caching.
* Maintained regularly with a focus on performance and reliability.
# Table of Contents
- [Features](#features)
- [Installation](#installation)
- [Overview](#overview)
- [Basic Usage](#basic-usage)
- [Custom Store Size](#custom-store-size)
- [Custom Hash Function](#custom-hash-function)
- [Iteration](#iteration)
- [For...of Loop](#forof-loop)
- [forEach](#foreach)
- [Keys, Values, and Entries](#keys-values-and-entries)
- [Advanced Features](#advanced-features)
- [Type Safety with Generics](#type-safety-with-generics)
- [Large-Scale Data](#large-scale-data)
- [Using with Keyv](#using-with-keyv)
- [createKeyv](#createkeyv)
- [With Custom Options](#with-custom-options)
- [Type Safety](#type-safety)
- [Integration with Keyv Ecosystem](#integration-with-keyv-ecosystem)
- [API](#api)
- [Constructor](#constructor)
- [Properties](#properties)
- [Methods](#methods)
- [set](#set)
- [get](#get)
- [has](#has)
- [delete](#delete)
- [clear](#clear)
- [forEach](#foreach)
- [keys](#keys)
- [values](#values)
- [entries](#entries)
- [Symbol.iterator](#symboliterator)
- [getStore](#getstorekey)
- [getStoreMap](#getstoremapindex)
- [initStore](#initstore)
- [Types](#types)
- [StoreHashFunction](#storehashfunction)
- [defaultHashFunction(key, storeSize)](#defaulthashfunctionkey-storesize)
- [Contributing](#contributing)
- [License](#license)
# Installation
```bash
npm install --save keyv @keyv/bigmap
```
# Overview
BigMap is a scalable Map implementation that overcomes JavaScript's built-in Map limit of approximately 17 million entries. It uses a distributed hash approach with multiple internal Map instances.
# Basic Usage
```typescript
import { BigMap } from '@keyv/bigmap';
// Create a new BigMap
const bigMap = new BigMap<string, number>();
// Set values
bigMap.set('key1', 100);
bigMap.set('key2', 200);
// Get values
const value = bigMap.get('key1'); // 100
// Check if key exists
bigMap.has('key1'); // true
// Delete a key
bigMap.delete('key1'); // true
// Get size
console.log(bigMap.size); // 1
// Clear all entries
bigMap.clear();
```
# Custom Store Size
By default, BigMap uses 4 internal Map instances. You can configure this:
```typescript
const bigMap = new BigMap<string, number>({ storeSize: 10 });
```
**Note:** Changing the `storeSize` after initialization will clear all entries.
# Custom Hash Function
Provide your own hash function for key distribution:
```typescript
const customHashFunction = (key: string, storeSize: number) => {
return key.length % storeSize;
};
const bigMap = new BigMap<string, string>({
storeHashFunction: customHashFunction
});
```
## Using Hashery for Hash Functions
[Hashery](https://github.com/jaredwray/hashery) is a powerful hashing library that provides multiple hash algorithms. You can use it for better key distribution and it is available as an export:
```typescript
import { BigMap, Hashery } from '@keyv/bigmap';
const hashery = new Hashery();
// Using Hashery's toNumberSync for deterministic key distribution
const bigMap = new BigMap<string, string>({
storeHashFunction: (key: string, storeSize: number) => {
return hashery.toNumberSync(key, { min: 0, max: storeSize - 1 });
}
});
// You can also use different algorithms
const hasheryFnv1 = new Hashery({ defaultAlgorithmSync: 'fnv1' });
const bigMapWithFnv1 = new BigMap<string, string>({
storeHashFunction: (key: string, storeSize: number) => {
return hasheryFnv1.toNumberSync(key, { min: 0, max: storeSize - 1 });
}
});
```
Hashery supports multiple synchronous hash algorithms:
- **djb2** - Fast hash function (default)
- **fnv1** - Excellent distribution for hash tables
- **murmer** - MurmurHash algorithm
- **crc32** - Cyclic Redundancy Check
# Iteration
BigMap supports all standard Map iteration methods:
## For...of Loop
```typescript
const bigMap = new BigMap<string, number>();
bigMap.set('a', 1);
bigMap.set('b', 2);
for (const [key, value] of bigMap) {
console.log(key, value);
}
```
## forEach
```typescript
bigMap.forEach((value, key) => {
console.log(key, value);
});
// With custom context
const context = { sum: 0 };
bigMap.forEach(function(value) {
this.sum += value;
}, context);
```
## Keys, Values, and Entries
```typescript
// Iterate over keys
for (const key of bigMap.keys()) {
console.log(key);
}
// Iterate over values
for (const value of bigMap.values()) {
console.log(value);
}
// Iterate over entries
for (const [key, value] of bigMap.entries()) {
console.log(key, value);
}
```
# Advanced Features
## Type Safety with Generics
```typescript
interface User {
id: number;
name: string;
}
const userMap = new BigMap<string, User>();
userMap.set('user1', { id: 1, name: 'Alice' });
```
## Large-Scale Data
BigMap is designed to handle millions of entries:
```typescript
const bigMap = new BigMap<string, number>({ storeSize: 16 });
// Add 20+ million entries without hitting Map limits
for (let i = 0; i < 20000000; i++) {
bigMap.set(`key${i}`, i);
}
console.log(bigMap.size); // 20000000
```
# Using with Keyv
BigMap can be used as a storage adapter for [Keyv](https://github.com/jaredwray/keyv), providing a scalable in-memory store with TTL support.
## createKeyv
The `createKeyv` function creates a Keyv instance with BigMap as the storage adapter.
**Parameters:**
- `options` (optional): BigMap configuration options
- `storeSize` (number): Number of internal Map instances. Default: `4`
- `storeHashFunction` (StoreHashFunction): Custom hash function for key distribution
**Returns:** `Keyv` instance with BigMap adapter
**Example:**
```typescript
import { createKeyv } from '@keyv/bigmap';
// Basic usage
const keyv = createKeyv();
// Set with TTL (in milliseconds)
await keyv.set('user:123', { name: 'Alice', age: 30 }, 60000); // Expires in 60 seconds
// Get value
const user = await keyv.get('user:123');
console.log(user); // { name: 'Alice', age: 30 }
// Check if key exists
const exists = await keyv.has('user:123');
// Delete key
await keyv.delete('user:123');
// Clear all keys
await keyv.clear();
```
## With Custom Options
```typescript
import { createKeyv } from '@keyv/bigmap';
// Create with custom store size for better performance with millions of keys
const keyv = createKeyv({ storeSize: 16 });
// With custom hash function
const keyv = createKeyv({
storeSize: 8,
storeHashFunction: (key, storeSize) => {
// Custom distribution logic
return key.length % storeSize;
}
});
```
## Type Safety
```typescript
import { createKeyv } from '@keyv/bigmap';
interface Product {
id: string;
name: string;
price: number;
}
const keyv = createKeyv<string, Product>();
await keyv.set('product:1', {
id: '1',
name: 'Laptop',
price: 999
});
const product = await keyv.get<Product>('product:1');
```
# Integration with Keyv Ecosystem
BigMap works seamlessly with the Keyv ecosystem:
```typescript
import { createKeyv } from '@keyv/bigmap';
const cache = createKeyv({ storeSize: 16 });
// Use with namespaces
const users = cache.namespace('users');
const products = cache.namespace('products');
await users.set('123', { name: 'Alice' });
await products.set('456', { name: 'Laptop' });
// Iterate over keys
for await (const [key, value] of cache.iterator()) {
console.log(key, value);
}
```
# API
## Constructor
`new BigMap<K, V>(options?)`
Creates a new BigMap instance.
**Parameters:**
- `options` (optional): Configuration options
- `storeSize` (number): Number of internal Map instances to use. Default: `4`. Must be at least 1.
- `storeHashFunction` (StoreHashFunction): Custom hash function for key distribution. Default: `defaultHashFunction`
**Example:**
```typescript
const bigMap = new BigMap<string, number>();
const customBigMap = new BigMap<string, number>({
storeSize: 10,
storeHashFunction: (key, storeSize) => key.length % storeSize
});
```
## Properties
| Property | Type | Access | Description |
|----------|------|--------|-------------|
| `size` | `number` | Read-only | Gets the total number of entries in the BigMap. |
| `storeSize` | `number` | Read/Write | Gets or sets the number of internal Map instances. **Note:** Setting this will clear all entries. Default: `4` |
| `storeHashFunction` | `StoreHashFunction \| undefined` | Read/Write | Gets or sets the hash function used for key distribution. |
| `store` | `Array<Map<K, V>>` | Read-only | Gets the internal array of Map instances. |
**Examples:**
```typescript
const bigMap = new BigMap<string, number>();
// size property
bigMap.set('key1', 100);
console.log(bigMap.size); // 1
// storeSize property
console.log(bigMap.storeSize); // 4 (default)
bigMap.storeSize = 8; // Changes size and clears all entries
// storeHashFunction property
bigMap.storeHashFunction = (key, storeSize) => key.length % storeSize;
// store property
console.log(bigMap.store.length); // 8
```
## Methods
### set
Sets the value for a key in the map.
**Parameters:**
- `key` (K): The key to set
- `value` (V): The value to associate with the key
**Returns:** `Map<K, V>` - The internal Map instance where the key was stored
**Example:**
```typescript
bigMap.set('user123', { name: 'Alice' });
```
### get
Gets the value associated with a key.
**Parameters:**
- `key` (K): The key to retrieve
**Returns:** `V | undefined` - The value, or undefined if not found
**Example:**
```typescript
const value = bigMap.get('user123');
```
### has
Checks if a key exists in the map.
**Parameters:**
- `key` (K): The key to check
**Returns:** `boolean` - True if the key exists, false otherwise
**Example:**
```typescript
if (bigMap.has('user123')) {
console.log('User exists');
}
```
### delete
Deletes a key-value pair from the map.
**Parameters:**
- `key` (K): The key to delete
**Returns:** `boolean` - True if the entry was deleted, false if the key was not found
**Example:**
```typescript
const deleted = bigMap.delete('user123');
```
### clear
Removes all entries from the map.
**Returns:** `void`
**Example:**
```typescript
bigMap.clear();
console.log(bigMap.size); // 0
```
### forEach
Executes a provided function once for each key-value pair.
**Parameters:**
- `callbackfn` (function): Function to execute for each entry
- `value` (V): The value of the current entry
- `key` (K): The key of the current entry
- `map` (`Map<K, V>`): The BigMap instance
- `thisArg` (optional): Value to use as `this` when executing the callback
**Returns:** `void`
**Example:**
```typescript
bigMap.forEach((value, key) => {
console.log(`${key}: ${value}`);
});
// With custom context
const context = { total: 0 };
bigMap.forEach(function(value) {
this.total += value;
}, context);
```
### keys
Returns an iterator of all keys in the map.
**Returns:** `IterableIterator<K>`
**Example:**
```typescript
for (const key of bigMap.keys()) {
console.log(key);
}
```
### values
Returns an iterator of all values in the map.
**Returns:** `IterableIterator<V>`
**Example:**
```typescript
for (const value of bigMap.values()) {
console.log(value);
}
```
### entries
Returns an iterator of all key-value pairs in the map.
**Returns:** `IterableIterator<[K, V]>`
**Example:**
```typescript
for (const [key, value] of bigMap.entries()) {
console.log(key, value);
}
```
### Symbol.iterator
Returns an iterator for the map (same as `entries()`). Enables `for...of` loops.
**Returns:** `IterableIterator<[K, V]>`
**Example:**
```typescript
for (const [key, value] of bigMap) {
console.log(key, value);
}
```
### getStore
Gets the internal Map instance for a specific key.
**Parameters:**
- `key` (K): The key to find the store for
**Returns:** `Map<K, V>` - The internal Map instance
**Example:**
```typescript
const store = bigMap.getStore('user123');
```
### getStoreMap
Gets the internal Map instance at a specific index.
**Parameters:**
- `index` (number): The index of the Map to retrieve (0 to storeSize - 1)
**Returns:** `Map<K, V>` - The Map at the specified index
**Throws:** Error if index is out of bounds
**Example:**
```typescript
const firstMap = bigMap.getStoreMap(0);
```
### initStore
Initializes the internal store with empty Map instances. Called automatically during construction.
**Returns:** `void`
## Types
### StoreHashFunction
Type definition for custom hash functions.
```typescript
type StoreHashFunction = (key: string, storeSize: number) => number;
```
**Parameters:**
- `key` (string): The key to hash (converted to string)
- `storeSize` (number): The number of stores (adjusted for zero-based index)
**Returns:** `number` - The index of the store to use (0 to storeSize - 1)
### defaultHashFunction
The default hash function using DJB2 algorithm from [Hashery](https://npmjs.com/package/hashery):
**Example:**
```typescript
import { defaultHashFunction } from '@keyv/bigmap';
const index = defaultHashFunction('myKey', 4);
```
### djb2Hash
DJB2 hash algorithm implementation.
**Parameters:**
- `string` (string): The string to hash
- `min` (number): Minimum value. Default: `0`
- `max` (number): Maximum value. Default: `10`
**Returns:** `number` - Hash value within the specified range
**Example:**
```typescript
import { djb2Hash } from '@keyv/bigmap';
const hash = djb2Hash('myKey', 0, 10);
```
# Contributing
Please see our [contributing](https://github.com/jaredwray/keyv/blob/main/CONTRIBUTING.md) guide.
# License
[MIT © Jared Wray](LICENSE)
@@ -1,73 +0,0 @@
{
"name": "@keyv/bigmap",
"version": "1.3.1",
"description": "Bigmap for Keyv",
"type": "module",
"main": "dist/index.js",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
},
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
}
},
"repository": {
"type": "git",
"url": "git+https://github.com/jaredwray/keyv.git"
},
"keywords": [
"bigmap",
"keyv",
"key",
"value",
"store",
"cache",
"ttl"
],
"author": "Jared Wray <me@jaredwray.com> (http://jaredwray.com)",
"license": "MIT",
"bugs": {
"url": "https://github.com/jaredwray/keyv/issues"
},
"homepage": "https://github.com/jaredwray/keyv",
"dependencies": {
"hashery": "^1.4.0",
"hookified": "^1.15.0"
},
"devDependencies": {
"@biomejs/biome": "^2.3.11",
"@faker-js/faker": "^10.2.0",
"@vitest/coverage-v8": "^4.0.17",
"rimraf": "^6.1.2",
"tsd": "^0.33.0",
"vitest": "^4.0.17"
},
"peerDependencies": {
"keyv": "^5.6.0"
},
"tsd": {
"directory": "test"
},
"engines": {
"node": ">= 18"
},
"files": [
"dist",
"LICENSE"
],
"scripts": {
"build": "rimraf ./dist && tsup src/index.ts --format cjs,esm --dts --clean",
"lint": "biome check --write --error-on-warnings",
"lint:ci": "biome check --error-on-warnings",
"test": "pnpm lint && vitest run --coverage",
"test:ci": "pnpm lint:ci && vitest --run --sequence.setupFiles=list --coverage",
"clean": "rimraf ./node_modules ./coverage ./dist"
}
}
@@ -1,22 +0,0 @@
MIT License
Copyright (c) 2017-2021 Luke Childs
Copyright (c) 2021-2022 Jared Wray
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -1,15 +0,0 @@
# @keyv/serialize [<img width="100" align="right" src="https://jaredwray.com/images/keyv-symbol.svg" alt="keyv">](https://github.com/jaredwra/keyv)
> Serialization functionality for [Keyv](https://github.com/jaredwray/keyv)
[![build](https://github.com/jaredwray/keyv/actions/workflows/tests.yaml/badge.svg)](https://github.com/jaredwray/keyv/actions/workflows/tests.yaml)
[![codecov](https://codecov.io/gh/jaredwray/keyv/branch/main/graph/badge.svg?token=bRzR3RyOXZ)](https://codecov.io/gh/jaredwray/keyv)
[![GitHub license](https://img.shields.io/github/license/jaredwray/keyv)](https://github.com/jaredwray/keyv/blob/main/LICENSE)
[![npm](https://img.shields.io/npm/dm/@keyv/serialize)](https://npmjs.com/package/@keyv/serialize)
This is the serialization functionality for [Keyv](https://github.com/jaredwray/keyv/tree/main/packages/keyv). It is used to serialize and deserialize data for storage and retrieval. You can also create your own [custom serialization functions](https://github.com/jaredwray/keyv/tree/main/packages/keyv#custom-serializers).
## License
[MIT © Jared Wray](LISCENCE)
@@ -1,55 +0,0 @@
{
"name": "@keyv/serialize",
"version": "1.1.1",
"description": "Serialization for Keyv",
"type": "module",
"main": "dist/index.cjs",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"require": "./dist/index.cjs",
"import": "./dist/index.js"
}
},
"repository": {
"type": "git",
"url": "git+https://github.com/jaredwray/keyv.git"
},
"keywords": [
"keyv",
"serialize",
"key",
"value",
"store"
],
"author": "Jared Wray <me@jaredwray.com> (https://jaredwray.com)",
"license": "MIT",
"bugs": {
"url": "https://github.com/jaredwray/keyv/issues"
},
"homepage": "https://github.com/jaredwray/keyv",
"devDependencies": {
"@biomejs/biome": "^2.2.3",
"@vitest/coverage-v8": "^3.2.4",
"rimraf": "^6.0.1",
"tsd": "^0.33.0",
"typescript": "^5.9.2",
"vitest": "^3.2.4",
"@keyv/test-suite": "^2.1.1",
"keyv": "^5.5.0"
},
"tsd": {
"directory": "test"
},
"files": [
"dist",
"LICENSE"
],
"scripts": {
"build": "rimraf ./dist && tsup src/index.ts --format cjs,esm --dts --clean",
"test": "biome check --write && vitest run --coverage",
"test:ci": "biome check && vitest --run --sequence.setupFiles=list --coverage",
"clean": "rimraf ./node_modules ./coverage ./dist"
}
}
@@ -1,13 +0,0 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
open-pull-requests-limit: 10
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "monthly"
open-pull-requests-limit: 10
@@ -1,48 +0,0 @@
name: CI
on:
push:
branches:
- main
- 'v*'
paths-ignore:
- 'docs/**'
- '*.md'
pull_request:
paths-ignore:
- 'docs/**'
- '*.md'
# This allows a subsequently queued workflow run to interrupt previous runs
concurrency:
group: "${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}"
cancel-in-progress: true
jobs:
test:
name: ${{ matrix.node-version }} ${{ matrix.os }}
runs-on: ${{ matrix.os }}
permissions:
contents: read
strategy:
fail-fast: false
matrix:
os: [macOS-latest, windows-latest, ubuntu-latest]
node-version: [18, 20, 22, 24]
steps:
- name: Check out repo
uses: actions/checkout@v5.0.0
with:
persist-credentials: false
- name: Setup Node ${{ matrix.node-version }}
uses: actions/setup-node@v5
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies
run: npm i --ignore-scripts
- name: Run tests
run: npm run test
@@ -1,43 +0,0 @@
name: Publish release
on:
workflow_dispatch:
inputs:
version:
description: 'The version number to tag and release'
required: true
type: string
prerelease:
description: 'Release as pre-release'
required: false
type: boolean
default: false
jobs:
release-npm:
runs-on: ubuntu-latest
environment: main
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v4
- uses: actions/setup-node@v5
with:
node-version: '22'
registry-url: 'https://registry.npmjs.org'
- run: npm install npm -g
- run: npm install
- name: Change version number and sync
run: |
node scripts/sync-version.mjs ${{ inputs.version }}
- name: GIT commit and push all changed files
run: |
git config --global user.name "mcollina"
git config --global user.email "hello@matteocollina.com"
git commit -n -a -m "Bumped v${{ inputs.version }}"
git push origin HEAD:${{ github.ref }}
- run: npm publish --access public --tag ${{ inputs.prerelease == true && 'next' || 'latest' }}
- name: 'Create release notes'
run: |
npx @matteo.collina/release-notes -a ${{ secrets.GITHUB_TOKEN }} -t v${{ inputs.version }} -r redact -o pinojs ${{ github.event.inputs.prerelease == 'true' && '-p' || '' }} -c ${{ github.ref }}
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2025 pinojs contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -1,350 +0,0 @@
# @pinojs/redact
> Smart object redaction for JavaScript applications - safe AND fast!
Redact JS objects with the same API as [fast-redact](https://github.com/davidmarkclements/fast-redact), but uses innovative **selective cloning** instead of mutating the original. This provides immutability guarantees with **performance competitive** to fast-redact for real-world usage patterns.
## Install
```bash
npm install @pinojs/redact
```
## Usage
```js
const slowRedact = require('@pinojs/redact')
const redact = slowRedact({
paths: ['headers.cookie', 'headers.authorization', 'user.password']
})
const obj = {
headers: {
cookie: 'secret-session-token',
authorization: 'Bearer abc123',
'x-forwarded-for': '192.168.1.1'
},
user: {
name: 'john',
password: 'secret123'
}
}
console.log(redact(obj))
// Output: {"headers":{"cookie":"[REDACTED]","authorization":"[REDACTED]","x-forwarded-for":"192.168.1.1"},"user":{"name":"john","password":"[REDACTED]"}}
// Original object is completely unchanged:
console.log(obj.headers.cookie) // 'secret-session-token'
```
## API
### slowRedact(options) → Function
Creates a redaction function with the specified options.
#### Options
- **paths** `string[]` (required): An array of strings describing the nested location of a key in an object
- **censor** `any` (optional, default: `'[REDACTED]'`): The value to replace sensitive data with. Can be a static value or function.
- **serialize** `Function|boolean` (optional, default: `JSON.stringify`): Serialization function. Set to `false` to return the redacted object.
- **remove** `boolean` (optional, default: `false`): Remove redacted keys from serialized output
- **strict** `boolean` (optional, default: `true`): Throw on non-object values or pass through primitives
#### Path Syntax
Supports the same path syntax as fast-redact:
- **Dot notation**: `'user.name'`, `'headers.cookie'`
- **Bracket notation**: `'user["password"]'`, `'headers["X-Forwarded-For"]'`
- **Array indices**: `'users[0].password'`, `'items[1].secret'`
- **Wildcards**:
- Terminal: `'users.*.password'` (redacts password for all users)
- Intermediate: `'*.password'` (redacts password at any level)
- Array wildcard: `'items.*'` (redacts all array elements)
#### Examples
**Custom censor value:**
```js
const redact = slowRedact({
paths: ['password'],
censor: '***HIDDEN***'
})
```
**Dynamic censor function:**
```js
const redact = slowRedact({
paths: ['password'],
censor: (value, path) => `REDACTED:${path}`
})
```
**Return object instead of JSON string:**
```js
const redact = slowRedact({
paths: ['secret'],
serialize: false
})
const result = redact({ secret: 'hidden', public: 'data' })
console.log(result.secret) // '[REDACTED]'
console.log(result.public) // 'data'
// Restore original values
const restored = result.restore()
console.log(restored.secret) // 'hidden'
```
**Custom serialization:**
```js
const redact = slowRedact({
paths: ['password'],
serialize: obj => JSON.stringify(obj, null, 2)
})
```
**Remove keys instead of redacting:**
```js
const redact = slowRedact({
paths: ['password', 'user.secret'],
remove: true
})
const obj = { username: 'john', password: 'secret123', user: { name: 'Jane', secret: 'hidden' } }
console.log(redact(obj))
// Output: {"username":"john","user":{"name":"Jane"}}
// Note: 'password' and 'user.secret' are completely absent, not redacted
```
**Wildcard patterns:**
```js
// Redact all properties in secrets object
const redact1 = slowRedact({ paths: ['secrets.*'] })
// Redact password for any user
const redact2 = slowRedact({ paths: ['users.*.password'] })
// Redact all items in an array
const redact3 = slowRedact({ paths: ['items.*'] })
// Remove all secrets instead of redacting them
const redact4 = slowRedact({ paths: ['secrets.*'], remove: true })
```
## Key Differences from fast-redact
### Safety First
- **No mutation**: Original objects are never modified
- **Selective cloning**: Only clones paths that need redaction, shares references for everything else
- **Restore capability**: Can restore original values when `serialize: false`
### Feature Compatibility
- **Remove option**: Full compatibility with fast-redact's `remove: true` option to completely omit keys from output
- **All path patterns**: Supports same syntax including wildcards, bracket notation, and array indices
- **Censor functions**: Dynamic censoring with path information passed as arrays
- **Serialization**: Custom serializers and `serialize: false` mode
### Smart Performance Approach
- **Selective cloning**: Analyzes redaction paths and only clones necessary object branches
- **Reference sharing**: Non-redacted properties maintain original object references
- **Memory efficiency**: Dramatically reduced memory usage for large objects with minimal redaction
- **Setup-time optimization**: Path analysis happens once during setup, not per redaction
### When to Use @pinojs/redact
- When immutability is critical
- When you need to preserve original objects
- When objects are shared across multiple contexts
- In functional programming environments
- When debugging and you need to compare before/after
- **Large objects with selective redaction** (now performance-competitive!)
- When memory efficiency with reference sharing is important
### When to Use fast-redact
- When absolute maximum performance is critical
- In extremely high-throughput scenarios (>100,000 ops/sec)
- When you control the object lifecycle and mutation is acceptable
- Very small objects where setup overhead matters
## Performance Benchmarks
@pinojs/redact uses **selective cloning** that provides good performance while maintaining immutability guarantees:
### Performance Results
| Operation Type | @pinojs/redact | fast-redact | Performance Ratio |
|---------------|-------------|-------------|-------------------|
| **Small objects** | ~690ns | ~200ns | ~3.5x slower |
| **Large objects (minimal redaction)** | **~18μs** | ~17μs | **~same performance** |
| **Large objects (wildcards)** | **~48μs** | ~37μs | **~1.3x slower** |
| **No redaction (large objects)** | **~18μs** | ~17μs | **~same performance** |
### Performance Improvements
@pinojs/redact is performance-competitive with fast-redact for large objects.
1. **Selective cloning approach**: Only clones object paths that need redaction
2. **Reference sharing**: Non-redacted properties share original object references
3. **Setup-time optimization**: Path analysis happens once, not per redaction
4. **Memory efficiency**: Dramatically reduced memory usage for typical use cases
### Benchmark Details
**Small Objects (~180 bytes)**:
- @pinojs/redact: **690ns** per operation
- fast-redact: **200ns** per operation
- **Slight setup overhead for small objects**
**Large Objects (~18KB, minimal redaction)**:
- @pinojs/redact: **18μs** per operation
- fast-redact: **17μs** per operation
- Near-identical performance
**Large Objects (~18KB, wildcard patterns)**:
- @pinojs/redact: **48μs** per operation
- fast-redact: **37μs** per operation
- Competitive performance for complex patterns
**Memory Considerations**:
- @pinojs/redact: **Selective reference sharing** (much lower memory usage than before)
- fast-redact: Mutates in-place (lowest memory usage)
- Large objects with few redacted paths now share most references
### When Performance Matters
Choose **fast-redact** when:
- Absolute maximum performance is critical (>100,000 ops/sec)
- Working with very small objects frequently
- Mutation is acceptable and controlled
- Every microsecond counts
Choose **@pinojs/redact** when:
- Immutability is required (with competitive performance)
- Objects are shared across contexts
- Large objects with selective redaction
- Memory efficiency through reference sharing is important
- Safety and functionality are priorities
- Most production applications (performance gap is minimal)
Run benchmarks yourself:
```bash
npm run bench
```
## How Selective Cloning Works
@pinojs/redact uses an innovative **selective cloning** approach that provides immutability guarantees while dramatically improving performance:
### Traditional Approach (before optimization)
```js
// Old approach: Deep clone entire object, then redact
const fullClone = deepClone(originalObject) // Clone everything
redact(fullClone, paths) // Then redact specific paths
```
### Selective Cloning Approach (current)
```js
// New approach: Analyze paths, clone only what's needed
const pathStructure = buildPathStructure(paths) // One-time setup
const selectiveClone = cloneOnlyNeededPaths(obj, pathStructure) // Smart cloning
redact(selectiveClone, paths) // Redact pre-identified paths
```
### Key Innovations
1. **Path Analysis**: Pre-processes redaction paths into an efficient tree structure
2. **Selective Cloning**: Only creates new objects for branches that contain redaction targets
3. **Reference Sharing**: Non-redacted properties maintain exact same object references
4. **Setup Optimization**: Path parsing happens once during redactor creation, not per redaction
### Example: Reference Sharing in Action
```js
const largeConfig = {
database: { /* large config object */ },
api: { /* another large config */ },
secrets: { password: 'hidden', apiKey: 'secret' }
}
const redact = slowRedact({ paths: ['secrets.password'] })
const result = redact(largeConfig)
// Only secrets object is cloned, database and api share original references
console.log(result.database === largeConfig.database) // true - shared reference!
console.log(result.api === largeConfig.api) // true - shared reference!
console.log(result.secrets === largeConfig.secrets) // false - cloned for redaction
```
This approach provides **immutability where it matters** while **sharing references where it's safe**.
## Remove Option
The `remove: true` option provides full compatibility with fast-redact's key removal functionality:
```js
const redact = slowRedact({
paths: ['password', 'secrets.*', 'users.*.credentials'],
remove: true
})
const data = {
username: 'john',
password: 'secret123',
secrets: { apiKey: 'abc', token: 'xyz' },
users: [
{ name: 'Alice', credentials: { password: 'pass1' } },
{ name: 'Bob', credentials: { password: 'pass2' } }
]
}
console.log(redact(data))
// Output: {"username":"john","secrets":{},"users":[{"name":"Alice"},{"name":"Bob"}]}
```
### Remove vs Redact Behavior
| Option | Behavior | Output Example |
|--------|----------|----------------|
| Default (redact) | Replaces values with censor | `{"password":"[REDACTED]"}` |
| `remove: true` | Completely omits keys | `{}` |
### Compatibility Notes
- **Same output as fast-redact**: Identical JSON output when using `remove: true`
- **Wildcard support**: Works with all wildcard patterns (`*`, `users.*`, `items.*.secret`)
- **Array handling**: Array items are set to `undefined` (omitted in JSON output)
- **Nested paths**: Supports deep removal (`users.*.credentials.password`)
- **Serialize compatibility**: Only works with `JSON.stringify` serializer (like fast-redact)
## Testing
```bash
# Run unit tests
npm test
# Run integration tests comparing with fast-redact
npm run test:integration
# Run all tests (unit + integration)
npm run test:all
# Run benchmarks
npm run bench
```
### Test Coverage
- **16 unit tests**: Core functionality and edge cases
- **16 integration tests**: Output compatibility with fast-redact
- **All major features**: Paths, wildcards, serialization, custom censors
- **Performance benchmarks**: Direct comparison with fast-redact
## License
MIT
## Contributing
Pull requests welcome! Please ensure all tests pass and add tests for new features.
@@ -1,184 +0,0 @@
const { bench, group, run } = require('mitata')
const slowRedact = require('../index.js')
const fastRedact = require('fast-redact')
// Test objects
const smallObj = {
user: { name: 'john', password: 'secret123' },
headers: { cookie: 'session-token', authorization: 'Bearer abc123' }
}
const largeObj = {
users: [],
metadata: {
version: '1.0.0',
secret: 'app-secret-key',
database: {
host: 'localhost',
password: 'db-password'
}
}
}
// Populate users array with for loop instead of Array.from
for (let i = 0; i < 100; i++) {
largeObj.users.push({
id: i,
name: `user${i}`,
email: `user${i}@example.com`,
password: `secret${i}`,
profile: {
age: 20 + (i % 50),
preferences: {
theme: 'dark',
notifications: true,
apiKey: `key-${i}-secret`
}
}
})
}
// Redaction configurations
const basicSlowRedact = slowRedact({
paths: ['user.password', 'headers.cookie']
})
const basicFastRedact = fastRedact({
paths: ['user.password', 'headers.cookie']
})
const wildcardSlowRedact = slowRedact({
paths: ['users.*.password', 'users.*.profile.preferences.apiKey']
})
const wildcardFastRedact = fastRedact({
paths: ['users.*.password', 'users.*.profile.preferences.apiKey']
})
const deepSlowRedact = slowRedact({
paths: ['metadata.secret', 'metadata.database.password']
})
const deepFastRedact = fastRedact({
paths: ['metadata.secret', 'metadata.database.password']
})
group('Small Object Redaction - @pinojs/redact', () => {
bench('basic paths', () => {
basicSlowRedact(smallObj)
})
bench('serialize: false', () => {
const redact = slowRedact({
paths: ['user.password'],
serialize: false
})
redact(smallObj)
})
bench('custom censor function', () => {
const redact = slowRedact({
paths: ['user.password'],
censor: (value, path) => `HIDDEN:${path}`
})
redact(smallObj)
})
})
group('Small Object Redaction - fast-redact', () => {
bench('basic paths', () => {
basicFastRedact(smallObj)
})
bench('serialize: false', () => {
const redact = fastRedact({
paths: ['user.password'],
serialize: false
})
redact(smallObj)
})
bench('custom censor function', () => {
const redact = fastRedact({
paths: ['user.password'],
censor: (value, path) => `HIDDEN:${path}`
})
redact(smallObj)
})
})
group('Large Object Redaction - @pinojs/redact', () => {
bench('wildcard patterns', () => {
wildcardSlowRedact(largeObj)
})
bench('deep nested paths', () => {
deepSlowRedact(largeObj)
})
bench('multiple wildcards', () => {
const redact = slowRedact({
paths: ['users.*.password', 'users.*.profile.preferences.*']
})
redact(largeObj)
})
})
group('Large Object Redaction - fast-redact', () => {
bench('wildcard patterns', () => {
wildcardFastRedact(largeObj)
})
bench('deep nested paths', () => {
deepFastRedact(largeObj)
})
bench('multiple wildcards', () => {
const redact = fastRedact({
paths: ['users.*.password', 'users.*.profile.preferences.*']
})
redact(largeObj)
})
})
group('Direct Performance Comparison', () => {
bench('@pinojs/redact - basic paths', () => {
basicSlowRedact(smallObj)
})
bench('fast-redact - basic paths', () => {
basicFastRedact(smallObj)
})
bench('@pinojs/redact - wildcards', () => {
wildcardSlowRedact(largeObj)
})
bench('fast-redact - wildcards', () => {
wildcardFastRedact(largeObj)
})
})
group('Object Cloning Overhead', () => {
bench('@pinojs/redact - no redaction (clone only)', () => {
const redact = slowRedact({ paths: [] })
redact(smallObj)
})
bench('fast-redact - no redaction', () => {
const redact = fastRedact({ paths: [] })
redact(smallObj)
})
bench('@pinojs/redact - large object clone', () => {
const redact = slowRedact({ paths: [] })
redact(largeObj)
})
bench('fast-redact - large object', () => {
const redact = fastRedact({ paths: [] })
redact(largeObj)
})
})
run()
@@ -1 +0,0 @@
module.exports = require('neostandard')()
@@ -1,52 +0,0 @@
export = F;
/**
* When called without any options, or with a zero length paths array, @pinojs/redact will return JSON.stringify or the serialize option, if set.
* @param redactOptions
* @param redactOptions.paths An array of strings describing the nested location of a key in an object.
* @param redactOptions.censor This is the value which overwrites redacted properties.
* @param redactOptions.remove The remove option, when set to true will cause keys to be removed from the serialized output.
* @param redactOptions.serialize The serialize option may either be a function or a boolean. If a function is supplied, this will be used to serialize the redacted object.
* @param redactOptions.strict The strict option, when set to true, will cause the redactor function to throw if instead of an object it finds a primitive.
* @returns Redacted value from input
*/
declare function F(
redactOptions: F.RedactOptionsNoSerialize
): F.redactFnNoSerialize;
declare function F(redactOptions?: F.RedactOptions): F.redactFn;
declare namespace F {
/** Redacts input */
type redactFn = <T>(input: T) => string | T;
/** Redacts input without serialization */
type redactFnNoSerialize = redactFn & {
/** Method that allowing the redacted keys to be restored with the original data. Supplied only when serialize option set to false. */
restore<T>(input: T): T;
};
interface RedactOptions {
/** An array of strings describing the nested location of a key in an object. */
paths?: string[] | undefined;
/** This is the value which overwrites redacted properties. */
censor?: string | ((v: any) => any) | undefined;
/** The remove option, when set to true will cause keys to be removed from the serialized output. */
remove?: boolean | undefined;
/**
* The serialize option may either be a function or a boolean. If a function is supplied, this will be used to serialize the redacted object.
* The default serialize is the function JSON.stringify
*/
serialize?: boolean | ((v: any) => any) | undefined;
/** The strict option, when set to true, will cause the redactor function to throw if instead of an object it finds a primitive. */
strict?: boolean | undefined;
}
/** RedactOptions without serialization. Instead of the serialized object, the output of the redactor function will be the mutated object itself. */
interface RedactOptionsNoSerialize extends RedactOptions {
serialize: false;
}
}
@@ -1,529 +0,0 @@
'use strict'
function deepClone (obj) {
if (obj === null || typeof obj !== 'object') {
return obj
}
if (obj instanceof Date) {
return new Date(obj.getTime())
}
if (obj instanceof Array) {
const cloned = []
for (let i = 0; i < obj.length; i++) {
cloned[i] = deepClone(obj[i])
}
return cloned
}
if (typeof obj === 'object') {
const cloned = Object.create(Object.getPrototypeOf(obj))
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
cloned[key] = deepClone(obj[key])
}
}
return cloned
}
return obj
}
function parsePath (path) {
const parts = []
let current = ''
let inBrackets = false
let inQuotes = false
let quoteChar = ''
for (let i = 0; i < path.length; i++) {
const char = path[i]
if (!inBrackets && char === '.') {
if (current) {
parts.push(current)
current = ''
}
} else if (char === '[') {
if (current) {
parts.push(current)
current = ''
}
inBrackets = true
} else if (char === ']' && inBrackets) {
// Always push the current value when closing brackets, even if it's an empty string
parts.push(current)
current = ''
inBrackets = false
inQuotes = false
} else if ((char === '"' || char === "'") && inBrackets) {
if (!inQuotes) {
inQuotes = true
quoteChar = char
} else if (char === quoteChar) {
inQuotes = false
quoteChar = ''
} else {
current += char
}
} else {
current += char
}
}
if (current) {
parts.push(current)
}
return parts
}
function setValue (obj, parts, value) {
let current = obj
for (let i = 0; i < parts.length - 1; i++) {
const key = parts[i]
// Type safety: Check if current is an object before using 'in' operator
if (typeof current !== 'object' || current === null || !(key in current)) {
return false // Path doesn't exist, don't create it
}
if (typeof current[key] !== 'object' || current[key] === null) {
return false // Path doesn't exist properly
}
current = current[key]
}
const lastKey = parts[parts.length - 1]
if (lastKey === '*') {
if (Array.isArray(current)) {
for (let i = 0; i < current.length; i++) {
current[i] = value
}
} else if (typeof current === 'object' && current !== null) {
for (const key in current) {
if (Object.prototype.hasOwnProperty.call(current, key)) {
current[key] = value
}
}
}
} else {
// Type safety: Check if current is an object before using 'in' operator
if (typeof current === 'object' && current !== null && lastKey in current && Object.prototype.hasOwnProperty.call(current, lastKey)) {
current[lastKey] = value
}
}
return true
}
function removeKey (obj, parts) {
let current = obj
for (let i = 0; i < parts.length - 1; i++) {
const key = parts[i]
// Type safety: Check if current is an object before using 'in' operator
if (typeof current !== 'object' || current === null || !(key in current)) {
return false // Path doesn't exist, don't create it
}
if (typeof current[key] !== 'object' || current[key] === null) {
return false // Path doesn't exist properly
}
current = current[key]
}
const lastKey = parts[parts.length - 1]
if (lastKey === '*') {
if (Array.isArray(current)) {
// For arrays, we can't really "remove" all items as that would change indices
// Instead, we set them to undefined which will be omitted by JSON.stringify
for (let i = 0; i < current.length; i++) {
current[i] = undefined
}
} else if (typeof current === 'object' && current !== null) {
for (const key in current) {
if (Object.prototype.hasOwnProperty.call(current, key)) {
delete current[key]
}
}
}
} else {
// Type safety: Check if current is an object before using 'in' operator
if (typeof current === 'object' && current !== null && lastKey in current && Object.prototype.hasOwnProperty.call(current, lastKey)) {
delete current[lastKey]
}
}
return true
}
// Sentinel object to distinguish between undefined value and non-existent path
const PATH_NOT_FOUND = Symbol('PATH_NOT_FOUND')
function getValueIfExists (obj, parts) {
let current = obj
for (const part of parts) {
if (current === null || current === undefined) {
return PATH_NOT_FOUND
}
// Type safety: Check if current is an object before property access
if (typeof current !== 'object' || current === null) {
return PATH_NOT_FOUND
}
// Check if the property exists before accessing it
if (!(part in current)) {
return PATH_NOT_FOUND
}
current = current[part]
}
return current
}
function getValue (obj, parts) {
let current = obj
for (const part of parts) {
if (current === null || current === undefined) {
return undefined
}
// Type safety: Check if current is an object before property access
if (typeof current !== 'object' || current === null) {
return undefined
}
current = current[part]
}
return current
}
function redactPaths (obj, paths, censor, remove = false) {
for (const path of paths) {
const parts = parsePath(path)
if (parts.includes('*')) {
redactWildcardPath(obj, parts, censor, path, remove)
} else {
if (remove) {
removeKey(obj, parts)
} else {
// Get value only if path exists - single traversal
const value = getValueIfExists(obj, parts)
if (value === PATH_NOT_FOUND) {
continue
}
const actualCensor = typeof censor === 'function'
? censor(value, parts)
: censor
setValue(obj, parts, actualCensor)
}
}
}
}
function redactWildcardPath (obj, parts, censor, originalPath, remove = false) {
const wildcardIndex = parts.indexOf('*')
if (wildcardIndex === parts.length - 1) {
const parentParts = parts.slice(0, -1)
let current = obj
for (const part of parentParts) {
if (current === null || current === undefined) return
// Type safety: Check if current is an object before property access
if (typeof current !== 'object' || current === null) return
current = current[part]
}
if (Array.isArray(current)) {
if (remove) {
// For arrays, set all items to undefined which will be omitted by JSON.stringify
for (let i = 0; i < current.length; i++) {
current[i] = undefined
}
} else {
for (let i = 0; i < current.length; i++) {
const indexPath = [...parentParts, i.toString()]
const actualCensor = typeof censor === 'function'
? censor(current[i], indexPath)
: censor
current[i] = actualCensor
}
}
} else if (typeof current === 'object' && current !== null) {
if (remove) {
// Collect keys to delete to avoid issues with deleting during iteration
const keysToDelete = []
for (const key in current) {
if (Object.prototype.hasOwnProperty.call(current, key)) {
keysToDelete.push(key)
}
}
for (const key of keysToDelete) {
delete current[key]
}
} else {
for (const key in current) {
const keyPath = [...parentParts, key]
const actualCensor = typeof censor === 'function'
? censor(current[key], keyPath)
: censor
current[key] = actualCensor
}
}
}
} else {
redactIntermediateWildcard(obj, parts, censor, wildcardIndex, originalPath, remove)
}
}
function redactIntermediateWildcard (obj, parts, censor, wildcardIndex, originalPath, remove = false) {
const beforeWildcard = parts.slice(0, wildcardIndex)
const afterWildcard = parts.slice(wildcardIndex + 1)
const pathArray = [] // Cached array to avoid allocations
function traverse (current, pathLength) {
if (pathLength === beforeWildcard.length) {
if (Array.isArray(current)) {
for (let i = 0; i < current.length; i++) {
pathArray[pathLength] = i.toString()
traverse(current[i], pathLength + 1)
}
} else if (typeof current === 'object' && current !== null) {
for (const key in current) {
pathArray[pathLength] = key
traverse(current[key], pathLength + 1)
}
}
} else if (pathLength < beforeWildcard.length) {
const nextKey = beforeWildcard[pathLength]
// Type safety: Check if current is an object before using 'in' operator
if (current && typeof current === 'object' && current !== null && nextKey in current) {
pathArray[pathLength] = nextKey
traverse(current[nextKey], pathLength + 1)
}
} else {
// Check if afterWildcard contains more wildcards
if (afterWildcard.includes('*')) {
// Recursively handle remaining wildcards
// Wrap censor to prepend current path context
const wrappedCensor = typeof censor === 'function'
? (value, path) => {
const fullPath = [...pathArray.slice(0, pathLength), ...path]
return censor(value, fullPath)
}
: censor
redactWildcardPath(current, afterWildcard, wrappedCensor, originalPath, remove)
} else {
// No more wildcards, apply the redaction directly
if (remove) {
removeKey(current, afterWildcard)
} else {
const actualCensor = typeof censor === 'function'
? censor(getValue(current, afterWildcard), [...pathArray.slice(0, pathLength), ...afterWildcard])
: censor
setValue(current, afterWildcard, actualCensor)
}
}
}
}
if (beforeWildcard.length === 0) {
traverse(obj, 0)
} else {
let current = obj
for (let i = 0; i < beforeWildcard.length; i++) {
const part = beforeWildcard[i]
if (current === null || current === undefined) return
// Type safety: Check if current is an object before property access
if (typeof current !== 'object' || current === null) return
current = current[part]
pathArray[i] = part
}
if (current !== null && current !== undefined) {
traverse(current, beforeWildcard.length)
}
}
}
function buildPathStructure (pathsToClone) {
if (pathsToClone.length === 0) {
return null // No paths to redact
}
// Parse all paths and organize by depth
const pathStructure = new Map()
for (const path of pathsToClone) {
const parts = parsePath(path)
let current = pathStructure
for (let i = 0; i < parts.length; i++) {
const part = parts[i]
if (!current.has(part)) {
current.set(part, new Map())
}
current = current.get(part)
}
}
return pathStructure
}
function selectiveClone (obj, pathStructure) {
if (!pathStructure) {
return obj // No paths to redact, return original
}
function cloneSelectively (source, pathMap, depth = 0) {
if (!pathMap || pathMap.size === 0) {
return source // No more paths to clone, return reference
}
if (source === null || typeof source !== 'object') {
return source
}
if (source instanceof Date) {
return new Date(source.getTime())
}
if (Array.isArray(source)) {
const cloned = []
for (let i = 0; i < source.length; i++) {
const indexStr = i.toString()
if (pathMap.has(indexStr) || pathMap.has('*')) {
cloned[i] = cloneSelectively(source[i], pathMap.get(indexStr) || pathMap.get('*'))
} else {
cloned[i] = source[i] // Share reference for non-redacted items
}
}
return cloned
}
// Handle objects
const cloned = Object.create(Object.getPrototypeOf(source))
for (const key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
if (pathMap.has(key) || pathMap.has('*')) {
cloned[key] = cloneSelectively(source[key], pathMap.get(key) || pathMap.get('*'))
} else {
cloned[key] = source[key] // Share reference for non-redacted properties
}
}
}
return cloned
}
return cloneSelectively(obj, pathStructure)
}
function validatePath (path) {
if (typeof path !== 'string') {
throw new Error('Paths must be (non-empty) strings')
}
if (path === '') {
throw new Error('Invalid redaction path ()')
}
// Check for double dots
if (path.includes('..')) {
throw new Error(`Invalid redaction path (${path})`)
}
// Check for comma-separated paths (invalid syntax)
if (path.includes(',')) {
throw new Error(`Invalid redaction path (${path})`)
}
// Check for unmatched brackets
let bracketCount = 0
let inQuotes = false
let quoteChar = ''
for (let i = 0; i < path.length; i++) {
const char = path[i]
if ((char === '"' || char === "'") && bracketCount > 0) {
if (!inQuotes) {
inQuotes = true
quoteChar = char
} else if (char === quoteChar) {
inQuotes = false
quoteChar = ''
}
} else if (char === '[' && !inQuotes) {
bracketCount++
} else if (char === ']' && !inQuotes) {
bracketCount--
if (bracketCount < 0) {
throw new Error(`Invalid redaction path (${path})`)
}
}
}
if (bracketCount !== 0) {
throw new Error(`Invalid redaction path (${path})`)
}
}
function validatePaths (paths) {
if (!Array.isArray(paths)) {
throw new TypeError('paths must be an array')
}
for (const path of paths) {
validatePath(path)
}
}
function slowRedact (options = {}) {
const {
paths = [],
censor = '[REDACTED]',
serialize = JSON.stringify,
strict = true,
remove = false
} = options
// Validate paths upfront to match fast-redact behavior
validatePaths(paths)
// Build path structure once during setup, not on every call
const pathStructure = buildPathStructure(paths)
return function redact (obj) {
if (strict && (obj === null || typeof obj !== 'object')) {
if (obj === null || obj === undefined) {
return serialize ? serialize(obj) : obj
}
if (typeof obj !== 'object') {
return serialize ? serialize(obj) : obj
}
}
// Only clone paths that need redaction
const cloned = selectiveClone(obj, pathStructure)
const original = obj // Keep reference to original for restore
let actualCensor = censor
if (typeof censor === 'function') {
actualCensor = censor
}
redactPaths(cloned, paths, actualCensor, remove)
if (serialize === false) {
cloned.restore = function () {
return deepClone(original) // Full clone only when restore is called
}
return cloned
}
if (typeof serialize === 'function') {
return serialize(cloned)
}
return JSON.stringify(cloned)
}
}
module.exports = slowRedact
@@ -1,22 +0,0 @@
import { expectType, expectAssignable } from "tsd";
import slowRedact from ".";
import type { redactFn, redactFnNoSerialize } from ".";
// should return redactFn
expectType<redactFn>(slowRedact());
expectType<redactFn>(slowRedact({ paths: [] }));
expectType<redactFn>(slowRedact({ paths: ["some.path"] }));
expectType<redactFn>(slowRedact({ paths: [], censor: "[REDACTED]" }));
expectType<redactFn>(slowRedact({ paths: [], strict: true }));
expectType<redactFn>(slowRedact({ paths: [], serialize: JSON.stringify }));
expectType<redactFn>(slowRedact({ paths: [], serialize: true }));
expectType<redactFnNoSerialize>(slowRedact({ paths: [], serialize: false }));
expectType<redactFn>(slowRedact({ paths: [], remove: true }));
// should return string
expectType<string>(slowRedact()(""));
// should return string or T
expectAssignable<string | { someField: string }>(
slowRedact()({ someField: "someValue" })
);
@@ -1,37 +0,0 @@
{
"name": "@pinojs/redact",
"version": "0.4.0",
"description": "Redact JS objects",
"main": "index.js",
"types": "index.d.ts",
"scripts": {
"test": "node --test && npm run test:types",
"test:integration": "node --test test/integration.test.js",
"test:types": "tsd",
"test:all": "node --test test/*.test.js",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"bench": "node benchmarks/basic.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/pinojs/redact.git"
},
"keywords": [
"redact"
],
"author": "Matteo Collina <hello@matteocollina.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/pinojs/redact/issues"
},
"homepage": "https://github.com/pinojs/redact#readme",
"devDependencies": {
"eslint": "^9.36.0",
"fast-redact": "^3.5.0",
"mitata": "^1.0.34",
"neostandard": "^0.12.2",
"tsd": "^0.33.0",
"typescript": "^5.9.2"
}
}
@@ -1,20 +0,0 @@
import fs from 'node:fs'
import path from 'node:path'
const packageJsonPath = path.resolve(import.meta.dirname, '../package.json')
let { version } = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'))
let passedVersion = process.argv[2]
if (passedVersion) {
passedVersion = passedVersion.trim().replace(/^v/, '')
if (version !== passedVersion) {
console.log(`Syncing version from ${version} to ${passedVersion}`)
version = passedVersion
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'))
packageJson.version = version
fs.writeFileSync(path.resolve('./package.json'), JSON.stringify(packageJson, null, 2) + '\n', { encoding: 'utf-8' })
}
} else {
throw new Error('Version argument is required')
}
@@ -1,211 +0,0 @@
'use strict'
// Node.js test comparing @pinojs/redact vs fast-redact for multiple wildcard patterns
// This test validates that @pinojs/redact correctly handles 3+ consecutive wildcards
// matching the behavior of fast-redact
const { test } = require('node:test')
const { strict: assert } = require('node:assert')
const fastRedact = require('fast-redact')
const slowRedact = require('../index.js')
// Helper function to test redaction and track which values were censored
function testRedactDirect (library, pattern, testData = {}) {
const matches = []
const redactor = library === '@pinojs/redact' ? slowRedact : fastRedact
try {
const redact = redactor({
paths: [pattern],
censor: (value, path) => {
if (
value !== undefined &&
value !== null &&
typeof value === 'string' &&
value.includes('secret')
) {
matches.push({
value,
path: path ? path.join('.') : 'unknown'
})
}
return '[REDACTED]'
}
})
redact(JSON.parse(JSON.stringify(testData)))
return {
library,
pattern,
matches,
success: true,
testData
}
} catch (error) {
return {
library,
pattern,
matches: [],
success: false,
error: error.message,
testData
}
}
}
function testSlowRedactDirect (pattern, testData) {
return testRedactDirect('@pinojs/redact', pattern, testData)
}
function testFastRedactDirect (pattern, testData) {
return testRedactDirect('fast-redact', pattern, testData)
}
test('@pinojs/redact: *.password (2 levels)', () => {
const result = testSlowRedactDirect('*.password', {
simple: { password: 'secret-2-levels' }
})
assert.strictEqual(result.success, true)
assert.strictEqual(result.matches.length, 1)
assert.strictEqual(result.matches[0].value, 'secret-2-levels')
})
test('@pinojs/redact: *.*.password (3 levels)', () => {
const result = testSlowRedactDirect('*.*.password', {
simple: { password: 'secret-2-levels' },
user: { auth: { password: 'secret-3-levels' } }
})
assert.strictEqual(result.success, true)
assert.strictEqual(result.matches.length, 1)
assert.strictEqual(result.matches[0].value, 'secret-3-levels')
})
test('@pinojs/redact: *.*.*.password (4 levels)', () => {
const result = testSlowRedactDirect('*.*.*.password', {
simple: { password: 'secret-2-levels' },
user: { auth: { password: 'secret-3-levels' } },
nested: { deep: { auth: { password: 'secret-4-levels' } } }
})
assert.strictEqual(result.success, true)
assert.strictEqual(result.matches.length, 1)
assert.strictEqual(result.matches[0].value, 'secret-4-levels')
})
test('@pinojs/redact: *.*.*.*.password (5 levels)', () => {
const result = testSlowRedactDirect('*.*.*.*.password', {
simple: { password: 'secret-2-levels' },
user: { auth: { password: 'secret-3-levels' } },
nested: { deep: { auth: { password: 'secret-4-levels' } } },
config: {
user: { auth: { settings: { password: 'secret-5-levels' } } }
}
})
assert.strictEqual(result.success, true)
assert.strictEqual(result.matches.length, 1)
assert.strictEqual(result.matches[0].value, 'secret-5-levels')
})
test('@pinojs/redact: *.*.*.*.*.password (6 levels)', () => {
const result = testSlowRedactDirect('*.*.*.*.*.password', {
simple: { password: 'secret-2-levels' },
user: { auth: { password: 'secret-3-levels' } },
nested: { deep: { auth: { password: 'secret-4-levels' } } },
config: {
user: { auth: { settings: { password: 'secret-5-levels' } } }
},
data: {
reqConfig: {
data: {
credentials: {
settings: {
password: 'real-secret-6-levels'
}
}
}
}
}
})
assert.strictEqual(result.success, true)
assert.strictEqual(result.matches.length, 1)
assert.strictEqual(result.matches[0].value, 'real-secret-6-levels')
})
test('fast-redact: *.password (2 levels)', () => {
const result = testFastRedactDirect('*.password', {
simple: { password: 'secret-2-levels' }
})
assert.strictEqual(result.success, true)
assert.strictEqual(result.matches.length, 1)
assert.strictEqual(result.matches[0].value, 'secret-2-levels')
})
test('fast-redact: *.*.password (3 levels)', () => {
const result = testFastRedactDirect('*.*.password', {
simple: { password: 'secret-2-levels' },
user: { auth: { password: 'secret-3-levels' } }
})
assert.strictEqual(result.success, true)
assert.strictEqual(result.matches.length, 1)
assert.strictEqual(result.matches[0].value, 'secret-3-levels')
})
test('fast-redact: *.*.*.password (4 levels)', () => {
const result = testFastRedactDirect('*.*.*.password', {
simple: { password: 'secret-2-levels' },
user: { auth: { password: 'secret-3-levels' } },
nested: { deep: { auth: { password: 'secret-4-levels' } } }
})
assert.strictEqual(result.success, true)
assert.strictEqual(result.matches.length, 1)
assert.strictEqual(result.matches[0].value, 'secret-4-levels')
})
test('fast-redact: *.*.*.*.password (5 levels)', () => {
const result = testFastRedactDirect('*.*.*.*.password', {
simple: { password: 'secret-2-levels' },
user: { auth: { password: 'secret-3-levels' } },
nested: { deep: { auth: { password: 'secret-4-levels' } } },
config: {
user: { auth: { settings: { password: 'secret-5-levels' } } }
}
})
assert.strictEqual(result.success, true)
assert.strictEqual(result.matches.length, 1)
assert.strictEqual(result.matches[0].value, 'secret-5-levels')
})
test('fast-redact: *.*.*.*.*.password (6 levels)', () => {
const result = testFastRedactDirect('*.*.*.*.*.password', {
simple: { password: 'secret-2-levels' },
user: { auth: { password: 'secret-3-levels' } },
nested: { deep: { auth: { password: 'secret-4-levels' } } },
config: {
user: { auth: { settings: { password: 'secret-5-levels' } } }
},
data: {
reqConfig: {
data: {
credentials: {
settings: {
password: 'real-secret-6-levels'
}
}
}
}
}
})
assert.strictEqual(result.success, true)
assert.strictEqual(result.matches.length, 1)
assert.strictEqual(result.matches[0].value, 'real-secret-6-levels')
})
@@ -1,824 +0,0 @@
const { test } = require('node:test')
const { strict: assert } = require('node:assert')
const slowRedact = require('../index.js')
test('basic path redaction', () => {
const obj = {
headers: {
cookie: 'secret-cookie',
authorization: 'Bearer token'
},
body: { message: 'hello' }
}
const redact = slowRedact({ paths: ['headers.cookie'] })
const result = redact(obj)
// Original object should remain unchanged
assert.strictEqual(obj.headers.cookie, 'secret-cookie')
// Result should have redacted path
const parsed = JSON.parse(result)
assert.strictEqual(parsed.headers.cookie, '[REDACTED]')
assert.strictEqual(parsed.headers.authorization, 'Bearer token')
assert.strictEqual(parsed.body.message, 'hello')
})
test('multiple paths redaction', () => {
const obj = {
user: { name: 'john', password: 'secret' },
session: { token: 'abc123' }
}
const redact = slowRedact({
paths: ['user.password', 'session.token']
})
const result = redact(obj)
// Original unchanged
assert.strictEqual(obj.user.password, 'secret')
assert.strictEqual(obj.session.token, 'abc123')
// Result redacted
const parsed = JSON.parse(result)
assert.strictEqual(parsed.user.password, '[REDACTED]')
assert.strictEqual(parsed.session.token, '[REDACTED]')
assert.strictEqual(parsed.user.name, 'john')
})
test('custom censor value', () => {
const obj = { secret: 'hidden' }
const redact = slowRedact({
paths: ['secret'],
censor: '***'
})
const result = redact(obj)
const parsed = JSON.parse(result)
assert.strictEqual(parsed.secret, '***')
})
test('serialize: false returns object with restore method', () => {
const obj = { secret: 'hidden' }
const redact = slowRedact({
paths: ['secret'],
serialize: false
})
const result = redact(obj)
// Should be object, not string
assert.strictEqual(typeof result, 'object')
assert.strictEqual(result.secret, '[REDACTED]')
// Should have restore method
assert.strictEqual(typeof result.restore, 'function')
const restored = result.restore()
assert.strictEqual(restored.secret, 'hidden')
})
test('bracket notation paths', () => {
const obj = {
'weird-key': { 'another-weird': 'secret' },
normal: 'public'
}
const redact = slowRedact({
paths: ['["weird-key"]["another-weird"]']
})
const result = redact(obj)
const parsed = JSON.parse(result)
assert.strictEqual(parsed['weird-key']['another-weird'], '[REDACTED]')
assert.strictEqual(parsed.normal, 'public')
})
test('array paths', () => {
const obj = {
users: [
{ name: 'john', password: 'secret1' },
{ name: 'jane', password: 'secret2' }
]
}
const redact = slowRedact({
paths: ['users[0].password', 'users[1].password']
})
const result = redact(obj)
const parsed = JSON.parse(result)
assert.strictEqual(parsed.users[0].password, '[REDACTED]')
assert.strictEqual(parsed.users[1].password, '[REDACTED]')
assert.strictEqual(parsed.users[0].name, 'john')
assert.strictEqual(parsed.users[1].name, 'jane')
})
test('wildcard at end of path', () => {
const obj = {
secrets: {
key1: 'secret1',
key2: 'secret2'
},
public: 'data'
}
const redact = slowRedact({
paths: ['secrets.*']
})
const result = redact(obj)
const parsed = JSON.parse(result)
assert.strictEqual(parsed.secrets.key1, '[REDACTED]')
assert.strictEqual(parsed.secrets.key2, '[REDACTED]')
assert.strictEqual(parsed.public, 'data')
})
test('wildcard with arrays', () => {
const obj = {
items: ['secret1', 'secret2', 'secret3']
}
const redact = slowRedact({
paths: ['items.*']
})
const result = redact(obj)
const parsed = JSON.parse(result)
assert.strictEqual(parsed.items[0], '[REDACTED]')
assert.strictEqual(parsed.items[1], '[REDACTED]')
assert.strictEqual(parsed.items[2], '[REDACTED]')
})
test('intermediate wildcard', () => {
const obj = {
users: {
user1: { password: 'secret1' },
user2: { password: 'secret2' }
}
}
const redact = slowRedact({
paths: ['users.*.password']
})
const result = redact(obj)
const parsed = JSON.parse(result)
assert.strictEqual(parsed.users.user1.password, '[REDACTED]')
assert.strictEqual(parsed.users.user2.password, '[REDACTED]')
})
test('censor function', () => {
const obj = { secret: 'hidden' }
const redact = slowRedact({
paths: ['secret'],
censor: (value, path) => `REDACTED:${path.join('.')}`
})
const result = redact(obj)
const parsed = JSON.parse(result)
assert.strictEqual(parsed.secret, 'REDACTED:secret')
})
test('custom serialize function', () => {
const obj = { secret: 'hidden', public: 'data' }
const redact = slowRedact({
paths: ['secret'],
serialize: (obj) => `custom:${JSON.stringify(obj)}`
})
const result = redact(obj)
assert(result.startsWith('custom:'))
const parsed = JSON.parse(result.slice(7))
assert.strictEqual(parsed.secret, '[REDACTED]')
assert.strictEqual(parsed.public, 'data')
})
test('nested paths', () => {
const obj = {
level1: {
level2: {
level3: {
secret: 'hidden'
}
}
}
}
const redact = slowRedact({
paths: ['level1.level2.level3.secret']
})
const result = redact(obj)
const parsed = JSON.parse(result)
assert.strictEqual(parsed.level1.level2.level3.secret, '[REDACTED]')
})
test('non-existent paths are ignored', () => {
const obj = { existing: 'value' }
const redact = slowRedact({
paths: ['nonexistent.path']
})
const result = redact(obj)
const parsed = JSON.parse(result)
assert.strictEqual(parsed.existing, 'value')
assert.strictEqual(parsed.nonexistent, undefined)
})
test('null and undefined handling', () => {
const obj = {
nullValue: null,
undefinedValue: undefined,
nested: {
nullValue: null
}
}
const redact = slowRedact({
paths: ['nullValue', 'nested.nullValue']
})
const result = redact(obj)
const parsed = JSON.parse(result)
assert.strictEqual(parsed.nullValue, '[REDACTED]')
assert.strictEqual(parsed.nested.nullValue, '[REDACTED]')
})
test('original object remains unchanged', () => {
const original = {
secret: 'hidden',
nested: { secret: 'hidden2' }
}
const copy = JSON.parse(JSON.stringify(original))
const redact = slowRedact({
paths: ['secret', 'nested.secret']
})
redact(original)
// Original should be completely unchanged
assert.deepStrictEqual(original, copy)
})
test('strict mode with primitives', () => {
const redact = slowRedact({
paths: ['test'],
strict: true
})
const stringResult = redact('primitive')
assert.strictEqual(stringResult, '"primitive"')
const numberResult = redact(42)
assert.strictEqual(numberResult, '42')
})
// Path validation tests to match fast-redact behavior
test('path validation - non-string paths should throw', () => {
assert.throws(() => {
slowRedact({ paths: [123] })
}, {
message: 'Paths must be (non-empty) strings'
})
assert.throws(() => {
slowRedact({ paths: [null] })
}, {
message: 'Paths must be (non-empty) strings'
})
assert.throws(() => {
slowRedact({ paths: [undefined] })
}, {
message: 'Paths must be (non-empty) strings'
})
})
test('path validation - empty string should throw', () => {
assert.throws(() => {
slowRedact({ paths: [''] })
}, {
message: 'Invalid redaction path ()'
})
})
test('path validation - double dots should throw', () => {
assert.throws(() => {
slowRedact({ paths: ['invalid..path'] })
}, {
message: 'Invalid redaction path (invalid..path)'
})
assert.throws(() => {
slowRedact({ paths: ['a..b..c'] })
}, {
message: 'Invalid redaction path (a..b..c)'
})
})
test('path validation - unmatched brackets should throw', () => {
assert.throws(() => {
slowRedact({ paths: ['invalid[unclosed'] })
}, {
message: 'Invalid redaction path (invalid[unclosed)'
})
assert.throws(() => {
slowRedact({ paths: ['invalid]unopened'] })
}, {
message: 'Invalid redaction path (invalid]unopened)'
})
assert.throws(() => {
slowRedact({ paths: ['nested[a[b]'] })
}, {
message: 'Invalid redaction path (nested[a[b])'
})
})
test('path validation - comma-separated paths should throw', () => {
assert.throws(() => {
slowRedact({ paths: ['req,headers.cookie'] })
}, {
message: 'Invalid redaction path (req,headers.cookie)'
})
assert.throws(() => {
slowRedact({ paths: ['user,profile,name'] })
}, {
message: 'Invalid redaction path (user,profile,name)'
})
assert.throws(() => {
slowRedact({ paths: ['a,b'] })
}, {
message: 'Invalid redaction path (a,b)'
})
})
test('path validation - mixed valid and invalid should throw', () => {
assert.throws(() => {
slowRedact({ paths: ['valid.path', 123, 'another.valid'] })
}, {
message: 'Paths must be (non-empty) strings'
})
assert.throws(() => {
slowRedact({ paths: ['valid.path', 'invalid..path'] })
}, {
message: 'Invalid redaction path (invalid..path)'
})
assert.throws(() => {
slowRedact({ paths: ['valid.path', 'req,headers.cookie'] })
}, {
message: 'Invalid redaction path (req,headers.cookie)'
})
})
test('path validation - valid paths should work', () => {
// These should not throw
assert.doesNotThrow(() => {
slowRedact({ paths: [] })
})
assert.doesNotThrow(() => {
slowRedact({ paths: ['valid.path'] })
})
assert.doesNotThrow(() => {
slowRedact({ paths: ['user.password', 'data[0].secret'] })
})
assert.doesNotThrow(() => {
slowRedact({ paths: ['["quoted-key"].value'] })
})
assert.doesNotThrow(() => {
slowRedact({ paths: ["['single-quoted'].value"] })
})
assert.doesNotThrow(() => {
slowRedact({ paths: ['array[0]', 'object.property', 'wildcard.*'] })
})
})
// fast-redact compatibility tests
test('censor function receives path as array (fast-redact compatibility)', () => {
const obj = {
headers: {
authorization: 'Bearer token',
'x-api-key': 'secret-key'
}
}
const pathsReceived = []
const redact = slowRedact({
paths: ['headers.authorization', 'headers["x-api-key"]'],
censor: (value, path) => {
pathsReceived.push(path)
assert(Array.isArray(path), 'Path should be an array')
return '[REDACTED]'
}
})
redact(obj)
// Verify paths are arrays
assert.strictEqual(pathsReceived.length, 2)
assert.deepStrictEqual(pathsReceived[0], ['headers', 'authorization'])
assert.deepStrictEqual(pathsReceived[1], ['headers', 'x-api-key'])
})
test('censor function with nested paths receives correct array', () => {
const obj = {
user: {
profile: {
credentials: {
password: 'secret123'
}
}
}
}
let receivedPath
const redact = slowRedact({
paths: ['user.profile.credentials.password'],
censor: (value, path) => {
receivedPath = path
assert.strictEqual(value, 'secret123')
assert(Array.isArray(path))
return '[REDACTED]'
}
})
redact(obj)
assert.deepStrictEqual(receivedPath, ['user', 'profile', 'credentials', 'password'])
})
test('censor function with wildcards receives correct array paths', () => {
const obj = {
users: {
user1: { password: 'secret1' },
user2: { password: 'secret2' }
}
}
const pathsReceived = []
const redact = slowRedact({
paths: ['users.*.password'],
censor: (value, path) => {
pathsReceived.push([...path]) // copy the array
assert(Array.isArray(path))
return '[REDACTED]'
}
})
redact(obj)
assert.strictEqual(pathsReceived.length, 2)
assert.deepStrictEqual(pathsReceived[0], ['users', 'user1', 'password'])
assert.deepStrictEqual(pathsReceived[1], ['users', 'user2', 'password'])
})
test('censor function with array wildcard receives correct array paths', () => {
const obj = {
items: [
{ secret: 'value1' },
{ secret: 'value2' }
]
}
const pathsReceived = []
const redact = slowRedact({
paths: ['items.*.secret'],
censor: (value, path) => {
pathsReceived.push([...path])
assert(Array.isArray(path))
return '[REDACTED]'
}
})
redact(obj)
assert.strictEqual(pathsReceived.length, 2)
assert.deepStrictEqual(pathsReceived[0], ['items', '0', 'secret'])
assert.deepStrictEqual(pathsReceived[1], ['items', '1', 'secret'])
})
test('censor function with end wildcard receives correct array paths', () => {
const obj = {
secrets: {
key1: 'secret1',
key2: 'secret2'
}
}
const pathsReceived = []
const redact = slowRedact({
paths: ['secrets.*'],
censor: (value, path) => {
pathsReceived.push([...path])
assert(Array.isArray(path))
return '[REDACTED]'
}
})
redact(obj)
assert.strictEqual(pathsReceived.length, 2)
// Sort paths for consistent testing since object iteration order isn't guaranteed
pathsReceived.sort((a, b) => a[1].localeCompare(b[1]))
assert.deepStrictEqual(pathsReceived[0], ['secrets', 'key1'])
assert.deepStrictEqual(pathsReceived[1], ['secrets', 'key2'])
})
test('type safety: accessing properties on primitive values should not throw', () => {
// Test case from GitHub issue #5
const redactor = slowRedact({ paths: ['headers.authorization'] })
const data = {
headers: 123 // primitive value
}
assert.doesNotThrow(() => {
const result = redactor(data)
const parsed = JSON.parse(result)
assert.strictEqual(parsed.headers, 123) // Should remain unchanged
})
// Test wildcards with primitives
const redactor2 = slowRedact({ paths: ['data.*.nested'] })
const data2 = {
data: {
item1: 123, // primitive, trying to access .nested on it
item2: { nested: 'secret' }
}
}
assert.doesNotThrow(() => {
const result2 = redactor2(data2)
const parsed2 = JSON.parse(result2)
assert.strictEqual(parsed2.data.item1, 123) // Primitive unchanged
assert.strictEqual(parsed2.data.item2.nested, '[REDACTED]') // Object property redacted
})
// Test deep nested access on primitives
const redactor3 = slowRedact({ paths: ['user.name.first.charAt'] })
const data3 = {
user: {
name: 'John' // string primitive
}
}
assert.doesNotThrow(() => {
const result3 = redactor3(data3)
const parsed3 = JSON.parse(result3)
assert.strictEqual(parsed3.user.name, 'John') // Should remain unchanged
})
})
// Remove option tests
test('remove option: basic key removal', () => {
const obj = { username: 'john', password: 'secret123' }
const redact = slowRedact({ paths: ['password'], remove: true })
const result = redact(obj)
// Original object should remain unchanged
assert.strictEqual(obj.password, 'secret123')
// Result should have password completely removed
const parsed = JSON.parse(result)
assert.strictEqual(parsed.username, 'john')
assert.strictEqual('password' in parsed, false)
assert.strictEqual(parsed.password, undefined)
})
test('remove option: multiple paths removal', () => {
const obj = {
user: { name: 'john', password: 'secret' },
session: { token: 'abc123', id: 'session1' }
}
const redact = slowRedact({
paths: ['user.password', 'session.token'],
remove: true
})
const result = redact(obj)
// Original unchanged
assert.strictEqual(obj.user.password, 'secret')
assert.strictEqual(obj.session.token, 'abc123')
// Result has keys completely removed
const parsed = JSON.parse(result)
assert.strictEqual(parsed.user.name, 'john')
assert.strictEqual(parsed.session.id, 'session1')
assert.strictEqual('password' in parsed.user, false)
assert.strictEqual('token' in parsed.session, false)
})
test('remove option: wildcard removal', () => {
const obj = {
secrets: {
key1: 'secret1',
key2: 'secret2'
},
public: 'data'
}
const redact = slowRedact({
paths: ['secrets.*'],
remove: true
})
const result = redact(obj)
const parsed = JSON.parse(result)
assert.strictEqual(parsed.public, 'data')
assert.deepStrictEqual(parsed.secrets, {}) // All keys removed
})
test('remove option: array wildcard removal', () => {
const obj = {
items: ['secret1', 'secret2', 'secret3'],
meta: 'data'
}
const redact = slowRedact({
paths: ['items.*'],
remove: true
})
const result = redact(obj)
const parsed = JSON.parse(result)
assert.strictEqual(parsed.meta, 'data')
// Array items set to undefined are omitted by JSON.stringify
assert.deepStrictEqual(parsed.items, [null, null, null])
})
test('remove option: intermediate wildcard removal', () => {
const obj = {
users: {
user1: { password: 'secret1', name: 'john' },
user2: { password: 'secret2', name: 'jane' }
}
}
const redact = slowRedact({
paths: ['users.*.password'],
remove: true
})
const result = redact(obj)
const parsed = JSON.parse(result)
assert.strictEqual(parsed.users.user1.name, 'john')
assert.strictEqual(parsed.users.user2.name, 'jane')
assert.strictEqual('password' in parsed.users.user1, false)
assert.strictEqual('password' in parsed.users.user2, false)
})
test('remove option: serialize false returns object with removed keys', () => {
const obj = { secret: 'hidden', public: 'data' }
const redact = slowRedact({
paths: ['secret'],
remove: true,
serialize: false
})
const result = redact(obj)
// Should be object, not string
assert.strictEqual(typeof result, 'object')
assert.strictEqual(result.public, 'data')
assert.strictEqual('secret' in result, false)
// Should have restore method
assert.strictEqual(typeof result.restore, 'function')
const restored = result.restore()
assert.strictEqual(restored.secret, 'hidden')
})
test('remove option: non-existent paths are ignored', () => {
const obj = { existing: 'value' }
const redact = slowRedact({
paths: ['nonexistent.path'],
remove: true
})
const result = redact(obj)
const parsed = JSON.parse(result)
assert.strictEqual(parsed.existing, 'value')
assert.strictEqual(parsed.nonexistent, undefined)
})
// Test for Issue #13: Empty string bracket notation paths not being redacted correctly
test('empty string bracket notation path', () => {
const obj = { '': { c: 'sensitive-data' } }
const redact = slowRedact({ paths: ["[''].c"] })
const result = redact(obj)
// Original object should remain unchanged
assert.strictEqual(obj[''].c, 'sensitive-data')
// Result should have redacted path
const parsed = JSON.parse(result)
assert.strictEqual(parsed[''].c, '[REDACTED]')
})
test('empty string bracket notation with double quotes', () => {
const obj = { '': { c: 'sensitive-data' } }
const redact = slowRedact({ paths: ['[""].c'] })
const result = redact(obj)
// Original object should remain unchanged
assert.strictEqual(obj[''].c, 'sensitive-data')
// Result should have redacted path
const parsed = JSON.parse(result)
assert.strictEqual(parsed[''].c, '[REDACTED]')
})
test('empty string key with nested bracket notation', () => {
const obj = { '': { '': { secret: 'value' } } }
const redact = slowRedact({ paths: ["[''][''].secret"] })
const result = redact(obj)
// Original object should remain unchanged
assert.strictEqual(obj[''][''].secret, 'value')
// Result should have redacted path
const parsed = JSON.parse(result)
assert.strictEqual(parsed[''][''].secret, '[REDACTED]')
})
// Test for Pino issue #2313: censor should only be called when path exists
test('censor function not called for non-existent paths', () => {
let censorCallCount = 0
const censorCalls = []
const redact = slowRedact({
paths: ['a.b.c', 'req.authorization', 'url'],
serialize: false,
censor (value, path) {
censorCallCount++
censorCalls.push({ value, path: path.slice() })
return '***'
}
})
// Test case 1: { req: { id: 'test' } }
// req.authorization doesn't exist, censor should not be called for it
censorCallCount = 0
censorCalls.length = 0
redact({ req: { id: 'test' } })
// Should not have been called for any path since none exist
assert.strictEqual(censorCallCount, 0, 'censor should not be called when paths do not exist')
// Test case 2: { a: { d: 'test' } }
// a.b.c doesn't exist (a.d exists, but not a.b.c)
censorCallCount = 0
redact({ a: { d: 'test' } })
assert.strictEqual(censorCallCount, 0)
// Test case 3: paths that do exist should still call censor
censorCallCount = 0
censorCalls.length = 0
const result = redact({ req: { authorization: 'bearer token' } })
assert.strictEqual(censorCallCount, 1, 'censor should be called when path exists')
assert.deepStrictEqual(censorCalls[0].path, ['req', 'authorization'])
assert.strictEqual(censorCalls[0].value, 'bearer token')
assert.strictEqual(result.req.authorization, '***')
})
test('censor function not called for non-existent nested paths', () => {
let censorCallCount = 0
const redact = slowRedact({
paths: ['headers.authorization'],
serialize: false,
censor (value, path) {
censorCallCount++
return '[REDACTED]'
}
})
// headers exists but authorization doesn't
censorCallCount = 0
const result1 = redact({ headers: { 'content-type': 'application/json' } })
assert.strictEqual(censorCallCount, 0)
assert.deepStrictEqual(result1.headers, { 'content-type': 'application/json' })
// headers doesn't exist at all
censorCallCount = 0
const result2 = redact({ body: 'data' })
assert.strictEqual(censorCallCount, 0)
assert.strictEqual(result2.body, 'data')
assert.strictEqual(typeof result2.restore, 'function')
// headers.authorization exists - should call censor
censorCallCount = 0
const result3 = redact({ headers: { authorization: 'Bearer token' } })
assert.strictEqual(censorCallCount, 1)
assert.strictEqual(result3.headers.authorization, '[REDACTED]')
})
@@ -1,390 +0,0 @@
const { test } = require('node:test')
const { strict: assert } = require('node:assert')
const slowRedact = require('../index.js')
const fastRedact = require('fast-redact')
test('integration: basic path redaction matches fast-redact', () => {
const obj = {
headers: {
cookie: 'secret-cookie',
authorization: 'Bearer token'
},
body: { message: 'hello' }
}
const slowResult = slowRedact({ paths: ['headers.cookie'] })(obj)
const fastResult = fastRedact({ paths: ['headers.cookie'] })(obj)
assert.strictEqual(slowResult, fastResult)
})
test('integration: multiple paths match fast-redact', () => {
const obj = {
user: { name: 'john', password: 'secret' },
session: { token: 'abc123' }
}
const paths = ['user.password', 'session.token']
const slowResult = slowRedact({ paths })(obj)
const fastResult = fastRedact({ paths })(obj)
assert.strictEqual(slowResult, fastResult)
})
test('integration: custom censor value matches fast-redact', () => {
const obj = { secret: 'hidden' }
const options = { paths: ['secret'], censor: '***' }
const slowResult = slowRedact(options)(obj)
const fastResult = fastRedact(options)(obj)
assert.strictEqual(slowResult, fastResult)
})
test('integration: bracket notation matches fast-redact', () => {
const obj = {
'weird-key': { 'another-weird': 'secret' },
normal: 'public'
}
const options = { paths: ['["weird-key"]["another-weird"]'] }
const slowResult = slowRedact(options)(obj)
const fastResult = fastRedact(options)(obj)
assert.strictEqual(slowResult, fastResult)
})
test('integration: array paths match fast-redact', () => {
const obj = {
users: [
{ name: 'john', password: 'secret1' },
{ name: 'jane', password: 'secret2' }
]
}
const options = { paths: ['users[0].password', 'users[1].password'] }
const slowResult = slowRedact(options)(obj)
const fastResult = fastRedact(options)(obj)
assert.strictEqual(slowResult, fastResult)
})
test('integration: wildcard at end matches fast-redact', () => {
const obj = {
secrets: {
key1: 'secret1',
key2: 'secret2'
},
public: 'data'
}
const options = { paths: ['secrets.*'] }
const slowResult = slowRedact(options)(obj)
const fastResult = fastRedact(options)(obj)
assert.strictEqual(slowResult, fastResult)
})
test('integration: wildcard with arrays matches fast-redact', () => {
const obj = {
items: ['secret1', 'secret2', 'secret3']
}
const options = { paths: ['items.*'] }
const slowResult = slowRedact(options)(obj)
const fastResult = fastRedact(options)(obj)
assert.strictEqual(slowResult, fastResult)
})
test('integration: intermediate wildcard matches fast-redact', () => {
const obj = {
users: {
user1: { password: 'secret1' },
user2: { password: 'secret2' }
}
}
const options = { paths: ['users.*.password'] }
const slowResult = slowRedact(options)(obj)
const fastResult = fastRedact(options)(obj)
assert.strictEqual(slowResult, fastResult)
})
test('integration: custom serialize function matches fast-redact', () => {
const obj = { secret: 'hidden', public: 'data' }
const options = {
paths: ['secret'],
serialize: (obj) => `custom:${JSON.stringify(obj)}`
}
const slowResult = slowRedact(options)(obj)
const fastResult = fastRedact(options)(obj)
assert.strictEqual(slowResult, fastResult)
})
test('integration: nested paths match fast-redact', () => {
const obj = {
level1: {
level2: {
level3: {
secret: 'hidden'
}
}
}
}
const options = { paths: ['level1.level2.level3.secret'] }
const slowResult = slowRedact(options)(obj)
const fastResult = fastRedact(options)(obj)
assert.strictEqual(slowResult, fastResult)
})
test('integration: non-existent paths match fast-redact', () => {
const obj = { existing: 'value' }
const options = { paths: ['nonexistent.path'] }
const slowResult = slowRedact(options)(obj)
const fastResult = fastRedact(options)(obj)
assert.strictEqual(slowResult, fastResult)
})
test('integration: null and undefined handling - legitimate difference', () => {
const obj = {
nullValue: null,
undefinedValue: undefined,
nested: {
nullValue: null
}
}
const options = { paths: ['nullValue', 'nested.nullValue'] }
const slowResult = slowRedact(options)(obj)
const fastResult = fastRedact(options)(obj)
// This is a legitimate behavioral difference:
// @pinojs/redact redacts null values, fast-redact doesn't
const slowParsed = JSON.parse(slowResult)
const fastParsed = JSON.parse(fastResult)
// @pinojs/redact redacts nulls
assert.strictEqual(slowParsed.nullValue, '[REDACTED]')
assert.strictEqual(slowParsed.nested.nullValue, '[REDACTED]')
// fast-redact preserves nulls
assert.strictEqual(fastParsed.nullValue, null)
assert.strictEqual(fastParsed.nested.nullValue, null)
})
test('integration: strict mode with primitives - different error handling', () => {
const options = { paths: ['test'], strict: true }
const slowRedactFn = slowRedact(options)
const fastRedactFn = fastRedact(options)
// @pinojs/redact handles primitives gracefully
const stringSlowResult = slowRedactFn('primitive')
assert.strictEqual(stringSlowResult, '"primitive"')
const numberSlowResult = slowRedactFn(42)
assert.strictEqual(numberSlowResult, '42')
// fast-redact throws an error for primitives in strict mode
assert.throws(() => {
fastRedactFn('primitive')
}, /primitives cannot be redacted/)
assert.throws(() => {
fastRedactFn(42)
}, /primitives cannot be redacted/)
})
test('integration: serialize false behavior difference', () => {
const slowObj = { secret: 'hidden' }
const fastObj = { secret: 'hidden' }
const options = { paths: ['secret'], serialize: false }
const slowResult = slowRedact(options)(slowObj)
const fastResult = fastRedact(options)(fastObj)
// Both should redact the secret
assert.strictEqual(slowResult.secret, '[REDACTED]')
assert.strictEqual(fastResult.secret, '[REDACTED]')
// @pinojs/redact always has restore method
assert.strictEqual(typeof slowResult.restore, 'function')
// @pinojs/redact should restore to original value
assert.strictEqual(slowResult.restore().secret, 'hidden')
// Key difference: original object state
// fast-redact mutates the original, @pinojs/redact doesn't
assert.strictEqual(slowObj.secret, 'hidden') // @pinojs/redact preserves original
assert.strictEqual(fastObj.secret, '[REDACTED]') // fast-redact mutates original
})
test('integration: censor function behavior', () => {
const obj = { secret: 'hidden' }
const options = {
paths: ['secret'],
censor: (value, path) => `REDACTED:${path}`
}
const slowResult = slowRedact(options)(obj)
const fastResult = fastRedact(options)(obj)
assert.strictEqual(slowResult, fastResult)
})
test('integration: complex object with mixed patterns', () => {
const obj = {
users: [
{
id: 1,
name: 'john',
credentials: { password: 'secret1', apiKey: 'key1' }
},
{
id: 2,
name: 'jane',
credentials: { password: 'secret2', apiKey: 'key2' }
}
],
config: {
database: { password: 'db-secret' },
api: { keys: ['key1', 'key2', 'key3'] }
}
}
const options = {
paths: [
'users.*.credentials.password',
'users.*.credentials.apiKey',
'config.database.password',
'config.api.keys.*'
]
}
const slowResult = slowRedact(options)(obj)
const fastResult = fastRedact(options)(obj)
assert.strictEqual(slowResult, fastResult)
})
// Remove option integration tests - comparing with fast-redact
test('integration: remove option basic comparison with fast-redact', () => {
const obj = { username: 'john', password: 'secret123' }
const options = { paths: ['password'], remove: true }
const slowResult = slowRedact(options)(obj)
const fastResult = fastRedact(options)(obj)
assert.strictEqual(slowResult, fastResult)
// Verify the key is actually removed
const parsed = JSON.parse(slowResult)
assert.strictEqual(parsed.username, 'john')
assert.strictEqual('password' in parsed, false)
})
test('integration: remove option multiple paths comparison with fast-redact', () => {
const obj = {
user: { name: 'john', password: 'secret' },
session: { token: 'abc123', id: 'session1' }
}
const options = {
paths: ['user.password', 'session.token'],
remove: true
}
const slowResult = slowRedact(options)(obj)
const fastResult = fastRedact(options)(obj)
assert.strictEqual(slowResult, fastResult)
})
test('integration: remove option wildcard comparison with fast-redact', () => {
const obj = {
secrets: {
key1: 'secret1',
key2: 'secret2'
},
public: 'data'
}
const options = {
paths: ['secrets.*'],
remove: true
}
const slowResult = slowRedact(options)(obj)
const fastResult = fastRedact(options)(obj)
assert.strictEqual(slowResult, fastResult)
})
test('integration: remove option intermediate wildcard comparison with fast-redact', () => {
const obj = {
users: {
user1: { password: 'secret1', name: 'john' },
user2: { password: 'secret2', name: 'jane' }
}
}
const options = {
paths: ['users.*.password'],
remove: true
}
const slowResult = slowRedact(options)(obj)
const fastResult = fastRedact(options)(obj)
assert.strictEqual(slowResult, fastResult)
})
test('integration: remove option with custom censor comparison with fast-redact', () => {
const obj = { secret: 'hidden', public: 'data' }
const options = {
paths: ['secret'],
censor: '***',
remove: true
}
const slowResult = slowRedact(options)(obj)
const fastResult = fastRedact(options)(obj)
assert.strictEqual(slowResult, fastResult)
// With remove: true, censor value should be ignored
const parsed = JSON.parse(slowResult)
assert.strictEqual('secret' in parsed, false)
assert.strictEqual(parsed.public, 'data')
})
test('integration: remove option serialize false behavior - @pinojs/redact only', () => {
// fast-redact doesn't support remove option with serialize: false
// so we test @pinojs/redact's behavior only
const obj = { secret: 'hidden', public: 'data' }
const options = { paths: ['secret'], remove: true, serialize: false }
const result = slowRedact(options)(obj)
// Should have the key removed
assert.strictEqual('secret' in result, false)
assert.strictEqual(result.public, 'data')
// Should have restore method
assert.strictEqual(typeof result.restore, 'function')
// Original object should be preserved
assert.strictEqual(obj.secret, 'hidden')
// Restore should bring back the removed key
const restored = result.restore()
assert.strictEqual(restored.secret, 'hidden')
})
@@ -1,227 +0,0 @@
'use strict'
const { test } = require('node:test')
const { strict: assert } = require('node:assert')
const slowRedact = require('../index.js')
// Tests for Issue #2319: @pinojs/redact fails to redact patterns with 3+ consecutive wildcards
test('three consecutive wildcards: *.*.*.password (4 levels deep)', () => {
const obj = {
simple: { password: 'secret-2-levels' },
user: { auth: { password: 'secret-3-levels' } },
nested: { deep: { auth: { password: 'secret-4-levels' } } }
}
const redact = slowRedact({
paths: ['*.*.*.password']
})
const result = redact(obj)
const parsed = JSON.parse(result)
// Only the 4-level deep password should be redacted
assert.strictEqual(parsed.simple.password, 'secret-2-levels', '2-level password should NOT be redacted')
assert.strictEqual(parsed.user.auth.password, 'secret-3-levels', '3-level password should NOT be redacted')
assert.strictEqual(parsed.nested.deep.auth.password, '[REDACTED]', '4-level password SHOULD be redacted')
})
test('four consecutive wildcards: *.*.*.*.password (5 levels deep)', () => {
const obj = {
simple: { password: 'secret-2-levels' },
user: { auth: { password: 'secret-3-levels' } },
nested: { deep: { auth: { password: 'secret-4-levels' } } },
config: { user: { auth: { settings: { password: 'secret-5-levels' } } } }
}
const redact = slowRedact({
paths: ['*.*.*.*.password']
})
const result = redact(obj)
const parsed = JSON.parse(result)
// Only the 5-level deep password should be redacted
assert.strictEqual(parsed.simple.password, 'secret-2-levels', '2-level password should NOT be redacted')
assert.strictEqual(parsed.user.auth.password, 'secret-3-levels', '3-level password should NOT be redacted')
assert.strictEqual(parsed.nested.deep.auth.password, 'secret-4-levels', '4-level password should NOT be redacted')
assert.strictEqual(parsed.config.user.auth.settings.password, '[REDACTED]', '5-level password SHOULD be redacted')
})
test('five consecutive wildcards: *.*.*.*.*.password (6 levels deep)', () => {
const obj = {
simple: { password: 'secret-2-levels' },
user: { auth: { password: 'secret-3-levels' } },
nested: { deep: { auth: { password: 'secret-4-levels' } } },
config: { user: { auth: { settings: { password: 'secret-5-levels' } } } },
data: {
reqConfig: {
data: {
credentials: {
settings: {
password: 'secret-6-levels'
}
}
}
}
}
}
const redact = slowRedact({
paths: ['*.*.*.*.*.password']
})
const result = redact(obj)
const parsed = JSON.parse(result)
// Only the 6-level deep password should be redacted
assert.strictEqual(parsed.simple.password, 'secret-2-levels', '2-level password should NOT be redacted')
assert.strictEqual(parsed.user.auth.password, 'secret-3-levels', '3-level password should NOT be redacted')
assert.strictEqual(parsed.nested.deep.auth.password, 'secret-4-levels', '4-level password should NOT be redacted')
assert.strictEqual(parsed.config.user.auth.settings.password, 'secret-5-levels', '5-level password should NOT be redacted')
assert.strictEqual(parsed.data.reqConfig.data.credentials.settings.password, '[REDACTED]', '6-level password SHOULD be redacted')
})
test('three wildcards with censor function receives correct values', () => {
const obj = {
nested: { deep: { auth: { password: 'secret-value' } } }
}
const censorCalls = []
const redact = slowRedact({
paths: ['*.*.*.password'],
censor: (value, path) => {
censorCalls.push({ value, path: [...path] })
return '[REDACTED]'
}
})
const result = redact(obj)
const parsed = JSON.parse(result)
// Should have been called exactly once with the correct value
assert.strictEqual(censorCalls.length, 1, 'censor should be called once')
assert.strictEqual(censorCalls[0].value, 'secret-value', 'censor should receive the actual value')
assert.deepStrictEqual(censorCalls[0].path, ['nested', 'deep', 'auth', 'password'], 'censor should receive correct path')
assert.strictEqual(parsed.nested.deep.auth.password, '[REDACTED]')
})
test('three wildcards with multiple matches', () => {
const obj = {
api1: { v1: { auth: { token: 'token1' } } },
api2: { v2: { auth: { token: 'token2' } } },
api3: { v1: { auth: { token: 'token3' } } }
}
const redact = slowRedact({
paths: ['*.*.*.token']
})
const result = redact(obj)
const parsed = JSON.parse(result)
// All three tokens should be redacted
assert.strictEqual(parsed.api1.v1.auth.token, '[REDACTED]')
assert.strictEqual(parsed.api2.v2.auth.token, '[REDACTED]')
assert.strictEqual(parsed.api3.v1.auth.token, '[REDACTED]')
})
test('three wildcards with remove option', () => {
const obj = {
nested: { deep: { auth: { password: 'secret', username: 'admin' } } }
}
const redact = slowRedact({
paths: ['*.*.*.password'],
remove: true
})
const result = redact(obj)
const parsed = JSON.parse(result)
// Password should be removed entirely
assert.strictEqual('password' in parsed.nested.deep.auth, false, 'password key should be removed')
assert.strictEqual(parsed.nested.deep.auth.username, 'admin', 'username should remain')
})
test('mixed: two and three wildcards in same redactor', () => {
const obj = {
user: { auth: { password: 'secret-3-levels' } },
config: { deep: { auth: { password: 'secret-4-levels' } } }
}
const redact = slowRedact({
paths: ['*.*.password', '*.*.*.password']
})
const result = redact(obj)
const parsed = JSON.parse(result)
// Both should be redacted
assert.strictEqual(parsed.user.auth.password, '[REDACTED]', '3-level should be redacted by *.*.password')
assert.strictEqual(parsed.config.deep.auth.password, '[REDACTED]', '4-level should be redacted by *.*.*.password')
})
test('three wildcards should not call censor for non-existent paths', () => {
const obj = {
shallow: { data: 'value' },
nested: { deep: { auth: { password: 'secret' } } }
}
let censorCallCount = 0
const redact = slowRedact({
paths: ['*.*.*.password'],
censor: (value, path) => {
censorCallCount++
return '[REDACTED]'
}
})
redact(obj)
// Should only be called once for the path that exists
assert.strictEqual(censorCallCount, 1, 'censor should only be called for existing paths')
})
test('three wildcards with arrays', () => {
const obj = {
users: [
{ auth: { password: 'secret1' } },
{ auth: { password: 'secret2' } }
]
}
const redact = slowRedact({
paths: ['*.*.*.password']
})
const result = redact(obj)
const parsed = JSON.parse(result)
// Both passwords should be redacted (users[0].auth.password is 4 levels)
assert.strictEqual(parsed.users[0].auth.password, '[REDACTED]')
assert.strictEqual(parsed.users[1].auth.password, '[REDACTED]')
})
test('four wildcards with authorization header (real-world case)', () => {
const obj = {
requests: {
api1: {
config: {
headers: {
authorization: 'Bearer secret-token'
}
}
},
api2: {
config: {
headers: {
authorization: 'Bearer another-token'
}
}
}
}
}
const redact = slowRedact({
paths: ['*.*.*.*.authorization']
})
const result = redact(obj)
const parsed = JSON.parse(result)
// Both authorization headers should be redacted
assert.strictEqual(parsed.requests.api1.config.headers.authorization, '[REDACTED]')
assert.strictEqual(parsed.requests.api2.config.headers.authorization, '[REDACTED]')
})
@@ -1,223 +0,0 @@
const { test } = require('node:test')
const { strict: assert } = require('node:assert')
const slowRedact = require('../index.js')
/* eslint-disable no-proto */
test('prototype pollution: __proto__ path should not pollute Object prototype', () => {
const obj = {
user: { name: 'john' },
__proto__: { isAdmin: true }
}
const redact = slowRedact({
paths: ['__proto__.isAdmin'],
serialize: false
})
const result = redact(obj)
// Should not pollute Object.prototype
assert.strictEqual(Object.prototype.isAdmin, undefined)
assert.strictEqual({}.isAdmin, undefined)
// Should redact the __proto__ property if it exists as a regular property
assert.strictEqual(result.__proto__.isAdmin, '[REDACTED]')
})
test('prototype pollution: constructor.prototype path should not pollute', () => {
const obj = {
user: { name: 'john' },
constructor: {
prototype: { isAdmin: true }
}
}
const redact = slowRedact({
paths: ['constructor.prototype.isAdmin'],
serialize: false
})
const result = redact(obj)
// Should not pollute Object.prototype
assert.strictEqual(Object.prototype.isAdmin, undefined)
assert.strictEqual({}.isAdmin, undefined)
// Should redact the constructor.prototype property if it exists as a regular property
assert.strictEqual(result.constructor.prototype.isAdmin, '[REDACTED]')
})
test('prototype pollution: nested __proto__ should not pollute', () => {
const obj = {
user: {
settings: {
__proto__: { isAdmin: true }
}
}
}
const redact = slowRedact({
paths: ['user.settings.__proto__.isAdmin'],
serialize: false
})
const result = redact(obj)
// Should not pollute Object.prototype
assert.strictEqual(Object.prototype.isAdmin, undefined)
assert.strictEqual({}.isAdmin, undefined)
// Should redact the nested __proto__ property
assert.strictEqual(result.user.settings.__proto__.isAdmin, '[REDACTED]')
})
test('prototype pollution: bracket notation __proto__ should not pollute', () => {
const obj = {
user: { name: 'john' },
__proto__: { isAdmin: true }
}
const redact = slowRedact({
paths: ['["__proto__"]["isAdmin"]'],
serialize: false
})
const result = redact(obj)
// Should not pollute Object.prototype
assert.strictEqual(Object.prototype.isAdmin, undefined)
assert.strictEqual({}.isAdmin, undefined)
// Should redact the __proto__ property when accessed via bracket notation
assert.strictEqual(result.__proto__.isAdmin, '[REDACTED]')
})
test('prototype pollution: wildcard with __proto__ should not pollute', () => {
const obj = {
users: {
__proto__: { isAdmin: true },
user1: { name: 'john' },
user2: { name: 'jane' }
}
}
const redact = slowRedact({
paths: ['users.*'],
serialize: false
})
const result = redact(obj)
// Should not pollute Object.prototype
assert.strictEqual(Object.prototype.isAdmin, undefined)
assert.strictEqual({}.isAdmin, undefined)
// Should redact only own properties
assert.strictEqual(result.users.user1, '[REDACTED]')
assert.strictEqual(result.users.user2, '[REDACTED]')
// __proto__ should only be redacted if it's an own property, not inherited
if (Object.prototype.hasOwnProperty.call(obj.users, '__proto__')) {
assert.strictEqual(result.users.__proto__, '[REDACTED]')
}
})
test('prototype pollution: malicious JSON payload should not pollute', () => {
// Simulate a malicious payload that might come from JSON.parse
const maliciousObj = JSON.parse('{"user": {"name": "john"}, "__proto__": {"isAdmin": true}}')
const redact = slowRedact({
paths: ['__proto__.isAdmin'],
serialize: false
})
const result = redact(maliciousObj)
// Should not pollute Object.prototype
assert.strictEqual(Object.prototype.isAdmin, undefined)
assert.strictEqual({}.isAdmin, undefined)
// The malicious payload should have been redacted
assert.strictEqual(result.__proto__.isAdmin, '[REDACTED]')
})
test('prototype pollution: verify prototype chain is preserved', () => {
function CustomClass () {
this.data = 'test'
}
CustomClass.prototype.method = function () { return 'original' }
const obj = new CustomClass()
const redact = slowRedact({
paths: ['data'],
serialize: false
})
const result = redact(obj)
// Should redact the data property
assert.strictEqual(result.data, '[REDACTED]')
// Should preserve the original prototype chain
assert.strictEqual(result.method(), 'original')
assert.strictEqual(Object.getPrototypeOf(result), CustomClass.prototype)
})
test('prototype pollution: setValue should not create prototype pollution', () => {
const obj = { user: { name: 'john' } }
// Try to pollute via non-existent path that could create __proto__
const redact = slowRedact({
paths: ['__proto__.isAdmin'],
serialize: false
})
const result = redact(obj)
// Should not pollute Object.prototype
assert.strictEqual(Object.prototype.isAdmin, undefined)
assert.strictEqual({}.isAdmin, undefined)
// Should not create the path if it doesn't exist
// The __proto__ property may exist due to Object.create, but should not contain our redacted value
if (result.__proto__) {
assert.strictEqual(result.__proto__.isAdmin, undefined)
}
})
test('prototype pollution: deep nested prototype properties should not pollute', () => {
const obj = {
level1: {
level2: {
level3: {
__proto__: { isAdmin: true },
constructor: {
prototype: { isEvil: true }
}
}
}
}
}
const redact = slowRedact({
paths: [
'level1.level2.level3.__proto__.isAdmin',
'level1.level2.level3.constructor.prototype.isEvil'
],
serialize: false
})
const result = redact(obj)
// Should not pollute Object.prototype
assert.strictEqual(Object.prototype.isAdmin, undefined)
assert.strictEqual(Object.prototype.isEvil, undefined)
assert.strictEqual({}.isAdmin, undefined)
assert.strictEqual({}.isEvil, undefined)
// Should redact the deep nested properties
assert.strictEqual(result.level1.level2.level3.__proto__.isAdmin, '[REDACTED]')
assert.strictEqual(result.level1.level2.level3.constructor.prototype.isEvil, '[REDACTED]')
})
@@ -1,115 +0,0 @@
const { test } = require('node:test')
const { strict: assert } = require('node:assert')
const slowRedact = require('../index.js')
test('selective cloning shares references for non-redacted paths', () => {
const sharedObject = { unchanged: 'data' }
const obj = {
toRedact: 'secret',
shared: sharedObject,
nested: {
toRedact: 'secret2',
shared: sharedObject
}
}
const redact = slowRedact({
paths: ['toRedact', 'nested.toRedact'],
serialize: false
})
const result = redact(obj)
// Redacted values should be different
assert.strictEqual(result.toRedact, '[REDACTED]')
assert.strictEqual(result.nested.toRedact, '[REDACTED]')
// Non-redacted references should be shared (same object reference)
assert.strictEqual(result.shared, obj.shared)
assert.strictEqual(result.nested.shared, obj.nested.shared)
// The shared object should be the exact same reference
assert.strictEqual(result.shared, sharedObject)
assert.strictEqual(result.nested.shared, sharedObject)
})
test('selective cloning works with arrays', () => {
const sharedItem = { unchanged: 'data' }
const obj = {
items: [
{ secret: 'hidden1', shared: sharedItem },
{ secret: 'hidden2', shared: sharedItem },
sharedItem
]
}
const redact = slowRedact({
paths: ['items.*.secret'],
serialize: false
})
const result = redact(obj)
// Secrets should be redacted
assert.strictEqual(result.items[0].secret, '[REDACTED]')
assert.strictEqual(result.items[1].secret, '[REDACTED]')
// Shared references should be preserved where possible
// Note: array items with secrets will be cloned, but their shared properties should still reference the original
assert.strictEqual(result.items[0].shared, sharedItem)
assert.strictEqual(result.items[1].shared, sharedItem)
// The third item gets cloned due to wildcard, but should have the same content
assert.deepStrictEqual(result.items[2], sharedItem)
// Note: Due to wildcard '*', all array items are cloned, even if they don't need redaction
// This is still a significant optimization for object properties that aren't in wildcard paths
})
test('selective cloning with no paths returns original object', () => {
const obj = { data: 'unchanged' }
const redact = slowRedact({
paths: [],
serialize: false
})
const result = redact(obj)
// Should return the exact same object reference
assert.strictEqual(result, obj)
})
test('selective cloning performance - large objects with minimal redaction', () => {
// Create a large object with mostly shared data
const sharedData = { large: 'data'.repeat(1000) }
const obj = {
secret: 'hidden',
shared1: sharedData,
shared2: sharedData,
nested: {
secret: 'hidden2',
shared3: sharedData,
deep: {
shared4: sharedData,
moreShared: sharedData
}
}
}
const redact = slowRedact({
paths: ['secret', 'nested.secret'],
serialize: false
})
const result = redact(obj)
// Verify redaction worked
assert.strictEqual(result.secret, '[REDACTED]')
assert.strictEqual(result.nested.secret, '[REDACTED]')
// Verify shared references are preserved
assert.strictEqual(result.shared1, sharedData)
assert.strictEqual(result.shared2, sharedData)
assert.strictEqual(result.nested.shared3, sharedData)
assert.strictEqual(result.nested.deep.shared4, sharedData)
assert.strictEqual(result.nested.deep.moreShared, sharedData)
})
@@ -1,19 +0,0 @@
{
"compilerOptions": {
"module": "commonjs",
"lib": [
"es6"
],
"noImplicitAny": true,
"noImplicitThis": true,
"strictFunctionTypes": true,
"strictNullChecks": true,
"types": [],
"noEmit": true,
"forceConsistentCasingInFileNames": true
},
"files": [
"index.d.ts",
"index.test-d.ts"
]
}
@@ -1,26 +0,0 @@
Copyright (c) 2016, Daniel Wirtz All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of its author, nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -1,13 +0,0 @@
@protobufjs/aspromise
=====================
[![npm](https://img.shields.io/npm/v/@protobufjs/aspromise.svg)](https://www.npmjs.com/package/@protobufjs/aspromise)
Returns a promise from a node-style callback function.
API
---
* **asPromise(fn: `function`, ctx: `Object`, ...params: `*`): `Promise<*>`**<br />
Returns a promise from a node-style callback function.
**License:** [BSD 3-Clause License](https://opensource.org/licenses/BSD-3-Clause)
@@ -1,13 +0,0 @@
export = asPromise;
type asPromiseCallback = (error: Error | null, ...params: any[]) => {};
/**
* Returns a promise from a node-style callback function.
* @memberof util
* @param {asPromiseCallback} fn Function to call
* @param {*} ctx Function context
* @param {...*} params Function arguments
* @returns {Promise<*>} Promisified function
*/
declare function asPromise(fn: asPromiseCallback, ctx: any, ...params: any[]): Promise<any>;
@@ -1,52 +0,0 @@
"use strict";
module.exports = asPromise;
/**
* Callback as used by {@link util.asPromise}.
* @typedef asPromiseCallback
* @type {function}
* @param {Error|null} error Error, if any
* @param {...*} params Additional arguments
* @returns {undefined}
*/
/**
* Returns a promise from a node-style callback function.
* @memberof util
* @param {asPromiseCallback} fn Function to call
* @param {*} ctx Function context
* @param {...*} params Function arguments
* @returns {Promise<*>} Promisified function
*/
function asPromise(fn, ctx/*, varargs */) {
var params = new Array(arguments.length - 1),
offset = 0,
index = 2,
pending = true;
while (index < arguments.length)
params[offset++] = arguments[index++];
return new Promise(function executor(resolve, reject) {
params[offset] = function callback(err/*, varargs */) {
if (pending) {
pending = false;
if (err)
reject(err);
else {
var params = new Array(arguments.length - 1),
offset = 0;
while (offset < params.length)
params[offset++] = arguments[offset];
resolve.apply(null, params);
}
}
};
try {
fn.apply(ctx || null, params);
} catch (err) {
if (pending) {
pending = false;
reject(err);
}
}
});
}

Some files were not shown because too many files have changed in this diff Show More