New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

manyfest

Package Overview
Dependencies
Maintainers
1
Versions
38
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

manyfest - npm Package Compare versions

Comparing version 1.0.1 to 1.0.2

examples/chocula/Chocula.js

2

.config/configstore/update-notifier-npm.json
{
"optOut": false,
"lastUpdateCheck": 1664845901965
"lastUpdateCheck": 1665021956542
}
{
"name": "manyfest",
"version": "1.0.1",
"version": "1.0.2",
"description": "JSON Object Manifest for Data Description and Parsing",
"main": "index.js",
"main": "source/Manyfest.js",
"scripts": {

@@ -7,0 +7,0 @@ "docker-dev-build-image": "docker build ./ -f Dockerfile_LUXURYCode -t retold/manyfest:local",

@@ -40,3 +40,3 @@ # Manyfest

```
```javascript
const libManyfest = require('Manyfest');

@@ -92,3 +92,3 @@

```
```javascript
let animalManyfest = new libManyfest(

@@ -113,3 +113,4 @@ {

"Hash":"ComfET",
"Description":"The most comfortable temperature for this animal to survive in."}
"Description":"The most comfortable temperature for this animal to survive in."
}
}

@@ -140,3 +141,3 @@ });

```
```javascript
// Write Code

@@ -149,3 +150,3 @@ let newAnimal = {};

```
```javascript
// Read Code

@@ -165,3 +166,3 @@ let newAnimal = {};

```
```javascript
let newAnimal = {};

@@ -187,3 +188,3 @@

```
```javascript
let favAnimal = favoriteAnimalAPI();

@@ -200,3 +201,3 @@

```
```javascript
const libManyfest = require('Manyfest');

@@ -219,3 +220,3 @@

```
```javascript
const libManyfest = require('Manyfest');

@@ -243,2 +244,327 @@

