
Research
Using Trusted Protocols Against You: Gmail as a C2 Mechanism
Socket uncovers malicious packages on PyPI using Gmail's SMTP protocol for command and control (C2) to exfiltrate data and execute commands.
Patella is a library for reactive programming in JavaScript, inspired by Hyperactiv and Vue.js.
Patella, formerly known as Luar, is a library for reactive programming in JavaScript, inspired by Hyperactiv and Vue.js. Patella is compatible with Chrome 5, Firefox 4, and Internet Explorer 9.
The patellar tendon is responsible for the well known "knee-jerk reaction".
Jump to one of:
Patella is available via npm:
$ npm install patella
// ECMAScript module environments
import { observe, ignore, computed, dispose } from "patella";
// CommonJS environments
const { observe, ignore, computed, dispose } = require("patella");
Or, for people working without a bundler, it can be included from UNPKG:
<script src="https://www.unpkg.com/patella"></script>
<script>
Patella.observe({});
Patella.ignore({});
Patella.computed(() => {});
Patella.dispose(() => {});
</script>
Various other Patella builds are available in the dist folder, including sourcemaps and minified versions. Minification is performed using both Terser and UglifyJS using custom configurations designed for a balance of speed and size (Patella is a micro-library at 900~ bytes gzipped).
Patella provides functions for observing object mutations and acting on those mutations automatically.
Possibly the best way to learn is by example, so let's take a page out of Vue.js's guide and make a button that counts how many times it has been clicked using Patella's observe(object)
and computed(func)
:
<h1>Click Counter</h1>
<button onclick="model.clicks++"></button>
<script>
const $button = document.getElementsByTagName("button")[0];
const model = Patella.observe({
clicks: 0
});
Patella.computed(() => {
$button.innerText = model.clicks
? `I've been clicked ${model.clicks} times`
: "Click me!";
});
</script>
View the full source or try it on JSFiddle.
Notice how in the above example, the <button>
doesn't do any extra magic to change its text when clicked; it just increments the model's click counter, which is "connected" to the button's text in the computed function.
Now let's try doing some math, here's a snippet that adds and multiplies two numbers:
const calculator = Patella.observe({
left: 1,
right: 1,
sum: 0,
product: 0
});
// Connect left, right -> sum
Patella.computed(() => calculator.sum = calculator.left + calculator.right);
// Connect left, right -> product
Patella.computed(() => calculator.product = calculator.left * calculator.right);
calculator.left = 2;
calculator.right = 10;
console.log(calculator.sum, calculator.product); // Output: 12 20
calcuator.left = 3;
console.log(calculator.sum, calculator.product); // Output: 13 30
Pretty cool, right? Patella's main goal is to be as simple as possible; you only need two functions to build almost anything.
Jump to one of:
<h1>Concatenator</h1>
<input type="text" oninput="model.first = value" placeholder="Enter some"/>
<input type="text" oninput="model.second = value" placeholder="text!"/>
<h3 id="output"></h3>
<script>
const $output = document.getElementById("output");
const model = Patella.observe({
first: "",
second: "",
full: ""
});
Patella.computed(() => {
model.full = model.first + " " + model.second;
});
Patella.computed(() => {
$output.innerText = model.full;
});
</script>
View the full source or try it on JSFiddle.
<h1>Debounced Search</h1>
<input type="text" oninput="model.input = value" placeholder="Enter your debounced search"/>
<h3 id="search"></h3>
<script>
const $search = document.getElementById("search");
const model = Patella.observe({
input: "",
search: ""
});
Patella.computed(() => {
search.innerText = model.search;
});
let timeoutID;
Patella.computed(() => {
const input = model.input;
if (timeoutID) clearTimeout(timeoutID);
timeoutID = setTimeout(() => {
model.search = input;
}, 1000);
});
</script>
View the full source or try it on JSFiddle.
<main id="app">
<h1>Pony Browser</h1>
<select></select>
<ul></ul>
<input type="text" placeholder="Add another pony"/>
</main>
<script>
// Find elements
const $app = document.getElementById("app");
const [, $select, $list, $input] = $app.children;
// Declare model
const model = Patella.observe({
/* Truncated, find full source in ./examples/pony.html */
});
// Populate <select>
for (const [value, { name }] of Object.entries(model.characterSets)) {
const $option = document.createElement("option");
$option.value = value;
$option.innerText = name;
$select.appendChild($option);
}
// Connect model.selected.key -> model.selected.current
Patella.computed(() => {
model.selected.current = model.characterSets[model.selected.key];
});
// Connect model.selected.current.members -> <ul>
Patella.computed(() => {
$list.innerHTML = "";
for (const member of model.selected.current.members) {
const $entry = document.createElement("li");
$entry.innerText = member;
$list.appendChild($entry);
}
});
// Connect <select> -> model.selected.key
$select.addEventListener("change", () => {
model.selected.key = $select.value;
});
// Connect <input> -> model.selected.current.members
$input.addEventListener("keyup", ({ key }) => {
if (key !== "Enter") return;
const currentSet = model.selected.current;
currentSet.members = [
...currentSet.members,
$input.value
];
$input.value = "";
});
</script>
View the full source or try it on JSFiddle.
// Setting up some reactive objects that contain some data about a US president...
// Disclaimer: I am not an American :P
const person = Patella.observe({
name: { first: "George", last: "Washington" },
age: 288
});
const account = Patella.observe({
user: "big-george12",
password: "IHateTheQueen!1"
});
// Declare that we will output a log message whenever person.name.first, account.user, or person.age are updated
Patella.computed(() => console.log(
`${person.name.first}'s username is ${account.user} (${person.age} years old)`
)); // Output: George's username is big-george12 (288 years old)
// Changing reactive properties will only run computed functions that depend on them
account.password = "not-telling"; // Does not output (no computed function depends on this)
// All operators work when updating properties
account.user += "3"; // Output: George's username is big-george123 (288 years old)
person.age++; // Output: George's username is big-george123 (289 years old)
// You can even replace objects entirely
// This will automatically observe this new object and will still trigger dependant computed functions
// Note: You should ideally use ignore or dispose to prevent depending on objects that get replaced, see pitfalls
person.name = {
first: "Abraham",
last: "Lincoln"
}; // Output: Abraham's username is big-george123 (289 years old)
person.name.first = "Thomas"; // Output: Thomas's username is big-george123 (289 years old)
// Create our nums object, with some default values for properties that will be computed
const nums = Patella.observe({
a: 33, b: 23, c: 84,
x: 0,
sumAB: 0, sumAX: 0, sumCX: 0,
sumAllSums: 0
});
// Declare that (x) will be equal to (a + b + c)
Patella.computed(() => nums.x = nums.a + nums.b + nums.c);
// Declare that (sumAB) will be equal to (a + b)
Patella.computed(() => nums.sumAB = nums.a + nums.b);
// Declare that (sumAX) will be equal to (a + x)
Patella.computed(() => nums.sumAX = nums.a + nums.x);
// Declare that (sumCX) will be equal to (c + x)
Patella.computed(() => nums.sumCX = nums.c + nums.x);
// Declare that (sumAllSums) will be equal to (sumAB + sumAX + sumCX)
Patella.computed(() => nums.sumAllSums = nums.sumAB + nums.sumAX + nums.sumCX);
// Now lets check the (sumAllSums) value
console.log(nums.sumAllSums); // Output: 453
// Notice that when we update one value ...
nums.c += 2;
// ... all the other values update! (since we declared them as such)
console.log(nums.sumAllSums); // Output: 459
Patella uses JavaScript's getters and setters to make all the reactivity magic possible, which comes with some tradeoffs that other libraries like Hyperactiv (which uses Proxy) don't have to deal with. This section details some of the stuff to look out for when using Patella in your applications.
const object = Patella.observe({ x: 10, y: 20 });
Patella.computed(function one() {
if (object.x > 20) object.y++;
});
Patella.computed(function two() {
if (object.y > 20) object.x++;
});
object.x = 25;
// Uncaught Error: Computed queue overflow! Last 10 functions in the queue:
// 1993: one
// 1994: two
// 1995: one
// 1996: two
// 1997: one
// 1998: two
// 1999: one
// 2000: two
// 2001: one
// 2002: two
// 2003: one
const object = Patella.observe({
array: [1, 2, 3]
});
Patella.computed(() => console.log(object.array)); // Output: 1,2,3
object.array[2] = 4; // No output, arrays are not reactive!
object.array.push(5); // Still no output, as Patella does not replace array methods
// If you want to use arrays, do it like this:
// 1. Run your operations
object.array[2] = 3;
object.array[3] = 4;
object.array.push(5);
// 2. Then set the array to itself
object.array = object.array; // Output: 1,2,3,4,5
const object = Patella.observe({ x: 10 });
object.y = 20;
Patella.computed(() => console.log(object.x)); // Output: 10
Patella.computed(() => console.log(object.y)); // Output: 20
object.x += 2; // Output: 12
object.y += 2; // No output, as this property was added after observation
Patella.observe(object);
object.y += 2; // Still no output, as objects cannot be re-observed
const object = { a: 20 };
const prototype = { b: 10 };
Object.setPrototypeOf(object, prototype);
Patella.observe(object);
Patella.computed(() => console.log(object.a)); // Output: 10
Patella.computed(() => console.log(object.b)); // Output: 20
object.a = 15; // Output: 15
object.b = 30; // No output, as this isn't an actual property on the object
prototype.b = 36; // No output, as prototypes are not made reactive by observe
Patella.observe(prototype);
prototype.b = 32; // Output: 32
const object = { x: 1 };
Object.defineProperty(object, "y", {
configurable: true,
enumerable: false,
value: 2
});
Object.defineProperty(object, "z", {
configurable: false,
enumerable: true,
value: 3
});
Patella.observe(object);
Patella.computed(() => console.log(object.x)); // Output: 1
Patella.computed(() => console.log(object.y)); // Output: 2
Patella.computed(() => console.log(object.z)); // Output: 3
object.x--; // Output: 0
object.y--; // No output as this property is non-enumerable
object.z--; // No output as this property is non-configurable
const object = {};
Object.defineProperty(object, "val", {
configurable: true,
enumerable: true,
writable: false,
value: 10
});
object.val = 20; // Does nothing
console.log(object.val); // Output: 10
Patella.observe(object);
object.val = 20; // Works because the property descriptor has been overwritten
console.log(object.val); // Output: 20
const object = {
get val() {
console.log("Gotten!");
return 10;
}
};
object.val; // Output: Gotten!
Patella.observe(object); // Output: Gotten!
object.val; // No output as the getter has been overwritten
__proto__
are ignoredconst object = {};
Object.defineProperty(object, "__proto__", {
configurable: true,
enumerable: true,
writable: true,
value: 10
});
Patella.observe(object);
Patella.computed(() => console.log(object.__proto__)); // Output: 10
object.__proto__++; // No output as properties named __proto__ are ignored
function observe(object)
observe
does not create a new object, it mutates the object passed into it: observe(object) === object
.
object
— Object or function to make reactiveobject
, now reactivefunction ignore(object)
observe
will do nothing.
Note that ignore
is not recursive, so subobjects can still be made reactive by calling observe
on them directly.
object
— Object or function to ignoreobject
, now permanently ignoredfunction computed(func)
func
with no arguments and records a list of all the reactive properties it accesses.
func
will then be called again whenever any of the accessed properties are mutated.
Note that if func
has been dispose
d with !!clean === false
, no operation will be performed.
func
— Function to executefunc
function dispose(func, clean)
computed
, deregistering it so that it will no longer be called whenever any of its accessed reactive properties update.
The clean
parameter controls whether calling computed
with func
will work or no-op.
func
— Function to dispose, omit to dispose the currently executing computed functionclean
— If truthy, only deregister the function from all dependencies, but allow it to be used with computed
again in the futurefunc
if func
is valid, otherwise undefined
Made with ❤ by Lua MacDougall (foxgirl.dev)
This project is licensed under MIT. More info in the LICENSE file.
"A short, permissive software license. Basically, you can do whatever you want as long as you include the original copyright and license notice in any copy of the software/source. There are many variations of this license in use." - tl;drLegal
FAQs
Patella is a library for reactive programming in JavaScript, inspired by Hyperactiv and Vue.js.
The npm package patella receives a total of 10 weekly downloads. As such, patella popularity was classified as not popular.
We found that patella demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Socket uncovers malicious packages on PyPI using Gmail's SMTP protocol for command and control (C2) to exfiltrate data and execute commands.
Product
We redesigned Socket's first logged-in page to display rich and insightful visualizations about your repositories protected against supply chain threats.
Product
Automatically fix and test dependency updates with socket fix—a new CLI tool that turns CVE alerts into safe, automated upgrades.