Socket
Socket
Sign inDemoInstall

protoblast

Package Overview
Dependencies
Maintainers
1
Versions
102
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

protoblast - npm Package Compare versions

Comparing version 0.9.0 to 0.9.1

5

CHANGELOG.md

@@ -0,1 +1,6 @@

## 0.9.1 (2024-02-19)
* Rewrite JavaScript tokenizer to no longer use regexes
* Allow `Pledge` instances to be cancelled
## 0.9.0 (2024-02-15)

@@ -2,0 +7,0 @@

0

lib/function_flow.js

@@ -0,0 +0,0 @@ const defStat = Blast.createStaticDefiner('Function');

618

lib/function.js

@@ -6,7 +6,2 @@ const defStat = Blast.createStaticDefiner('Function'),

let tokenMatches,
tokenTesters,
haveCombined,
combineError;
let keywords = [

@@ -85,74 +80,19 @@ 'async', 'await',

let token_patterns = {
whitespace : /\s+/,
keyword : null,
name : /[a-zA-Z_\$][a-zA-Z_\$0-9]*/,
string1 : /"(?:(?:\\\n|\\"|[^"\n]))*?"/,
string2 : /'(?:(?:\\\n|\\'|[^'\n]))*?'/,
string3 : /`(?:(?:\\`|.|[\n\r]))*?`/,
comment1 : /\/\*[\s\S]*?\*\//,
comment2 : /\/\/.*?(?=\r\n|\r|\n|$)/,
number2 : /\d+(?:_\d+)*(?:\.\d+(?:_\d+)*)?(?:e[+-]?\d+(?:_\d+)*)?/i,
number : /\d+(?:\.\d+)?(?:e[+-]?\d+)?/,
parens : /[\(\)]/,
curly : /[{}]/,
square : /[\[\]]/,
punct : /[;.:\?\^%<>=!&|+\-,~]/,
regexp : /\/(?:(?:\\\/|[^\n\/]))*?\//,
};
const TOKEN_WHITESPACE = 1,
TOKEN_KEYWORD = 2,
TOKEN_NAME = 3,
TOKEN_STRING_DOUBLE = 4,
TOKEN_STRING_SINGLE = 5,
TOKEN_STRING_BACK = 6,
TOKEN_COMMENT_BLOCK = 7,
TOKEN_COMMENT_INLINE = 8,
TOKEN_NUMBER = 9,
TOKEN_PARENS = 10,
TOKEN_CURLY = 11,
TOKEN_SQUARE = 12,
TOKEN_PUNCT = 13,
TOKEN_REGEXP = 14,
TOKEN_REGEXP_FLAG = 15,
TOKEN_INVALID = 16;
let rx_regex = /^\/(?:(?:\\\/|[^\n\/]))*?\/(?:[gimuy]*)$/;
let patternNames = {
string1 : 'string',
string2 : 'string',
string3 : 'string',
comment1 : 'comment',
comment2 : 'comment',
};
Blast._fn_token_prepare = function BlastReadyFunction() {
var patterns = [],
temp,
name,
key,
i;
token_patterns.keyword = RegExp('\\b(?:' + keywords.join('|') + ')\\b');
temp = '';
for (key in operators) {
if (temp) {
temp += '|';
}
temp += Bound.RegExp.escape(key);
}
token_patterns.punct = RegExp('(?:' + temp + ')');
for (key in token_patterns) {
patterns.push(token_patterns[key]);
}
try {
// Create the matches
tokenMatches = Collection.RegExp.combine.apply(null, patterns);
} catch (err) {
combineError = err;
return;
}
// Create the testers
tokenTesters = {};
for (name in token_patterns) {
tokenTesters[name] = new RegExp('^' + Collection.RegExp.prototype.getPattern.call(token_patterns[name]) + '$');
}
haveCombined = true;
};
/**

@@ -263,3 +203,3 @@ * Create a function with the given variable as name.

* @since 0.1.2
* @version 0.1.2
* @version 0.9.0
*

@@ -271,21 +211,4 @@ * @param {string} tokenString

defStat(function getTokenType(tokenString) {
var patternName;
if (!haveCombined) {
throw combineError;
}
for (patternName in token_patterns) {
if (tokenTesters[patternName].test(tokenString)) {
if (patternNames[patternName]) {
return patternNames[patternName];
}
return patternName;
}
}
return 'invalid';
let result = Fn.tokenize(tokenString, true, false);
return result?.[0]?.type || 'invalid';
});

@@ -300,177 +223,430 @@

*
* @param {string} sourceCode
* @param {boolean} addType Add the type of the token
* @param {boolean} throwErrors Throw error when invalid token is found
* @param {string} source_code
* @param {boolean} add_type Add the type of the token
* @param {boolean} throw_errors Throw error when invalid token is found
*
* @return {Array}
*/
defStat(function tokenize(sourceCode, addType, throwErrors) {
defStat(function tokenize(source_code, add_type, throw_errors) {
let line_nr = 0,
tokens = [],
obj,
if (typeof source_code !== 'string') {
source_code = ''+source_code;
}
let prev_usable_token,
check_next_state,
is_punctuation = false,
current_state,
current_line = 0,
string_state = false,
prev_token,
is_digit = false,
end_char,
has_dot = false,
escaped = false,
result = [],
length = source_code.length,
buffer,
next,
trim,
prev,
char,
i;
if (!haveCombined) {
throw combineError;
}
const createBuffer = (state, char) => {
endState();
current_state = state;
if (typeof sourceCode !== 'string') {
sourceCode = ''+sourceCode;
}
buffer = {
type : state,
value : '',
line_start : current_line,
line_end : current_line,
};
sourceCode = sourceCode.split(tokenMatches);
pushChar(char);
};
for (i = 0; i < sourceCode.length; i++) {
const createStringState = (state, char) => {
createBuffer(state, char);
end_char = char;
string_state = true;
};
// Every uneven match should be used
if (i % 2) {
tokens.push(sourceCode[i]);
} else if (sourceCode[i] !== '') {
const endState = (last_char) => {
string_state = false;
current_state = null;
end_char = null;
has_dot = false;
// If an even match contains something, it's invalid
if (throwErrors) {
throw new Error('Invalid token: ' + JSON.stringify(e));
if (buffer) {
pushChar(last_char);
buffer.line_end = current_line;
result.push(buffer);
if (buffer.type != TOKEN_WHITESPACE && buffer.type != TOKEN_COMMENT_INLINE && buffer.type != TOKEN_COMMENT_BLOCK) {
prev_usable_token = buffer;
}
tokens.push(sourceCode[i]);
prev_token = buffer;
buffer = null;
}
}
};
if (!addType) {
return tokens;
}
const pushChar = (new_char) => {
let was_declaring,
declaring,
assigning,
result = [],
prev;
if (new_char != null) {
buffer.value += new_char;
char = new_char;
for (i = 0; i < tokens.length; i++) {
was_declaring = declaring;
if (new_char === '\n') {
current_line++;
}
}
obj = {
type : Fn.getTokenType(tokens[i]),
value : tokens[i],
line_start : line_nr,
line_end : 0,
};
escaped = false;
};
if (obj.type === 'number2') {
obj.type = 'number';
obj.value = obj.value.replaceAll('_', '');
const getNextChars = (amount) => {
let result = char;
for (let j = 1; j < amount; j++) {
result += source_code[i + j];
}
if (declaring) {
if (obj.value == ';') {
declaring = false;
assigning = false;
} else {
if (assigning && obj.value == ',') {
assigning = false;
} else if (obj.value == '=') {
assigning = true;
return result;
};
const createOperator = (chars, type) => {
let skip = chars.length - 1;
char = chars[skip];
i += skip;
createBuffer(TOKEN_PUNCT, chars);
buffer.name = type;
};
const checkPunct = (char) => {
let chars = getNextChars(4),
type = operators[chars];
if (type) {
createOperator(chars, type);
return;
}
chars = getNextChars(3);
type = operators[chars];
if (type) {
createOperator(chars, type);
return;
}
chars = getNextChars(2);
type = operators[chars];
if (type) {
createOperator(chars, type);
return;
}
type = operators[char];
if (type) {
createOperator(char, type);
return;
}
};
const hasSlashBeforeEndOfLine = () => {
let char,
j = i + 1;
while (j < length) {
char = source_code[j];
j++;
if (char === '\\') {
j++;
continue;
}
if (char === '\n') {
return false;
}
if (char === '/') {
return true;
}
}
return false;
};
const isValidNameChar = (char) => {
return char == '_' || char == '$' || char.match(/[a-zA-Z0-9]/);
};
for (i = 0; i < length; i++) {
prev = char;
char = source_code[i];
trim = char.trim();
next = source_code[i + 1];
is_punctuation = false;
is_digit = false;
switch (char) {
case '.':
case '~':
case '!':
case '%':
case '/':
case '*':
case '-':
case '+':
case '>':
case '<':
case '=':
case '&':
case '^':
case '|':
case ',':
case ';':
case '?':
case ':':
case '(':
case ')':
case '{':
case '}':
case '[':
case ']':
is_punctuation = true;
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
is_digit = true;
break;
}
if (current_state) {
check_next_state = false;
if (current_state == TOKEN_WHITESPACE) {
if (!trim) {
pushChar(char);
} else {
check_next_state = true;
}
} else if (string_state) {
if (obj.type != 'whitespace' && obj.type != 'name') {
if (char === '\\' && !escaped) {
pushChar(char);
escaped = true;
} else if (char == end_char && !escaped) {
endState(char);
} else {
pushChar(char);
}
} else if (current_state == TOKEN_COMMENT_INLINE) {
if (char == '\n') {
check_next_state = true;
} else {
pushChar(char);
}
} else if (current_state == TOKEN_COMMENT_BLOCK) {
if (char == '*' && next == '/') {
i++;
pushChar(char);
pushChar(next);
endState();
} else {
pushChar(char);
}
} else if (current_state == TOKEN_NAME || current_state == TOKEN_INVALID) {
if (!trim || is_punctuation) {
if (assigning) {
if (obj.type != 'punct' && obj.value != 'this') {
assigning = false;
}
} else if (obj.value != '=' && obj.value != ',') {
declaring = false;
if (keywords.includes(buffer.value)) {
buffer.type = TOKEN_KEYWORD;
buffer.name = buffer.value;
}
check_next_state = true;
} else {
pushChar(char);
}
}
}
} else if (current_state == TOKEN_REGEXP) {
if (char == '/' && !escaped) {
current_state = TOKEN_REGEXP_FLAG
}
if (obj.type == 'keyword') {
obj.name = obj.value;
pushChar(char);
} else if (current_state == TOKEN_NUMBER) {
if (obj.value !== 'this') {
if (obj.value == 'var' || obj.value == 'let' || obj.value == 'const') {
declaring = true;
if (char == '.') {
if (has_dot) {
check_next_state = true;
} else {
pushChar(char);
has_dot = true;
}
} else if (is_digit || char == 'e' || char == 'E') {
pushChar(char);
} else if (char == '_' && prev != '_') {
// Ignore
} else {
declaring = false;
check_next_state = true;
}
} else if (current_state == TOKEN_REGEXP_FLAG) {
switch (char) {
case 'g':
case 'i':
case 'm':
case 'u':
case 'y':
pushChar(char);
break;
default:
check_next_state = true;
};
} else {
check_next_state = true;
}
} else if (operators[tokens[i]]) {
obj.name = operators[tokens[i]];
}
if (!check_next_state) {
continue;
}
if (obj.value === '/') {
i = checkTogenizeRegex(i, tokens, obj, prev, was_declaring, assigning);
endState();
}
// Replace the original string with the object
result.push(obj);
if (!trim) {
createBuffer(TOKEN_WHITESPACE, char);
} else if (char == '"') {
createStringState(TOKEN_STRING_DOUBLE, char);
} else if (char == "'") {
createStringState(TOKEN_STRING_SINGLE, char);
} else if (char == '`') {
createStringState(TOKEN_STRING_BACK, char);
} else if (char == '(' || char == ')') {
createBuffer(TOKEN_PARENS, char);
} else if (char == '{' || char == '}') {
createBuffer(TOKEN_CURLY, char);
} else if (char == '[' || char == ']') {
createBuffer(TOKEN_SQUARE, char);
} else if (is_digit) {
createBuffer(TOKEN_NUMBER, char);
if (obj.type == 'whitespace') {
line_nr += Bound.String.count(obj.value, '\n');
// It technically isn't valid JavaScript either,
// But it is even more wrong when 'a.0.1.b' is considered a number
if (prev_usable_token?.value === '.' || prev_usable_token?.value === '?.') {
endState();
}
} else {
prev = obj;
if (char == '/') {
if (next == '/') {
i++;
createBuffer(TOKEN_COMMENT_INLINE, '//');
continue;
} else if (next == '*') {
i++;
char = '*'; // For next iteration's prev
createBuffer(TOKEN_COMMENT_BLOCK, '/*');
continue;
} else if (prev_usable_token?.type != TOKEN_NAME && prev_usable_token?.type != TOKEN_NUMBER && hasSlashBeforeEndOfLine()) {
createBuffer(TOKEN_REGEXP, char);
continue;
}
}
if (is_punctuation) {
checkPunct(char);
} else if (isValidNameChar(char)) {
createBuffer(TOKEN_NAME, char);
} else {
createBuffer(TOKEN_INVALID, char);
}
}
obj.line_end = line_nr;
}
return result;
});
endState();
/**
* Fix regex literals in tokenized objects
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 0.7.0
* @version 0.7.0
*
* @param {number} index The current index
* @param {Array} tokens Array of all the tokens
* @param {Object} obj The current object
* @param {Object} prev The previous object
* @param {boolean} declaring Are we currently declaring something?
*
* @return {number}
*/
function checkTogenizeRegex(index, tokens, obj, prev, declaring, assigning) {
let mapper;
if (prev && (prev.type != 'punct' && prev.type != 'parens')) {
if (!declaring || ((assigning && prev.type == 'name') || prev.type == 'string' || prev.type == 'number')) {
return index;
}
}
if (add_type) {
mapper = entry => {
switch (entry.type) {
case TOKEN_WHITESPACE:
entry.type = 'whitespace';
break;
let matched = false,
next = tokens[index + 1],
temp = obj.value,
str = obj.value,
i;
case TOKEN_KEYWORD:
entry.type = 'keyword';
break;
for (i = index + 1; i < tokens.length; i++) {
temp += tokens[i];
case TOKEN_NAME:
entry.type = 'name';
break;
if (rx_regex.test(temp)) {
matched = i;
str = temp;
} else {
// If it matched before, that'll be correct
if (matched) {
i = matched;
break;
case TOKEN_STRING_DOUBLE:
case TOKEN_STRING_SINGLE:
case TOKEN_STRING_BACK:
entry.type = 'string';
break;
case TOKEN_COMMENT_INLINE:
case TOKEN_COMMENT_BLOCK:
entry.type = 'comment';
break;
case TOKEN_NUMBER:
entry.type = 'number';
break;
case TOKEN_PARENS:
entry.type = 'parens';
break;
case TOKEN_CURLY:
entry.type = 'curly';
break;
case TOKEN_SQUARE:
entry.type = 'square';
break;
case TOKEN_PUNCT:
entry.type = 'punct';
break;
case TOKEN_REGEXP:
case TOKEN_REGEXP_FLAG:
entry.type = 'regexp';
break;
case TOKEN_INVALID:
entry.type = 'invalid';
break;
}
}
}
if (str.length > 1) {
obj.value = str;
obj.type = obj.name = 'regexp';
return i;
return entry;
};
} else {
mapper = entry => entry.value;
}
return index;
}
return result.map(mapper);
});

@@ -477,0 +653,0 @@ /**

@@ -1617,3 +1617,2 @@ module.exports = function BlastInitLoader(modifyPrototype) {

Blast._fn_token_prepare();
Blast.emit('pre-extra-files');

@@ -1620,0 +1619,0 @@

const PENDING = 0,
RESOLVED = 1,
REJECTED = 2;
REJECTED = 2,
CANCELLED = 3;

@@ -9,5 +10,8 @@ const REJECTED_REASON = Symbol('rejected_reason'),

ON_FULFILLED = Symbol('on_fulfilled'),
SUB_PLEDGES = Symbol('sub_pledges'),
ON_CANCELLED = Symbol('on_cancelled'),
SUB_PLEDGES = Symbol('sub_pledges'),
ON_REJECTED = Symbol('on_rejected'),
DO_RESOLVE = Symbol('do_resolve'),
ON_FINALLY = Symbol('on_finally'),
DO_FINALLY = Symbol('do_finally'),
DO_REJECT = Symbol('do_reject'),

@@ -97,2 +101,11 @@ EXECUTOR = Symbol('executor'),

/**
* An array of tasks to perform when this pledge is cancelled
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 0.9.1
* @version 0.9.1
*/
[ON_CANCELLED]: null,
/**
* The eventual resolved value

@@ -138,13 +151,2 @@ *

/**
* Property that could be a function to cancel the pledge
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 0.7.0
* @version 0.7.0
*
* @type {Function}
*/
cancel: null,
/**
* Warn when an error is not caught

@@ -526,2 +528,107 @@ *

/**
* Cancel the pledge
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 0.9.1
* @version 0.9.1
*/
AbstractPledge.setMethod(function cancel() {
if (!this.isPending()) {
return;
}
this[STATE] = CANCELLED;
// Always do the on-cancel tasks as swiftly as possible
return Swift.all(this[ON_CANCELLED]).finally(() => this[DO_FINALLY]());
});
/**
* Do the given task when this pledge is cancelled
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 0.9.1
* @version 0.9.1
*/
AbstractPledge.setMethod(function onCancelled(task) {
if (typeof task != 'function') {
return;
}
if (!this.isPending()) {
if (this.isCancelled() && task) {
task();
}
return;
}
if (!this[ON_CANCELLED]) {
this[ON_CANCELLED] = [];
}
this[ON_CANCELLED].push(next => Swift.done(task(), next));
});
/**
* Do all the finally callbacks
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 0.9.1
* @version 0.9.1
*/
AbstractPledge.setMethod(DO_FINALLY, function doFinally() {
while (this[ON_FINALLY]?.length) {
this[ON_FINALLY].shift()();
}
});
/**
* Has this pledge been resolved?
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 0.9.1
* @version 0.9.1
*/
AbstractPledge.setMethod(function isResolved() {
return this[STATE] === RESOLVED;
});
/**
* Has this pledge been rejected?
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 0.9.1
* @version 0.9.1
*/
AbstractPledge.setMethod(function isRejected() {
return this[STATE] === REJECTED;
});
/**
* Has this pledge been cancelled?
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 0.9.1
* @version 0.9.1
*/
AbstractPledge.setMethod(function isCancelled() {
return this[STATE] === CANCELLED;
});
/**
* Is this pledge still pending?
*
* @author Jelle De Loecker <jelle@elevenways.be>
* @since 0.9.1
* @version 0.9.1
*/
AbstractPledge.setMethod(function isPending() {
return this[STATE] === PENDING;
});
/**
* The BasePledge Class

@@ -1082,3 +1189,3 @@ *

* @since 0.5.6
* @version 0.5.6
* @version 0.9.1
*

@@ -1091,4 +1198,17 @@ * @param {Function} on_finally

var constructor = this.constructor;
let constructor = this.constructor;
if (this.isPending() || this.isCancelled()) {
if (!this[ON_FINALLY]) {
this[ON_FINALLY] = [];
}
// We keep this in an array in case the pledge gets cancelled
this[ON_FINALLY].push(on_finally);
if (this.isCancelled()) {
return this[DO_FINALLY]();
}
}
return this.then(

@@ -1383,5 +1503,6 @@ function afterResolved(value) {

}
this[DO_FINALLY]();
});
/**

@@ -1403,2 +1524,3 @@ * Reject with the given reason

this[DO_REJECT](reason);
this[DO_FINALLY]();
});

@@ -1405,0 +1527,0 @@

@@ -436,3 +436,3 @@ module.exports = function serverFunctions(Blast, extras) {

properties: options.depropertize,
return_tokens: true
return_tokens: true,
});

@@ -443,3 +443,4 @@ }

tokens = Blast.destringifyCode(tokens, {
return_tokens: true
return_tokens : true,
depropertize : options.depropertize,
});

@@ -1324,5 +1325,12 @@ }

remove_symbol_names = options.remove_symbol_names ?? true,
previous_type,
current_type,
depropertize = !!options.depropertize,
property_map = new Map(),
string_map = new Map(),
usage_map = new Map(),
first_char,
next_type,
last_char,
is_string,
new_code = 'const ',

@@ -1336,3 +1344,2 @@ replaced = 0,

next,
type,
i;

@@ -1343,5 +1350,6 @@

first_char = token[0];
is_string = false;
if (first_char != '"' && first_char != "'") {
continue;
if (first_char == '"' || first_char == "'") {
is_string = true;
}

@@ -1357,26 +1365,40 @@

if (last_char != first_char) {
is_string = false;
}
if (is_string) {
current_type = 'string';
} else if (!depropertize) {
continue;
} else {
current_type = Fn.getTokenType(token);
}
previous = tokens[i - 1];
type = Fn.getTokenType(previous);
previous_type = Fn.getTokenType(previous);
if (type == 'name') {
if (previous_type == 'name') {
continue;
}
if (type == 'whitespace') {
if (previous_type == 'whitespace') {
previous = tokens[i - 2];
type = Fn.getTokenType(previous);
previous_type = Fn.getTokenType(previous);
}
if (!is_string) {
if (previous != '.' && previous != '?.') {
continue;
}
}
next = tokens[i + 1];
type = Fn.getTokenType(next);
next_type = Fn.getTokenType(next);
if (type == 'whitespace') {
if (next_type == 'whitespace') {
next = tokens[i + 2];
type = Fn.getTokenType(next);
next_type = Fn.getTokenType(next);
}
if (type == 'name') {
if (next_type == 'name') {
continue;

@@ -1404,3 +1426,5 @@ }

if (first_char == '"' && !token.includes("'")) {
if (!is_string) {
token = "'" + token + "'";
} else if (first_char == '"' && !token.includes("'")) {
token = "'" + token.slice(1, -1) + "'";

@@ -1411,2 +1435,3 @@ }

// Always add at least an empty array of indexes to the string map
if (!indexes) {

@@ -1417,2 +1442,22 @@ indexes = [];

if (is_string) {
indexes.push(i);
} else {
indexes = property_map.get(token);
if (!indexes) {
indexes = [];
property_map.set(token, indexes);
}
indexes.push(i);
}
indexes = usage_map.get(token);
if (!indexes) {
indexes = [];
usage_map.set(token, indexes);
}
indexes.push(i);

@@ -1423,4 +1468,6 @@ }

for (let [string, indexes] of string_map) {
for (let [string, string_indexes] of string_map) {
indexes = usage_map.get(string);
// Ignore strings that are only used once

@@ -1436,5 +1483,6 @@ if (indexes.length < 2) {

sorted.push({
string : string,
indexes : indexes,
count : indexes.length,
string : string,
string_indexes : string_indexes,
property_indexes : property_map.get(string),
count : indexes.length,
});

@@ -1449,4 +1497,8 @@ }

for (let {string, indexes} of sorted) {
// Re-use the string map for mapping
string_map = new Map();
options.string_map = string_map;
for (let {string, string_indexes, property_indexes} of sorted) {
if (replaced) {

@@ -1461,6 +1513,21 @@ new_code += '\n, ';

for (i = 0; i < indexes.length; i++) {
index = indexes[i];
tokens[indexes[i]] = new_name;
string_map.set(string, new_name);
for (i = 0; i < string_indexes.length; i++) {
index = string_indexes[i];
tokens[index] = new_name;
}
if (!property_indexes?.length) {
continue;
}
for (i = 0; i < property_indexes.length; i++) {
index = property_indexes[i];
tokens[index] = '[' + new_name + ']';
if (tokens[index - 1] === '.') {
tokens[index - 1] = '';
}
}
}

@@ -1467,0 +1534,0 @@

{
"name": "protoblast",
"description": "Native object expansion library",
"version": "0.9.0",
"version": "0.9.1",
"author": "Jelle De Loecker <jelle@elevenways.be>",

@@ -6,0 +6,0 @@ "keywords": [

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