With manyfest, you can easily create a description for each API and code against getting values by hash. This abstracts the complexity of multiple API services without requiring you to marshal it into your own persistence format.
With manyfest, you can easily create a description for each API and code against getting values by hash. This abstracts the complexity of multiple API services without requiring you to marshal it into your own persistence format.
## Archive dot Org
Modern APIs are super complicated. And worse, they change. Let's take a real example. [Archive.org](https://archive.org/) is a wonderful resource for downloading awesome content gathered from the web and such.
There is an API to access their data. It's ... really messy, the data you get back. below is the JSON for a single video (a rad Count Chocula commercial from 1971) -- it takes quite a bit of scroll to get down to the bottom.
![Count Chocula will Rise Again](http://ia800202.us.archive.org/7/items/FrankenberryCountChoculaTevevisionCommercial1971/frankerberry_countchockula_1971.0001.gif)
```JSON
{
"created": 1664830085,
"d1": "ia600202.us.archive.org",
"d2": "ia800202.us.archive.org",
"dir": "/7/items/FrankenberryCountChoculaTevevisionCommercial1971",
"files": [
{
"name": "FrankenberryCountChoculaTevevisionCommercial1971.thumbs/frankerberry_countchockula_1971.0001_000001.jpg",
"source": "derivative",
"format": "Thumbnail",
"original": "frankerberry_countchockula_1971.0001.mpg",
"mtime": "1296336956",
"size": "838",
"md5": "e47269cd5a82db9594f265a65785ec12",
"crc32": "165c668b",
"sha1": "383303d9546c381267569ad4e33aff691f0bb8c7"
},
{
"name": "FrankenberryCountChoculaTevevisionCommercial1971.thumbs/frankerberry_countchockula_1971.0001_000004.jpg",
"source": "derivative",
"format": "Thumbnail",
"original": "frankerberry_countchockula_1971.0001.mpg",
"mtime": "1296336957",
"size": "6843",
"md5": "c93fa52000ab4665e69b25c403e11aff",
"crc32": "9444e6f6",
"sha1": "716b4f9950b8147f51d3265f9c62ff86451308d5"
},
{
"name": "FrankenberryCountChoculaTevevisionCommercial1971.thumbs/frankerberry_countchockula_1971.0001_000009.jpg",
"source": "derivative",
"format": "Thumbnail",
"original": "frankerberry_countchockula_1971.0001.mpg",
"mtime": "1296336957",
"size": "8388",
"md5": "30eb3eb4cbbdfa08d531a0a74da7c000",
"crc32": "be874a9e",
"sha1": "0c392d777609e967b6022be27edad678c5ae74e2"
},
{
"name": "FrankenberryCountChoculaTevevisionCommercial1971.thumbs/frankerberry_countchockula_1971.0001_000014.jpg",
"source": "derivative",
"format": "Thumbnail",
"original": "frankerberry_countchockula_1971.0001.mpg",
"mtime": "1296336958",
"size": "5993",
"md5": "4e9ebc3d076bec8cf7dfe76795f8c769",
"crc32": "912ec98c",
"sha1": "01dc49c852e1bbb421199450dd902935c62b06de"
},
{
"name": "FrankenberryCountChoculaTevevisionCommercial1971.thumbs/frankerberry_countchockula_1971.0001_000019.jpg",
"source": "derivative",
"format": "Thumbnail",
"original": "frankerberry_countchockula_1971.0001.mpg",
"mtime": "1296336958",
"size": "4951",
"md5": "59f190f0c5b0a048415b26412860b6dd",
"crc32": "a70a30b1",
"sha1": "a284af9757cb24d28f96ec88ec1b1c23a8cea9fe"
},
{
"name": "FrankenberryCountChoculaTevevisionCommercial1971.thumbs/frankerberry_countchockula_1971.0001_000024.jpg",
"source": "derivative",
"format": "Thumbnail",
"original": "frankerberry_countchockula_1971.0001.mpg",
"mtime": "1296336959",
"size": "3383",
"md5": "be2a908acd563b896e7758b598295148",
"crc32": "ed467831",
"sha1": "94c001e72ebc86d837a78c61a004db9ab9d597bd"
},
{
"name": "FrankenberryCountChoculaTevevisionCommercial1971.thumbs/frankerberry_countchockula_1971.0001_000029.jpg",
"source": "derivative",
"format": "Thumbnail",
"original": "frankerberry_countchockula_1971.0001.mpg",
"mtime": "1296336960",
"size": "3503",
"md5": "c82199d09be07633000fd07b363dd8a3",
"crc32": "a1fd79cb",
"sha1": "2bc8e761edb24a441fa5906dda1c424e1f98a47a"
},
{
"name": "FrankenberryCountChoculaTevevisionCommercial1971_archive.torrent",
"source": "metadata",
"btih": "de6b371e7cc3c83db1cc08150500753eae533409",
"mtime": "1542761794",
"size": "4093",
"md5": "a275d3b4028cccb5bea8b47a88c838af",
"crc32": "5ffa7334",
"sha1": "af8222637b574cba1360d0ea77e231640ffd43c4",
"format": "Archive BitTorrent"
},
{
"name": "FrankenberryCountChoculaTevevisionCommercial1971_files.xml",
"source": "metadata",
"format": "Metadata",
"md5": "3a7e87b08bed1e203a5858b31352c110"
},
{
"name": "FrankenberryCountChoculaTevevisionCommercial1971_meta.xml",
"source": "metadata",
"format": "Metadata",
"mtime": "1542761793",
"size": "1371",
"md5": "0b9c9bf21b9a26aea43a2f735b404624",
"crc32": "41077288",
"sha1": "22e6f2c73bf63072f671d846355da2785db51dbd"
},
{
"name": "FrankenberryCountChoculaTevevisionCommercial1971_reviews.xml",
"source": "original",
"mtime": "1466898697",
"size": "620",
"md5": "260bfba5d696772445dcc7ff6e6d5bdb",
"crc32": "25ea3229",
"sha1": "7d541f18fcd5ad9c6e593afe5a80f18771f23b32",
"format": "Metadata"
},
{
"name": "__ia_thumb.jpg",
"source": "original",
"mtime": "1539115881",
"size": "7481",
"md5": "8cec324fa0016fd77cc04e6a4b2ebb00",
"crc32": "d9e1b316",
"sha1": "4dab42952fe0405a3b7f80146636b33d7b1bd01e",
"format": "Item Tile",
"rotation": "0"
},
{
"name": "frankerberry_countchockula_1971.0001.gif",
"source": "derivative",
"format": "Animated GIF",
"original": "frankerberry_countchockula_1971.0001.mpg",
"mtime": "1296336965",
"size": "101114",
"md5": "b78a13094030f104900eb996bafe2b7d",
"crc32": "6650cd8",
"sha1": "669798c037205cac14f70592deef6f7831b3d4a1"
},
{
"name": "frankerberry_countchockula_1971.0001.mpg",
"source": "original",
"format": "MPEG2",
"mtime": "1296335803",
"size": "31625216",
"md5": "762ba18b026b85b3f074523e7fcb4db0",
"crc32": "42347f78",
"sha1": "41162dc2d1a91b618124c84628d0c231544a02be",
"length": "31.14",
"height": "480",
"width": "640"
},
{
"name": "frankerberry_countchockula_1971.0001.mpg.idx",
"source": "derivative",
"format": "Video Index",
"original": "frankerberry_countchockula_1971.0001.mpg",
"mtime": "1296336956",
"size": "31141",
"md5": "49423e072726e4ea3cdd8ebdd26c7dfc",
"crc32": "ae969a68",
"sha1": "805782cd2d0f9002555816daadf3b8607e621f79"
},
{
"name": "frankerberry_countchockula_1971.0001.ogv",
"source": "derivative",
"format": "Ogg Video",
"original": "frankerberry_countchockula_1971.0001.mpg",
"mtime": "1296336994",
"size": "2248166",
"md5": "f1b933e97ce63594fb28a0a019ff3436",
"crc32": "a2a0e5e9",
"sha1": "a6bf0aec9f006baeca37c03f586686ebe685d59b",
"length": "31.15",
"height": "300",
"width": "400"
},
{
"name": "frankerberry_countchockula_1971.0001_512kb.mp4",
"source": "derivative",
"format": "512Kb MPEG4",
"original": "frankerberry_countchockula_1971.0001.mpg",
"mtime": "1296336977",
"size": "2378677",
"md5": "a7750839519c61ba3bb99fc66b32011d",
"crc32": "4dbd37c8",
"sha1": "3929314c192dec006fac2739bcb4730788e8c068",
"length": "31.13",
"height": "240",
"width": "320"
}
],
"files_count": 17,
"item_last_updated": 1542761794,
"item_size": 36431778,
"metadata": {
"identifier": "FrankenberryCountChoculaTevevisionCommercial1971",
"title": "Franken Berry / Count Chocula : Tevevision Commercial 1971",
"creator": "General Mills",
"mediatype": "movies",
"collection": [
"classic_tv_commercials",
"television"
],
"description": "Count Chocula and Franken Berry were both introduced in 1971. Boo Berry Cereal appeared in 1973 followed by Fruit Brute in 1974. Yummy Mummy appeared more than a decade later in 1988 - completing the the group known as the General Mills Monster Cereals.",
"subject": "Third Eye Cinema; Classic Television Commercials; animation; cartoons;General Mills",
"licenseurl": "http://creativecommons.org/publicdomain/mark/1.0/",
"publicdate": "2011-01-29 21:36:42",
"addeddate": "2011-01-29 21:35:38",
"uploader": "bolexman@msn.com",
"updater": [
"Bolexman",
"Bolexman",
"Jeff Kaplan"
],
"updatedate": [
"2011-01-29 21:45:38",
"2011-01-29 21:55:46",
"2011-01-29 23:04:55"
],
"sound": "sound",
"color": "color",
"runtime": "0:31",
"backup_location": "ia903608_22",
"ia_orig__runtime": "31 seconds"
},
"reviews": [
{
"reviewbody": "Sugar cereal cartoon Karloff and Lugosi argue self-importance pre Lorre ghost. Interesting how kids still know the voices without any idea of the origins.",
"reviewtitle": "pre booberry",
"reviewer": "outofthebox",
"reviewdate": "2016-06-25 23:51:36",
"createdate": "2016-06-25 23:51:36",
"stars": "4"
}
],
"server": "ia800202.us.archive.org",
"uniq": 1957612749,
"workable_servers": [
"ia800202.us.archive.org",
"ia600202.us.archive.org"
]
}
```
### A Manyfest Schema for Count Chocula to Thrive In
With just a small number of element descriptors, we can make this huge blob of JSON very usable for a developer.
```JSON
{
"Scope": "Archive.org",
"Descriptors": {
"d1": {
"Hash": "Server",
"Name": "Server",
"Description": "The primary server to download these files from.",
"DataType": "String"
},
"d2": {
"Hash": "ServerAlternate",
"Name": "Alternate Server",
"Description": "The alternate server to download these files from.",
"DataType": "String"
},
"dir": {
"Hash": "Path",
"Name": "Server URL Path",
"NameShort": "Path",
"Description": "The path on the server where these files are located."
},
"metadata.identifier": {
"Hash": "GUID",
"Name": "Globally Unique Identifier",
"NameShort": "GUID",
"Description": "Archive.org unique identifier string."
},
"metadata.title": {
"Hash": "Title",
"Name": "Title",
"NameShort": "Title",
"Description": "The title of the media item."
},
"metadata.creator": {
"Hash": "Creator",
"Name": "Creator",
"NameShort": "Creator",
"Description": "The creator of the media item."
},
"metadata.mediatype": {
"Hash": "Type",
"Name": "Media Type",
"NameShort": "Type",
"Description": "The type of media item."
}
}
}
```
These two JSON files are in the [examples/chocula](https://github.com/stevenvelozo/manyfest/tree/main/examples/chocula) folder along with a javascript file `Chocula.js` you can try out yourself and hack on.
```
let libManyfest = require('../../source/Manyfest.js');
let dataArchiveOrg = require(`./Data-Archive-org-Frankenberry.json`);
let schemaArchiveOrg = require(`./Schema-Archive-org-Item.json`);
let _Schema = new libManyfest(schemaArchiveOrg);
console.log(`The URL for "${_Schema.getValueByHash(dataArchiveOrg,'Title')}" is: ${_Schema.getValueByHash(dataArchiveOrg,'Server')}${_Schema.getValueByHash(dataArchiveOrg,'Path')}`);
```

@@ -192,2 +192,15 @@ /**

cleanWrapCharacters (pCharacter, pString)
{
if (pString.startsWith(pCharacter) && pString.endsWith(pCharacter))
{
return pString.substring(1, pString.length - 1);
}
else
{
return pString;
}
}
// Get the value of an element at an address

@@ -201,8 +214,74 @@ getValueAtAddress (pObject, pAddress)

// TODO: Make this work for things like SomeRootObject.Metadata["Some.People.Use.Bad.Object.Property.Names"]
let tmpSeparatorIndex = pAddress.indexOf('.');
// This is the terminal address string (no more dots so the RECUSION ENDS IN HERE somehow)
if (tmpSeparatorIndex === -1)
{
// Now is the point in recursion to return the value in the address
return pObject[pAddress];
// Check if it's a boxed property
let tmpBracketStartIndex = pAddress.indexOf('[');
let tmpBracketStopIndex = pAddress.indexOf(']');
// Boxed elements look like this:
// MyValues[10]
// MyValues['Name']
// MyValues["Age"]
// MyValues[`Cost`]
//
// When we are passed SomeObject["Name"] this code below recurses as if it were SomeObject.Name
// The requirements to detect a boxed element are:
// 1) The start bracket is after character 0
if ((tmpBracketStartIndex > 0)
// 2) The end bracket has something between them
&& (tmpBracketStopIndex > tmpBracketStartIndex)
// 3) There is data
&& (tmpBracketStopIndex - tmpBracketStartIndex > 0))
{
// The "Name" of the Object contained too the left of the bracket
let tmpBoxedPropertyName = pAddress.substring(0, tmpBracketStartIndex).trim();
// If the subproperty doesn't test as a proper Object, none of the rest of this is possible.
// This is a rare case where Arrays testing as Objects is useful
if (typeof(pObject[tmpBoxedPropertyName]) !== 'object')
{
return undefined;
}
// The "Reference" to the property within it, either an array element or object property
let tmpBoxedPropertyReference = pAddress.substring(tmpBracketStartIndex+1, tmpBracketStopIndex).trim();
// Attempt to parse the reference as a number, which will be used as an array element
let tmpBoxedPropertyNumber = parseInt(tmpBoxedPropertyReference, 10);
// Guard: If the referrant is a number and the boxed property is not an array, or vice versa, return undefined.
// This seems confusing to me at first read, so explaination:
// Is the Boxed Object an Array? TRUE
// And is the Reference inside the boxed Object not a number? TRUE
// --> So when these are in agreement, it's an impossible access state
if (Array.isArray(pObject[tmpBoxedPropertyName]) == isNaN(tmpBoxedPropertyNumber))
{
return undefined;
}
// 4) If the middle part is *only* a number (no single, double or backtick quotes) it is an array element,
// otherwise we will try to treat it as a dynamic object property.
if (isNaN(tmpBoxedPropertyNumber))
{
// This isn't a number ... let's treat it as a dynamic object property.
// We would expect the property to be wrapped in some kind of quotes so strip them
tmpBoxedPropertyReference = this.cleanWrapCharacters('"', tmpBoxedPropertyReference);
tmpBoxedPropertyReference = this.cleanWrapCharacters('`', tmpBoxedPropertyReference);
tmpBoxedPropertyReference = this.cleanWrapCharacters("'", tmpBoxedPropertyReference);
// Return the value in the property
return pObject[tmpBoxedPropertyName][tmpBoxedPropertyReference];
}
else
{
return pObject[tmpBoxedPropertyName][tmpBoxedPropertyNumber];
}
}
else
{
// Now is the point in recursion to return the value in the address
return pObject[pAddress];
}
}

@@ -214,2 +293,66 @@ else

// Test if the tmpNewAddress is an array or object
// Check if it's a boxed property
let tmpBracketStartIndex = tmpSubObjectName.indexOf('[');
let tmpBracketStopIndex = tmpSubObjectName.indexOf(']');
// Boxed elements look like this:
// MyValues[42]
// MyValues['Color']
// MyValues["Weight"]
// MyValues[`Diameter`]
//
// When we are passed SomeObject["Name"] this code below recurses as if it were SomeObject.Name
// The requirements to detect a boxed element are:
// 1) The start bracket is after character 0
if ((tmpBracketStartIndex > 0)
// 2) The end bracket has something between them
&& (tmpBracketStopIndex > tmpBracketStartIndex)
// 3) There is data
&& (tmpBracketStopIndex - tmpBracketStartIndex > 0))
{
let tmpBoxedPropertyName = tmpSubObjectName.substring(0, tmpBracketStartIndex).trim();
let tmpBoxedPropertyReference = tmpSubObjectName.substring(tmpBracketStartIndex+1, tmpBracketStopIndex).trim();
let tmpBoxedPropertyNumber = parseInt(tmpBoxedPropertyReference, 10);
// Guard: If the referrant is a number and the boxed property is not an array, or vice versa, return undefined.
// This seems confusing to me at first read, so explaination:
// Is the Boxed Object an Array? TRUE
// And is the Reference inside the boxed Object not a number? TRUE
// --> So when these are in agreement, it's an impossible access state
// This could be a failure in the recursion chain because they passed something like this in:
// StudentData.Sections.Algebra.Students[1].Tardy
// BUT
// StudentData.Sections.Algebra.Students[1] is an object, so the .Tardy is not possible to access
// This could be a failure in the recursion chain because they passed something like this in:
// StudentData.Sections.Algebra.Students["JaneDoe"].Grade
// BUT
// StudentData.Sections.Algebra.Students["JaneDoe"] is an array, so the .Grade is not possible to access
// TODO: Should this be an error or something? Should we keep a log of failures like this?
if (Array.isArray(pObject[tmpBoxedPropertyName]) == isNaN(tmpBoxedPropertyNumber))
{
return undefined;
}
//This is a bracketed value
// 4) If the middle part is *only* a number (no single, double or backtick quotes) it is an array element,
// otherwise we will try to reat it as a dynamic object property.
if (isNaN(tmpBoxedPropertyNumber))
{
// This isn't a number ... let's treat it as a dynanmic object property.
tmpBoxedPropertyReference = this.cleanWrapCharacters('"', tmpBoxedPropertyReference);
tmpBoxedPropertyReference = this.cleanWrapCharacters('`', tmpBoxedPropertyReference);
tmpBoxedPropertyReference = this.cleanWrapCharacters("'", tmpBoxedPropertyReference);
// Recurse directly into the subobject
return this.getValueAtAddress(pObject[tmpBoxedPropertyName][tmpBoxedPropertyReference], tmpNewAddress);
}
else
{
// We parsed a valid number out of the boxed property name, so recurse into the array
return this.getValueAtAddress(pObject[tmpBoxedPropertyName][tmpBoxedPropertyNumber], tmpNewAddress);
}
}
// If there is an object property already named for the sub object, but it isn't an object

@@ -216,0 +359,0 @@ // then the system can't set the value in there. Error and abort!

@@ -19,9 +19,3 @@ /**

{
setup
(
()=>
{
// No custom per-test spool-up required
}
);
setup (()=> {} );

@@ -49,3 +43,3 @@ suite

{
let _Manyfest = new libManyfest({});
let _Manyfest = new libManyfest();
Expect(_Manyfest.scope)

@@ -60,111 +54,3 @@ .to.be.a('string', 'Manyfest should have a scope.');

);
suite
(
'Object Access',
()=>
{
test
(
'Properties should be gettable and settable without a schema.',
(fTestComplete)=>
{
let _Manyfest = new libManyfest({});
let _SimpleObject = {Name:'Bob',Age:31,Pets:{Fido:'Dog',Spot:'Cat'}};
Expect(_Manyfest.getValueAtAddress(_SimpleObject,'Name'))
.to.equal('Bob');
_Manyfest.setValueAtAddress(_SimpleObject,'Name','Jim');
Expect(_Manyfest.getValueAtAddress(_SimpleObject,'Name'))
.to.equal('Jim');
fTestComplete();
}
);
test
(
'Properties should be accessible via Hash.',
(fTestComplete)=>
{
let animalManyfest = new libManyfest(
{
"Scope": "Animal",
"Descriptors":
{
"IDAnimal": { "Name":"Database ID", "Description":"The unique integer-based database identifier for an Animal record.", "DataType":"Integer" },
"Name": { "Description":"The animal's colloquial species name (e.g. Rabbit, Dog, Bear, Mongoose)." },
"Type": { "Description":"Whether or not the animal is wild, domesticated, agricultural, in a research lab or a part of a zoo.." },
"MedicalStats":
{
"Name":"Medical Statistics", "Description":"Basic medical statistics for this animal"
},
"MedicalStats.Temps.MinET": { "Name":"Minimum Environmental Temperature", "NameShort":"MinET", "Description":"Safest minimum temperature for this animal to survive in."},
"MedicalStats.Temps.MaxET": { "Name":"Maximum Environmental Temperature", "NameShort":"MaxET", "Description":"Safest maximum temperature for this animal to survive in."},
"MedicalStats.Temps.CET":
{
"Name":"Comfortable Environmental Temperature",
"NameShort":"Comf Env Temp",
"Hash":"ComfET",
"Description":"The most comfortable temperature for this animal to survive in."
}
}
});
Expect(animalManyfest.getValueByHash({MedicalStats: { Temps: { CET:200 }},Name:'Froggy'}, 'ComfET'))
.to.equal(200);
fTestComplete();
}
);
test
(
'Validate should check that required elements exist',
(fTestComplete)=>
{
let animalManyfest = new libManyfest(
{
"Scope": "Animal",
"Descriptors":
{
"IDAnimal": { "Name":"Database ID", "Description":"The unique integer-based database identifier for an Animal record.", "DataType":"Integer" },
"Name": { "Required":true, "Description":"The animal's colloquial species name (e.g. Rabbit, Dog, Bear, Mongoose)." }
}
});
let tmpValidationResults = animalManyfest.validate({MedicalStats: { Temps: { CET:200 }},Name:'Froggy'});
Expect(tmpValidationResults.Error)
.to.equal(null);
fTestComplete();
}
);
test
(
'Validate should error when required elements do not exist',
(fTestComplete)=>
{
let animalManyfest = new libManyfest(
{
"Scope": "Animal",
"Descriptors":
{
"IDAnimal": { "Name":"Database ID", "Description":"The unique integer-based database identifier for an Animal record.", "DataType":"Integer" },
"Name": { "Required":true, "Description":"The animal's colloquial species name (e.g. Rabbit, Dog, Bear, Mongoose)." }
}
});
let tmpValidationResults = animalManyfest.validate({MedicalStats: { Temps: { CET:200 }}});
Expect(tmpValidationResults.Error)
.to.equal(true);
fTestComplete();
}
);
}
);
}
);
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc