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

@mathicsorg/mathics-threejs-backend

Package Overview
Dependencies
Maintainers
2
Versions
29
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@mathicsorg/mathics-threejs-backend - npm Package Compare versions

Comparing version 0.2.0 to 0.3.0

bundle/index.js

22

backstop.json

@@ -19,3 +19,3 @@ {

"label": "Ambient light",
"url": "http://localhost:8080/test/lights/Ambient.html",
"url": "http://localhost:8080/test/lights/ambient.html",
"delay": 1000,

@@ -26,3 +26,3 @@ "misMatchThreshold": 0.01

"label": "Directional light",
"url": "http://localhost:8080/test/lights/Directional.html",
"url": "http://localhost:8080/test/lights/directional.html",
"delay": 1000,

@@ -33,3 +33,3 @@ "misMatchThreshold": 0.01

"label": "Point light",
"url": "http://localhost:8080/test/lights/Point.html",
"url": "http://localhost:8080/test/lights/point.html",
"delay": 1000,

@@ -40,3 +40,3 @@ "misMatchThreshold": 0.01

"label": "Spot light",
"url": "http://localhost:8080/test/lights/Spot.html",
"url": "http://localhost:8080/test/lights/spot.html",
"delay": 1000,

@@ -47,3 +47,3 @@ "misMatchThreshold": 0.01

"label": "Arrow",
"url": "http://localhost:8080/test/primitives/Arrow.html",
"url": "http://localhost:8080/test/primitives/arrow.html",
"delay": 1000,

@@ -54,3 +54,3 @@ "misMatchThreshold": 0.01

"label": "Cuboid",
"url": "http://localhost:8080/test/primitives/Cuboid.html",
"url": "http://localhost:8080/test/primitives/cuboid.html",
"delay": 1000,

@@ -61,3 +61,3 @@ "misMatchThreshold": 0.01

"label": "Cylinder",
"url": "http://localhost:8080/test/primitives/Cylinder.html",
"url": "http://localhost:8080/test/primitives/cylinder.html",
"delay": 1000,

@@ -68,3 +68,3 @@ "misMatchThreshold": 0.01

"label": "Line",
"url": "http://localhost:8080/test/primitives/Line.html",
"url": "http://localhost:8080/test/primitives/line.html",
"delay": 1000,

@@ -75,3 +75,3 @@ "misMatchThreshold": 0.01

"label": "Point",
"url": "http://localhost:8080/test/primitives/Point.html",
"url": "http://localhost:8080/test/primitives/point.html",
"delay": 1000,

@@ -82,3 +82,3 @@ "misMatchThreshold": 0.01

"label": "Polygon",
"url": "http://localhost:8080/test/primitives/Polygon.html",
"url": "http://localhost:8080/test/primitives/polygon.html",
"delay": 1000,

@@ -89,3 +89,3 @@ "misMatchThreshold": 0.01

"label": "Sphere",
"url": "http://localhost:8080/test/primitives/Sphere.html",
"url": "http://localhost:8080/test/primitives/sphere.html",
"delay": 1000,

@@ -92,0 +92,0 @@ "misMatchThreshold": 0.01

{
"name": "@mathicsorg/mathics-threejs-backend",
"version": "0.2.0",
"version": "0.3.0",
"description": "Mathics 3D Graphics backend using three.js",

@@ -23,3 +23,10 @@ "main": "src/index.js",

},
"homepage": "https://github.com/Mathics3/mathics-threejs-backend"
}
"homepage": "https://github.com/Mathics3/mathics-threejs-backend",
"scripts": {
"build": "rollup src/index.js --file bundle/tmp.js --format iife && minify bundle/tmp.js > bundle/index.js && rm bundle/tmp.js"
},
"devDependencies": {
"minify": "^7",
"rollup": "^2"
}
}

@@ -34,1 +34,36 @@ # mathics-threejs-backend

