Effortless JavaScript Collections and Queries
Qstore is a lightweight JavaScript library designed to simplify working with collections. Whether you're dealing with arrays of objects or complex nested data structures, Qstore provides tools to search, manipulate, and manage your data with ease.
I created this project many years ago for my working tasks and as my diploma project. I needed some alternative to SQL but on the client javascript. The syntax was inspired by MongoDB. The alternative nowdays could be https://github.com/jmespath/jmespath.js
Key Features:
- Easy Collection Creation: Initialize collections from arrays or custom data formats.
- Flexible Data Queries: Search and update data using intuitive query syntax.
- Deep Searching: Perform deep searches within nested objects and arrays.
- Computed Fields: Define fields that automatically compute values based on other fields.
- Change Tracking: Keep track of changes in your collections for debugging or syncing.
- Extendable Query Language: Customize operators and functions to suit your needs.
https://holiber.github.io/activedata/examples/
Using a Script Tag:
Include the qstore.js file in your HTML:
<script src="path/to/qstore.js"></script>Using NPM:
For Node.js environments:
npm install qstoreLet's dive right in and see how Qstore can simplify your data handling.
You can initialize a collection using an array of objects:
var fruits = new Qstore([
{ type: 'apple', color: 'red', weight: 0.25, price: 1.5 },
{ type: 'pear', color: 'green', weight: 0.4, price: 2 },
{ type: 'pear', color: 'red', weight: 0.3, price: 1.8 },
{ type: 'apple', color: 'yellow', weight: 0.26, price: 1.2 },
]);Or using a reduced format with columns and rows:
var fruits = new Qstore({
columns: ['type', 'color', 'weight', 'price'],
rows: [
['apple', 'red', 0.25, 1.5],
['pear', 'green', 0.4, 2],
['pear', 'red', 0.3, 1.8],
['apple', 'yellow', 0.26, 1.2],
],
});Find all green apples:
fruits.find({ type: 'apple', color: 'green' });Find all apples and pears:
fruits.find({ type: ['apple', 'pear'] });Which fruits can be red?
fruits.getList({ color: 'red' }, 'type'); // ['apple', 'pear', 'strawberries']Find all fruits with price between $1 and $2:
fruits.find({ price: { $gte: 1, $lte: 2 } });Find fruits where the price per kg is greater than $5:
fruits.find(function (item) {
return item.price / item.weight > 5;
});Add a computed field for price per kilogram:
fruits.addFields({
name: 'pricePerKg',
compute: function (fruit) {
return (fruit.price / fruit.weight).toFixed(2);
},
});Now each item in the collection has a pricePerKg field.
Group fruits by color:
var groupedFruits = fruits.groupBy('color');Searches the collection and returns an array of items that match the query.
Examples:
// Find all red fruits
fruits.find({ color: 'red' });
// Find all apples or pears
fruits.find({ type: ['apple', 'pear'] });
// Find all fruits with price less than $2
fruits.find({ price: { $lt: 2 } });
// Using regular expressions to find fruits starting with 'p'
fruits.find({ type: /^p/ }); // Matches 'pear' and 'pineapple'You can perform deep searches within nested objects:
var usersMessages = new Qstore({
columns: ['text', 'subject', 'user'],
rows: [
[
'Hi',
'new year',
{ id: 1, name: 'Bob', company: { name: 'IBM', phone: '+9999' } },
],
[
'Happy new year!',
'new year',
{ id: 2, name: 'Kate', company: { name: 'Microsoft', phone: '+8888' } },
],
],
});
// Find messages from users working at IBM
usersMessages.find({ 'user.company.name': 'IBM' });Search within arrays inside your data:
var costumes = new Qstore([
{
name: 'policeman',
items: [
{ name: 'tie', color: 'black' },
{ name: 'cap', color: 'blue' },
],
},
{ name: 'fireman', items: [{ name: 'helmet', color: 'yellow' }] },
]);
// Find costumes that include a 'helmet'
costumes.find({ items: { name: 'helmet' } });You can create aliases for fields when selecting data:
// Select 'text' and 'user.name' as 'userName'
messages.find({ subject: 'new year' }, ['text', 'user.name:userName']);
// Using alias map
messages.find({ subject: 'new year' }, { text: true, userName: 'user.name' });Result:
[
{ text: 'Happy new year!', userName: 'Kate' },
{ text: 'Anyone want to dance?', userName: 'James' },
];You can compare fields within the same item:
var diet = new Qstore({
columns: ['month', 'breakfast', 'dinner'],
rows: [
[
'April',
{ calories: 400, food: 'egg' },
{ calories: 300, food: 'soup' },
],
[
'May',
{ calories: 300, food: 'bacon' },
{ calories: 500, food: 'steak' },
],
],
});
// Find days where dinner calories are less than breakfast calories
diet.find({ 'dinner.calories': { $lt: '$.breakfast.calories' } });Combine queries using logical operators:
var filter1 = { type: 'apple' };
var filter2 = { color: 'red' };
// Find items that are apples OR red
var orFilter = [filter1, filter2];
// Find items that are apples AND red
var andFilter = { $and: [filter1, filter2] };Qstore provides several built-in operators for advanced querying:
$eq: Equal to$ne: Not equal to$gt: Greater than$lt: Less than$gte: Greater than or equal to$lte: Less than or equal to$like: String contains$has: Checks if a value exists in an array, object, or string
Example of $has Operator:
var clothes = new Qstore([
{ name: 'skirt', sizes: ['XS', 'S', 'XL'] },
{ name: 'jeans', sizes: ['M', 'XXL'] },
]);
// Find clothes that have size 'XS'
clothes.find({ sizes: { $has: 'XS' } });You can add your own operators to extend the query language:
// Add "isInt" operator to check for integer values
Qstore.addOperator('isInt', function (left, right) {
var isInt = left % 1 === 0;
return right ? isInt : !isInt;
});
// Find fruits with integer prices
fruits.find({ price: { $isInt: true } });Use functions in queries for dynamic computations.
Built-in Functions:
$length: Length of an array or string$first: First item of an array or string$min: Minimum value in an array$max: Maximum value in an array$upper: Converts a string to uppercase$lower: Converts a string to lowercase
Example:
// Find users with more than 2 friends
users.find({ 'friends.$length': { $gt: 2 } });
// Select user name and number of friends
users.find(true, ['name', 'friends.$length:friendsCount']);Group your data based on one or more fields.
Example:
// Group fruits by type
var groupedFruits = fruits.groupBy('type');Result:
Each group contains an _g property with the grouped items.
// Add a new fruit
fruits.add({ type: 'banana', color: 'yellow', weight: 0.2, price: 1.0 });// Increase the price of all apples by $0.5
fruits.update({ type: 'apple' }, function (item) {
return { price: item.price + 0.5 };
});// Remove all fruits that are red
fruits.remove({ color: 'red' });Track changes in your collection to sync with a database or for undo functionality.
Returns a collection of changes made since the last commit.
var changes = fruits.getChanges();Example:
// Get a list of indices of removed items
var removedIndices = fruits
.getChanges()
.search({ action: 'remove' })
.getList('source.idx');Commits the changes, clearing the change history.
fruits.commit();Reverts the collection to the state at the last commit.
fruits.rollback();If you don't need change tracking, you can enable soft mode:
fruits.setSoftMode(true);Returns the number of items in the collection.
var totalFruits = fruits.size();Compresses the collection data for efficient storage or transmission.
var packedFruits = fruits.pack();Example:
var apples = fruits.pack({ type: 'apple' }, ['idx', 'weight', 'price']);Returns a deep copy of the collection.
var fruitsCopy = fruits.getCopy();Listen to collection events for reactive programming.
Sets a listener function that reacts to collection events like 'change', 'commit', or 'sort'.
fruits.setListener(function (eventName, data, collection) {
if (eventName === 'change' && data.action === 'update') {
var changes = data.changes.find({ 'source.type': 'apple', 'patch.color': { $ne: undefined } });
changes.forEach(function (change) {
console.log(
'An apple changed color from ' + change.source.color + ' to ' + change.patch.color
);
});
}
});var collection = new Qstore(arrayOfObjects);var collection = new Qstore({
columns: ['column1', 'column2'],
rows: [
['value1', 'value2'],
['value3', 'value4'],
],
});Returns all objects matching the query.
query: The search criteria.fields: (Optional) Array of field names to include in the result.options: (Optional) Additional options likelimit.
Examples:
// Find all red fruits
fruits.find({ color: 'red' });
// Find first two apples
fruits.find({ type: 'apple' }, true, { limit: 2 });Same as .find but returns a new Qstore collection.
Returns the first object that matches the query.
var firstApple = fruits.findOne({ type: 'apple' });Static method to search within an array.
Qstore.findIn(arrayOfObjects, { key: 'value' });Checks if an object matches the query.
Qstore.test({ type: 'apple', color: 'red' }, { color: 'red' }); // trueReturns a list of unique values for a specified field.
// List of all fruit colors
fruits.getList('color'); // ['red', 'green', 'yellow']
// List of types for red fruits
fruits.getList({ color: 'red' }, 'type'); // ['apple', 'strawberries']Applies a function to each item in the collection.
fruits.each(function (item, index) {
console.log('Fruit #' + index + ' is a ' + item.type);
});$eq: Equal to$ne: Not equal to$gt: Greater than$lt: Less than$gte: Greater than or equal to$lte: Less than or equal to$like: Contains substring$has: Contains value in array, object, or string
Add custom operators using:
Qstore.addOperator('operatorName', function (left, right) {
// Your logic here
});$length: Length of an array or string$first: First item in an array or string$min: Minimum value in an array$max: Maximum value in an array$upper: Convert string to uppercase$lower: Convert string to lowercase$toNumber: Convert to number$toString: Convert to string
// Find users with no friends
users.find({ 'friends.$length': 0 });
// Get list of first friends' names
users.getList('friends.$first');Add custom functions using:
Qstore.addFunction('functionName', function (value) {
// Your logic here
});Adds new items to the collection.
fruits.add({ type: 'orange', color: 'orange', weight: 0.3, price: 1.2 });Updates items in the collection.
// Make all green fruits red
fruits.update({ color: 'green' }, { color: 'red' });Updates the collection using an array of patches.
var patches = [
{ id: 1, connected: true },
{ id: 2, connected: false },
];
users.patch(patches, 'id');Removes items from the collection.
// Remove messages without an author
messages.remove({ author: undefined });Adds new fields to the collection.
fruits.addFields([
{ name: 'season', default: 'summer' },
{
name: 'pricePerKg',
compute: function (item) {
return item.price / item.weight;
},
},
]);Forces recomputation of computed fields.
Removes fields from the collection.
// Remove the 'weight' field
fruits.removeFields('weight');Sorts the collection.
// Sort by price ascending
fruits.sort({ fieldName: 'price', order: 'asc' });
// Sort by type ascending, then price descending
fruits.sort([
{ fieldName: 'type', order: 'asc' },
{ fieldName: 'price', order: 'desc' },
]);Groups the collection based on specified fields.
// Group shops by country and city
var shopsGrouped = shops.groupBy(['country', 'city']);Creates a map of the collection indexed by specified fields.
// Index users by 'id'
var usersById = users.indexBy('id');Similar to .indexBy but always wraps values in an array.
Returns a collection of changes.
Returns a map of changes grouped by action.
Commits the changes.
Reverts the changes.
Enables or disables soft mode.
Returns the number of items.
Compresses the collection data.
Unpacks compressed data into a collection.
Returns a deep copy of the collection.
Sets a listener for collection events.
Event Names:
changecommitsort
var fruits = new Qstore({
columns: ['type', 'color', 'weight', 'price'],
rows: [
['apple', 'red', 0.25, 1.5],
['pear', 'green', 0.4, 2],
['pear', 'red', 0.3, 1.8],
['apple', 'yellow', 0.26, 1.2],
['pineapple', 'yellow', 1, 4],
['banana', 'yellow', 0.3, 1.5],
['melon', 'yellow', 3, 3],
['watermelon', 'green', 10, 5],
['apple', 'green', 0.24, 1],
['strawberries', 'red', 0.1, 0.2],
],
});var usersMessages = new Qstore({
columns: ['text', 'subject', 'user'],
rows: [
[
'Hi',
'new year',
{ id: 1, name: 'Bob', company: { name: 'IBM', phone: '+9999' } },
],
[
'Happy new year!',
'new year',
{ id: 2, name: 'Kate', company: { name: 'Microsoft', phone: '+8888' } },
],
['How to learn JavaScript?', 'programming', { id: 3, name: 'Stan' }],
['Anyone want to dance?', 'new year', { id: 4, name: 'James' }],
],
});var diet = new Qstore({
columns: ['month', 'breakfast', 'dinner'],
rows: [
[
'April',
{ calories: 400, food: 'egg' },
{ calories: 300, food: 'soup' },
],
[
'May',
{ calories: 300, food: 'bacon' },
{ calories: 500, food: 'steak' },
],
[
'June',
{ calories: 350, food: 'porridge' },
{ calories: 300, food: 'chicken' },
],
],
});var users = new Qstore([
{ id: 12, name: 'Bob', friends: ['Mike', 'Sam'] },
{ id: 4, name: 'Martin', friends: ['Bob'] },
{ id: 5, name: 'Mike', friends: ['Bob', 'Martin', 'Sam'] },
{ id: 10, name: 'Sam', friends: [] },
{ id: 15, name: 'Sam', friends: ['Mike'] },
]);var costumes = new Qstore([
{
name: 'policeman',
items: [
{ name: 'tie', color: 'black' },
{ name: 'cap', color: 'blue' },
],
},
{ name: 'fireman', items: [{ name: 'helmet', color: 'yellow' }] },
{ name: 'soldier', items: [{ name: 'helmet', color: 'green' }] },
{
name: 'zombie',
items: [
{ name: 'skin', color: 'green' },
{ name: 'brain', color: 'pink' },
],
},
]);var shops = new Qstore({
columns: ['country', 'city', 'address'],
rows: [
['UK', 'London', 'Mace St. 5'],
['UK', 'York', 'Temple Ave. 10'],
['France', 'Paris', 'De Rivoli St. 20'],
['France', 'Paris', 'Pelleport St. 3'],
['Germany', 'Dresden', 'Haydn St. 2'],
['Germany', 'Berlin', 'Bornitz St. 50'],
['Germany', 'Munich', 'Eva St. 12'],
['Russia', 'Vladivostok', 'Stroiteley St. 9'],
],
});Qstore is open-source and released under the MIT License.