```
Lots of other examples can be found in the [test](https://github.com/Mathics3/mathics-threejs-backend/tree/master/test) directory of this repository.
## Displaying Examples
Install an HTTP server and run it:
```console
$ npm install http-server # Do this only one time.
...
added 30 packages from 40 contributors and audited 30 packages in 1.595s
...
found 0 vulnerabilities
```
The above only needs to be done once to set up an HTTP server to use. If you already have an HTTP server running that is also able to browse local files in this repository, then you probably don't need to do this.
Otherwise, start the HTTP server that was just installed:
```console
$ ./node_modules/http-server/bin/http-server # Do this once per session
Starting up http-server, serving ./
Available on:
http://127.0.0.1:8080
...
Hit CTRL-C to stop the server
```
Finally, look at a file that you want to see rendered, e.g. http://127.0.0.1:8080/test/axes/axes.html
## JSON API for Compact Graphics3D
See the [wiki](https://github.com/Mathics3/mathics-threejs-backend/wiki) for the JSON API description for Graphics3D Primitives.

@@ -13,8 +13,8 @@ export default function (elements) {

if (
element.type === 'Arrow' ||
element.type === 'Cuboid' ||
element.type === 'Line' ||
element.type === 'Polygon'
element.type === 'arrow' ||
element.type === 'cuboid' ||
element.type === 'line' ||
element.type === 'polygon'
) {
element.Coords.forEach((coordinate => {
element.coords.forEach((coordinate => {
if (coordinate[0]) {

@@ -41,29 +41,29 @@ if (coordinate[0][0] < extent.xmin) {

// the extent isn't calculated correctly for cylinders, their extent should be transformationVector * Radius
element.type === 'Cylinder' ||
element.type === 'Point' ||
element.type === 'Sphere'
element.type === 'cylinder' ||
element.type === 'point' ||
element.type === 'sphere'
) {
element.Radius ??= element.PointSize
element.radius ??= element.pointSize;
element.Coords.forEach((coordinate => {
element.coords.forEach((coordinate => {
if (coordinate[0]) {
if (coordinate[0][0] - element.Radius < extent.xmin) {
extent.xmin = coordinate[0][0] - element.Radius;
if (coordinate[0][0] - element.radius < extent.xmin) {
extent.xmin = coordinate[0][0] - element.radius;
}
if (coordinate[0][0] + element.Radius > extent.xmax) {
extent.xmax = coordinate[0][0] + element.Radius;
if (coordinate[0][0] + element.radius > extent.xmax) {
extent.xmax = coordinate[0][0] + element.radius;
}
if (coordinate[0][1] - element.Radius < extent.ymin) {
extent.ymin = coordinate[0][1] - element.Radius;
if (coordinate[0][1] - element.radius < extent.ymin) {
extent.ymin = coordinate[0][1] - element.radius;
}
if (coordinate[0][1] + element.Radius > extent.ymax) {
extent.ymax = coordinate[0][1] + element.Radius;
if (coordinate[0][1] + element.radius > extent.ymax) {
extent.ymax = coordinate[0][1] + element.radius;
}
if (coordinate[0][2] - element.Radius < extent.zmin) {
extent.zmin = coordinate[0][2] - element.Radius;
if (coordinate[0][2] - element.radius < extent.zmin) {
extent.zmin = coordinate[0][2] - element.radius;
}
if (coordinate[0][2] + element.Radius > extent.zmax) {
extent.zmax = coordinate[0][2] + element.Radius;
if (coordinate[0][2] + element.radius > extent.zmax) {
extent.zmax = coordinate[0][2] + element.radius;
}

@@ -75,20 +75,39 @@ }

// if it's all zeroes means that no non-scaled coordinate was passed, and so we set the mins to 0 and maxs to 1
if (
extent.xmin === 0 &&
extent.xmax === 0 &&
extent.ymin === 0 &&
extent.ymax === 0 &&
extent.zmin === 0 &&
extent.zmax === 0
) {
extent.xmin = 0;
extent.ymin = 0;
extent.zmin = 0;
extent.xmax = 1;
extent.ymax = 1;
extent.zmax = 1;
if (extent.xmin > extent.xmax) {
[extent.xmin, extent.xmax] = [extent.xmax, extent.xmin];
} else if (extent.xmin === extent.xmax) {
if (extent.xmin < 0) {
[extent.xmin, extent.xmax] = [2 * extent.xmin, 0];
} else if (extent.xmin > 0) {
[extent.xmin, extent.xmax] = [0, 2 * extent.xmin];
} else {
[extent.xmin, extent.xmax] = [-1, 1];
}
}
if (extent.ymin > extent.ymax) {
[extent.ymin, extent.ymax] = [extent.ymax, extent.ymin];
} else if (extent.ymin === extent.ymax) {
if (extent.ymin < 0) {
[extent.ymin, extent.ymax] = [2 * extent.ymin, 0];
} else if (extent.ymin > 0) {
[extent.ymin, extent.ymax] = [0, 2 * extent.ymin];
} else {
[extent.ymin, extent.ymax] = [-1, 1];
}
}
if (extent.zmin > extent.zmax) {
[extent.zmin, extent.zmax] = [extent.zmax, extent.zmin];
} else if (extent.zmin === extent.zmax) {
if (extent.zmin < 0) {
[extent.zmin, extent.zmax] = [2 * extent.zmin, 0];
} else if (extent.zmin > 0) {
[extent.zmin, extent.zmax] = [0, 2 * extent.zmin];
} else {
[extent.zmin, extent.zmax] = [-1, 1];
}
}
return extent;
}

@@ -28,3 +28,2 @@ import {

) {
// TODO: add a mechanism to update the enclosing <mspace>
// TODO: shading, handling of VertexNormals

@@ -54,11 +53,11 @@

// scale the viewpoint so the camera doesn't be inside the bounding box
const viewPointScale = Math.max(
extent.xmax - extent.xmin,
extent.ymax - extent.ymin,
extent.zmax - extent.zmin
);
const viewPoint = new Vector3(...viewpoint)
.multiplyScalar(viewPointScale)
.multiplyScalar(
// scale the viewpoint so the camera isn't inside the bounding box
Math.max(
extent.xmax - extent.xmin,
extent.ymax - extent.ymin,
extent.zmax - extent.zmin
)
)
.sub(focus);

@@ -95,4 +94,4 @@

function getInitialLightPosition({ Coords }) {
if (!(Coords instanceof Array)) {
function getInitialLightPosition({ coords }) {
if (!(coords instanceof Array)) {
return;

@@ -103,3 +102,3 @@ }

const temporaryPosition = new Vector3(
...(Coords[0] ?? scaleCoordinate(Coords[1], extent))
...(coords[0] ?? scaleCoordinate(coords[1], extent))
);

@@ -304,3 +303,3 @@

function getTickDir(i) {
function getTickDirection(i) {
const tickDir = new Vector3();

@@ -358,3 +357,3 @@

if (hasAxes[i]) {
let tickDir = getTickDir(i);
const tickDirection = getTickDirection(i);

@@ -367,3 +366,3 @@ for (let j = 0; j < axes.ticks[i][0].length; j++) {

axesGeometry[i].vertices[0],
tickDir
tickDirection
);

@@ -391,3 +390,3 @@

axesGeometry[i].vertices[0],
tickDir.clone().multiplyScalar(0.5)
tickDirection.clone().multiplyScalar(0.5)
);

@@ -665,7 +664,5 @@

positionAxes();
scaleInView();
render();
positionTickNumbers();
}
import drawGraphics3d from './graphics3d.js';
window.drawGraphics3d = drawGraphics3d;
// it'll be removed on Mathics Core update
function translationLayer(div, object) {
object.elements.forEach((primitive) => {
if (primitive.faceColor) {
primitive.color = primitive.faceColor;
}
});
export default drawGraphics3d;
object.lighting.forEach((light) => {
if (light.position) {
light.coords = [light.position];
}
light.type = light.type.toLowerCase();
});
return drawGraphics3d(div, object);
}
window.drawGraphics3d = translationLayer;
export default translationLayer;

@@ -16,17 +16,17 @@ import {

export default {
Ambient: ({ RGBColor }) => {
return new AmbientLight(new Color(...RGBColor).getHex());
ambient: ({ color }) => {
return new AmbientLight(new Color(...color).getHex());
},
Directional: ({ RGBColor }) => {
return new DirectionalLight(new Color(...RGBColor).getHex(), 1);
directional: ({ color }) => {
return new DirectionalLight(new Color(...color).getHex(), 1);
},
Spot: ({ Angle, Coords, RGBColor, Target }, extent) => {
const light = new SpotLight(new Color(...RGBColor).getHex());
spot: ({ angle, color, coords, target }, extent) => {
const light = new SpotLight(new Color(...color).getHex());
light.position.set(
...(Coords[0] ?? scaleCoordinate(Coords[1], extent))
...(coords[0] ?? scaleCoordinate(coords[1], extent))
);
light.angle = Angle;
light.angle = angle;
light.target.position.set(
...(Target[0] ?? scaleCoordinate(Target[1], extent))
...(target[0] ?? scaleCoordinate(target[1], extent))
);

@@ -37,10 +37,10 @@ light.target.updateMatrixWorld();

},
Point: ({ Coords, RGBColor }, extent, radius) => {
point: ({ color, coords }, extent, radius) => {
const group = new Group();
const color = new Color(...RGBColor).getHex();
const colorHex = new Color(...color).getHex();
const light = new PointLight(color);
const light = new PointLight(colorHex);
light.position.set(
...(Coords[0] ?? scaleCoordinate(Coords[1], extent))
...(coords[0] ?? scaleCoordinate(coords[1], extent))
);

@@ -52,3 +52,3 @@ group.add(light);

new SphereGeometry(0.007 * radius, 16, 8),
new MeshBasicMaterial({ color })
new MeshBasicMaterial({ color: colorHex })
);

@@ -55,0 +55,0 @@ lightSphere.position.copy(light.position);

@@ -30,13 +30,13 @@ import {

export default {
Arrow: ({ Coords, Opacity, RGBColor }, extent) => {
arrow: ({ color, coords, opacity }, extent) => {
const group = new Group();
const color = new Color(...RGBColor).getHex();
const colorHex = new Color(...color).getHex();
const startCoordinate = new Vector3(
...(Coords[Coords.length - 2][0] ?? scaleCoordinate(Coords[Coords.length - 2][1], extent))
...(coords[coords.length - 2][0] ?? scaleCoordinate(coords[coords.length - 2][1], extent))
);
const endCoordinate = new Vector3(
...(Coords[Coords.length - 1][0] ?? scaleCoordinate(Coords[Coords.length - 1][1], extent))
...(coords[coords.length - 1][0] ?? scaleCoordinate(coords[coords.length - 1][1], extent))
);

@@ -51,5 +51,5 @@

new MeshBasicMaterial({
color,
opacity: Opacity ?? 1,
transparent: (Opacity ?? 1) !== 1
color: colorHex,
opacity: opacity ?? 1,
transparent: (opacity ?? 1) !== 1
})

@@ -70,10 +70,10 @@ );

const points = new Float32Array(Coords.length * 3);
const coordinates = new Float32Array(coords.length * 3);
for (let i = 0; i < points.length / 3; i++) {
Coords[i][0] ??= scaleCoordinate(Coords[i][1], extent);
for (let i = 0; i < coordinates.length / 3; i++) {
coords[i][0] ??= scaleCoordinate(coords[i][1], extent);
points[i * 3] = Coords[i][0][0];
points[i * 3 + 1] = Coords[i][0][1];
points[i * 3 + 2] = Coords[i][0][2];
coordinates[i * 3] = coords[i][0][0];
coordinates[i * 3 + 1] = coords[i][0][1];
coordinates[i * 3 + 2] = coords[i][0][2];
}

@@ -85,3 +85,3 @@

'position',
new BufferAttribute(points, 3)
new BufferAttribute(coordinates, 3)
);

@@ -93,5 +93,5 @@

new LineBasicMaterial({
color,
opacity: Opacity ?? 1,
transparent: (Opacity ?? 1) !== 1
color: colorHex,
opacity: opacity ?? 1,
transparent: (opacity ?? 1) !== 1
})

@@ -103,11 +103,11 @@ )

},
Cuboid: ({ Coords, Opacity, RGBColor }, extent) => {
cuboid: ({ color, coords, opacity }, extent) => {
const group = new Group();
for (let i = 0; i < Coords.length / 2; i++) {
for (let i = 0; i < coords.length / 2; i++) {
const startCoordinate = new Vector3(
...(Coords[i * 2][0] ?? scaleCoordinate(Coords[i * 2][1], extent))
...(coords[i * 2][0] ?? scaleCoordinate(coords[i * 2][1], extent))
);
const endCoordinate = new Vector3(
...(Coords[i * 2 + 1][0] ?? scaleCoordinate(Coords[i * 2 + 1][1], extent))
...(coords[i * 2 + 1][0] ?? scaleCoordinate(coords[i * 2 + 1][1], extent))
);

@@ -120,5 +120,5 @@

new MeshLambertMaterial({
color: new Color(...RGBColor).getHex(),
opacity: Opacity ?? 1,
transparent: (Opacity ?? 1) !== 1
color: new Color(...color).getHex(),
opacity: opacity ?? 1,
transparent: (opacity ?? 1) !== 1
})

@@ -137,11 +137,11 @@ );

},
Cylinder: ({ Coords, Opacity, Radius, RGBColor }, extent) => {
cylinder: ({ color, coords, opacity, radius }, extent) => {
const group = new Group();
for (let i = 0; i < Coords.length / 2; i++) {
for (let i = 0; i < coords.length / 2; i++) {
const startCoordinate = new Vector3(
...(Coords[i * 2][0] ?? scaleCoordinate(Coords[i * 2][1], extent))
...(coords[i * 2][0] ?? scaleCoordinate(coords[i * 2][1], extent))
);
const endCoordinate = new Vector3(
...(Coords[i * 2 + 1][0] ?? scaleCoordinate(Coords[i * 2][1], extent))
...(coords[i * 2 + 1][0] ?? scaleCoordinate(coords[i * 2][1], extent))
);

@@ -151,4 +151,4 @@

new CylinderBufferGeometry(
Radius,
Radius,
radius,
radius,
startCoordinate.distanceTo(endCoordinate), // the height of the cylinder

@@ -161,5 +161,5 @@ 24

new MeshLambertMaterial({
color: new Color(...RGBColor).getHex(),
opacity: Opacity ?? 1,
transparent: (Opacity ?? 1) !== 1
color: new Color(...color).getHex(),
opacity: opacity ?? 1,
transparent: (opacity ?? 1) !== 1
})

@@ -179,15 +179,15 @@ );

},
Line: ({ Coords, Opacity, RGBColor }, extent) => {
line: ({ color, coords, opacity }, extent) => {
const geometry = new BufferGeometry();
const points = new Float32Array(Coords.length * 3);
const coordinates = new Float32Array(coords.length * 3);
Coords.forEach((coordinate, i) => {
coords.forEach((coordinate, i) => {
coordinate[0] ??= scaleCoordinate(coordinate[1], extent);
points[i * 3] = coordinate[0][0];
points[i * 3 + 1] = coordinate[0][1];
points[i * 3 + 2] = coordinate[0][2];
coordinates[i * 3] = coordinate[0][0];
coordinates[i * 3 + 1] = coordinate[0][1];
coordinates[i * 3 + 2] = coordinate[0][2];
});
geometry.setAttribute('position', new BufferAttribute(points, 3));
geometry.setAttribute('position', new BufferAttribute(coordinates, 3));

@@ -197,22 +197,22 @@ return new Line(

new LineBasicMaterial({
color: new Color(...RGBColor).getHex(),
opacity: Opacity ?? 1,
transparent: (Opacity ?? 1) !== 1
color: new Color(...color).getHex(),
opacity: opacity ?? 1,
transparent: (opacity ?? 1) !== 1
})
);
},
Point: ({ Coords, Opacity, PointSize, RGBColor }, extent, canvasSize) => {
point: ({ color, coords, opacity, pointSize }, extent, canvasSize) => {
const geometry = new BufferGeometry();
const points = new Float32Array(Coords.length * 3);
const coordinates = new Float32Array(coords.length * 3);
Coords.forEach((coordinate, i) => {
coords.forEach((coordinate, i) => {
coordinate[0] ??= scaleCoordinate(coordinate[1], extent);
points[i * 3] = coordinate[0][0];
points[i * 3 + 1] = coordinate[0][1];
points[i * 3 + 2] = coordinate[0][2];
coordinates[i * 3] = coordinate[0][0];
coordinates[i * 3 + 1] = coordinate[0][1];
coordinates[i * 3 + 2] = coordinate[0][2];
});
geometry.setAttribute('position', new BufferAttribute(points, 3));
geometry.setAttribute('position', new BufferAttribute(coordinates, 3));

@@ -223,5 +223,6 @@ return new Points(

transparent: true,
depthWrite: false,
uniforms: {
size: { value: PointSize * canvasSize * 0.5 },
color: { value: new Vector4(...RGBColor, Opacity) },
size: { value: pointSize * canvasSize * 0.5 },
color: { value: new Vector4(...color, opacity) },
},

@@ -250,6 +251,6 @@ vertexShader: `

},
Polygon: ({ Coords, Opacity, RGBColor }, extent) => {
polygon: ({ color, coords, opacity }, extent) => {
let geometry;
if (Coords.length === 3) { // triangle
if (coords.length === 3) { // triangle
geometry = new BufferGeometry();

@@ -260,5 +261,5 @@

new BufferAttribute(new Float32Array([
...(Coords[0][0] ?? scaleCoordinate(Coords[0][1], extent)),
...(Coords[1][0] ?? scaleCoordinate(Coords[1][1], extent)),
...(Coords[2][0] ?? scaleCoordinate(Coords[2][1], extent))
...(coords[0][0] ?? scaleCoordinate(coords[0][1], extent)),
...(coords[1][0] ?? scaleCoordinate(coords[1][1], extent)),
...(coords[2][0] ?? scaleCoordinate(coords[2][1], extent))
]), 3)

@@ -272,12 +273,12 @@ );

Coords.forEach((coordinate) => {
coords.forEach((coordinate) => {
coordinate[0] ??= scaleCoordinate(coordinate[1], extent);
if (coordinate[0][0] !== Coords[0][0][0]) {
if (coordinate[0][0] !== coords[0][0][0]) {
isXCoplanar = 0;
}
if (coordinate[0][1] !== Coords[0][0][1]) {
if (coordinate[0][1] !== coords[0][0][1]) {
isYCoplanar = 0;
}
if (coordinate[0][2] !== Coords[0][0][2]) {
if (coordinate[0][2] !== coords[0][0][2]) {
isZCoplanar = 0;

@@ -296,15 +297,15 @@ }

const points = Coords.map((coordinate) =>
new Vector3(
...(coordinate[0] ?? scaleCoordinate(coordinate[1], extent))
).applyQuaternion(
new Quaternion().setFromUnitVectors(
normalVector,
normalZVector
geometry = new ShapeGeometry(new Shape(
coords.map((coordinate) =>
new Vector3(
...(coordinate[0] ?? scaleCoordinate(coordinate[1], extent))
).applyQuaternion(
new Quaternion().setFromUnitVectors(
normalVector,
normalZVector
)
)
)
);
));
geometry = new ShapeGeometry(new Shape(points));
geometry.vertices = geometry.vertices.map(

@@ -323,5 +324,5 @@ (vertex) => vertex.applyQuaternion(

const coordinates = new Float32Array(Coords.length * 3);
const coordinates = new Float32Array(coords.length * 3);
Coords.forEach((coordinate, i) => {
coords.forEach((coordinate, i) => {
coordinate[0] ??= scaleCoordinate(coordinate[1], extent);

@@ -346,20 +347,20 @@

return new Mesh(geometry, new MeshLambertMaterial({
color: new Color(...RGBColor).getHex(),
opacity: Opacity ?? 1,
transparent: (Opacity ?? 1) !== 1,
color: new Color(...color).getHex(),
opacity: opacity ?? 1,
transparent: (opacity ?? 1) !== 1,
side: DoubleSide
}));
},
Sphere: ({ Coords, Opacity, Radius, RGBColor }, extent) => {
sphere: ({ color, coords, opacity, radius }, extent) => {
const spheres = new InstancedMesh(
new SphereBufferGeometry(Radius, 48, 48),
new SphereBufferGeometry(radius, 48, 48),
new MeshLambertMaterial({
color: new Color(...RGBColor).getHex(),
opacity: Opacity ?? 1,
transparent: (Opacity ?? 1) !== 1
color: new Color(...color).getHex(),
opacity: opacity ?? 1,
transparent: (opacity ?? 1) !== 1
}),
Coords.length
coords.length
);
Coords.forEach((coordinate, i) =>
coords.forEach((coordinate, i) =>
spheres.setMatrixAt(

@@ -366,0 +367,0 @@ i,

Sorry, the diff of this file is not supported yet

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