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

adaka

Package Overview
Dependencies
Maintainers
1
Versions
13
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

adaka - npm Package Compare versions

Comparing version 0.0.8 to 0.0.9

21

CHANGELOG.md
# Changelog
## 0.0.9 / 2024-02-28
**New**
- Simplify `Selector` API to use common method naming and configuration.
- Validate projection expression immediately on selector creation.
- Support querying the entire object using empty expression.
**Fixes**
- Improve extracting dependent fields in projection and query expressions.
## 0.0.8 / 2024-02-26
**New**
- Replace `boolean` with `UpdateResult` after an update operation.
## 0.0.7 / 2023-09-27
- fix `notifyAll` semantics to behave correctly for conditions.
## 0.0.6 / 2023-09-26
- Pin peer dependency as `mingo@6.x.x`.

@@ -15,11 +31,15 @@ - Default clone mode to "copy".

## 0.0.5 / 2023-06-26
- Add `mingo@6.4.2` as peerDependency.
## 0.0.4 / 2023-06-25
- Upgrade to `mingo@6.4.2` for `Context` support.
## 0.0.3 / 2023-06-09
- Load only basic mingo operators by default.
## 0.0.2 / 2023-06-08
- Upgrade to `mingo@6.4.1` for full update operator support.

@@ -29,2 +49,3 @@ - Default to not cloning inputs on update.

## 0.0.1 / 2023-06-04
- Moved React integration to separate package [reack-adaka](https://www.npmjs.com/package/react-adaka).

@@ -31,0 +52,0 @@

133

dist/cjs/_internal/store.js

@@ -33,3 +33,3 @@ "use strict";

this.selectors = new Set();
this.hashIndex = new Map();
this.cache = new Map();
// signals for notifying selectors of changes.

@@ -44,2 +44,3 @@ this.signals = new Map();

* Creates a new observable for a view of the state.
*
* @param projection Fields of the state to view. Expressed as MongoDB projection query.

@@ -50,2 +51,8 @@ * @param condition Conditions to match for a valid state view. Expressed as MongoDB filter query.

select(projection, condition = {}) {
// disallow exclusions.
for (const v of Object.values(projection)) {
// validate projection expression immediately catch errors early.
(0, util_1.assert)(v !== 0 && v !== false, "field exclusion not allowed");
(0, util_1.assert)((0, util_2.isProjectExpression)(v), `selector projection value must be an object, array, true, or 1: '${JSON.stringify(projection)}'`);
}
// ensure not modifiable. some guards for sanity

@@ -56,16 +63,24 @@ condition = (0, util_2.cloneFrozen)(condition);

const hash = (0, util_1.stringify)({ c: condition, p: projection });
if (this.hashIndex.has(hash)) {
if (this.cache.has(hash)) {
// anytime we pull selector from cache, we should mark it as dirty.
return this.hashIndex.get(hash);
return this.cache.get(hash);
}
// get expected paths to monitor for changes. use fields in both projection and condition
const [cond, proj] = [condition, projection].map(o => Array.from((0, util_2.extractKeyPaths)(o)));
const expected = Array.from(new Set(cond.concat(proj)));
// get expected paths to monitor for changes.
// extract paths in condition expression
const expected = (0, util_2.getDependentPaths)(Object.entries(condition).reduce((m, [k, v]) => {
m[k] = (0, util_1.normalize)(v);
return m;
}, {}), { includeRootFields: true });
// extract path in projection expression
(0, util_2.getDependentPaths)(projection, { includeRootFields: false }).forEach(s => expected.add(s));
// create and add a new selector
const selector = new Selector(this.state, projection, new mingo_1.Query(condition, this.queryOptions), this.queryOptions);
const pred = util_2.sameAncestor.bind(null, expected);
// if no field is specified, select everything.
const pred = !expected.size
? () => true
: util_2.sameAncestor.bind(null, expected);
// function to detect changes and notify observers
const signal = (changed) => {
const isize = new Set(changed.concat(expected)).size; // intersection
const usize = expected.length + changed.length; // union
const isize = new Set(changed.concat(Array.from(expected))).size; // intersection
const usize = expected.size + changed.length; // union
const notify = isize < usize || changed.some(pred);

@@ -79,3 +94,3 @@ // notify listeners only when change is detected

this.signals.set(selector, signal);
this.hashIndex.set(hash, selector);
this.cache.set(hash, selector);
return selector;

@@ -85,3 +100,4 @@ }

* Dispatches an update expression to mutate the state. Triggers a notification to relevant selectors only.
* @param {RawObject} expr Update expression as a MongoDB update query.
*
* @param {UpdateExpression} expr Update expression as a MongoDB update query.
* @param {Array<RawObject>} arrayFilters Array filter expressions to filter elements to update.

@@ -102,7 +118,6 @@ * @param {RawObject} condition Condition to check before applying update.

const signal = this.signals.get(selector);
// take a snapshot of the size before sending the notification.
// this accounts for subscribers that may be removed after notification either from running only once or throwing an error.
const increment = selector.size;
// record the number of listeners before signalling which may remove a listener if it throws or is configured to run once.
const size = selector.size;
if (signal(fields)) {
notifyCount += increment;
notifyCount += size;
}

@@ -176,14 +191,15 @@ }

if (!(0, util_1.isEqual)(prev, val)) {
for (const cb of this.listeners) {
for (const f of this.listeners) {
/*eslint-disable*/
try {
cb(val);
f(val);
}
catch (_a) {
this.listeners.delete(cb);
// on error unsubscribe listener
this.listeners.delete(f);
}
finally {
if (this.onceOnly.has(cb)) {
this.listeners.delete(cb);
this.onceOnly.delete(cb);
// if runOnce, cleanup afterwards
if (this.onceOnly.delete(f)) {
this.listeners.delete(f);
}

@@ -200,62 +216,43 @@ }

this.listeners.clear();
this.onceOnly.clear();
}
/**
* Register a listener to be notified about state updates.
* @param listener The observer function to receive data.
* @returns {Callback} Function to unsubscribe listener.
* Subscribe a listener to be notified about state updates.
*
* @param listener The function to receive new data on update.
* @returns {Unsubscribe} Function to unsubscribe listener.
*/
listen(listener) {
subscribe(listener, options) {
// check if we are reregistering the same observer
if (this.onceOnly.has(listener)) {
throw new Error(`Already subscribed to listen once.`);
if (this.listeners.has(listener)) {
throw new Error("Listener already subscribed.");
}
if (!this.listeners.has(listener)) {
this.listeners.add(listener);
// setup to throw after first run.
if (options && options.runOnce) {
this.onceOnly.add(listener);
}
return () => {
this.listeners.add(listener);
const unsub = () => {
this.onceOnly.delete(listener);
this.listeners.delete(listener);
};
}
/**
* Like listen() but also immediately invoke the listener if a value is pending for selector.
* @param listener The observer function to receive data.
* @returns {Callback} Function to unsubscribe listener.
*/
listenNow(listener) {
// check if we are reregistering the same observer
const unsub = this.listen(listener);
// immediately invoke
const val = this.get();
if (val !== undefined) {
try {
listener(val);
if (options && options.runImmediately) {
// immediately invoke
const val = this.get();
if (val !== undefined) {
try {
listener(val);
}
catch (e) {
unsub();
throw e;
}
finally {
if (this.onceOnly.has(listener))
unsub();
}
}
catch (e) {
unsub();
throw e;
}
}
return unsub;
}
/**
* Like listen(), but invokes the listener only once and then automatically removes it.
* @param listener The observer functino to receive data.
* @returns {Callback} Function to unsubscribe listener explicitly before it is called.
*/
listenOnce(listener) {
// check if we are reregistering the same observer
if (this.listeners.has(listener) && !this.onceOnly.has(listener)) {
throw new Error(`Already subscribed to listen repeatedly.`);
}
if (!this.onceOnly.has(listener)) {
this.listeners.add(listener);
this.onceOnly.add(listener);
}
return () => {
this.listeners.delete(listener);
this.onceOnly.delete(listener);
};
}
}
exports.Selector = Selector;
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.cloneFrozen = exports.sameAncestor = exports.extractKeyPaths = void 0;
exports.cloneFrozen = exports.sameAncestor = exports.getDependentPaths = exports.isProjectExpression = void 0;
const projectionOperators = __importStar(require("mingo/operators/projection"));
const updateOperators = __importStar(require("mingo/operators/update"));
const util_1 = require("mingo/util");

@@ -15,7 +40,20 @@ const KEYED_OPERATORS_MAP = Object.freeze({

});
const peekOperator = (o) => {
const keys = (0, util_1.isObject)(o) && Object.keys(o);
return keys && keys.length === 1 && (0, util_1.isOperator)(keys[0]) && keys[0];
};
/** checks that value is a valid project expression. */
const isProjectExpression = (o) => o === 1 ||
o === true ||
!!peekOperator(o) ||
((0, util_1.isString)(o) && o.startsWith("$"));
exports.isProjectExpression = isProjectExpression;
/**
* Extract all valid field paths used in the expression.
* Extract all dependent paths used in the expressions for project, match and update expressions.
*
* @param expr The expression.
* @param options Options to customize path retrieval.
*/
function extractKeyPaths(expr, parent) {
function getDependentPaths(expr, options = { includeRootFields: false }) {
const parent = options.__parent;
const result = new Set();

@@ -25,8 +63,9 @@ switch ((0, util_1.getType)(expr)) {

// exclude variables which all begin with "$$"
if (expr.startsWith("$$"))
if (expr.startsWith("$$")) {
return result;
}
else if (expr.startsWith("$")) {
result.add(expr.substring(1));
}
else if (parent) {
else if (parent && !(0, util_1.isOperator)(parent)) {
result.add(parent);

@@ -37,3 +76,3 @@ }

expr
.map(v => extractKeyPaths(v, parent))
.map(v => getDependentPaths(v, options))
.forEach(s => s.forEach(v => result.add(v)));

@@ -47,5 +86,6 @@ break;

// handle top-level boolean operators ($and, $or,..) and $expr.
if (key.startsWith("$")) {
if ((0, util_1.isOperator)(key)) {
let val2 = val;
// handle operators with keyed arguments.
// this ensure we process each leaf object correctly and don't treat the leaf itself as a field in our state.
const opts = KEYED_OPERATORS_MAP[key];

@@ -57,7 +97,44 @@ if (opts && typeof val === "object") {

}
extractKeyPaths(val2, parent).forEach(v => result.add(v));
getDependentPaths(val2, Object.assign(Object.assign({}, options), {
// for update expressions send the key as the parent, since they are always top-level.
__parent: !parent && (0, util_1.has)(updateOperators, key) ? key : parent })).forEach(v => result.add(v));
}
else {
const ancestor = parent ? parent + "." + key : key;
extractKeyPaths(val, ancestor).forEach(v => result.add(v));
// handle update operators first. we pass the operator as the parent since that should always be the top-level field.
// extracting fields for update expressions is not used yet, but may be leveraged for optimizations latter.
if ((0, util_1.isOperator)(parent) && (0, util_1.has)(updateOperators, parent)) {
getDependentPaths(val, Object.assign(Object.assign({}, options), { __parent: key })).forEach(v => result.add(v));
continue;
}
if (!options.includeRootFields &&
!parent &&
!(0, exports.isProjectExpression)(val)) {
// skip if not a valid project expression or ignoring root fields.
continue;
}
let ancestor = parent ? parent + "." + key : key;
// Since this method is written to support both $project and $match expressions we need to specially handle projection operators $elemMatch and $slice.
// This avoids treating all top-level fields as valid within the state object which would not be true for projections based on expression operators.
// A user may reuse field names in the state object for their selectors. We want to avoid notifying listeners if the actual dependent state fields have not changed.
// To find out whether we have a projection operator, we peek into the value object to detect and operator and also check the current field is top-level.
// If the operator is not a projection and the field is top-level, we don't record it as a valid state field and pass an empty value further down the extractor.
const op = peekOperator(val);
const valObj = val;
if (op && !options.includeRootFields) {
if (
// expr is not a projection operator and not nested so the top-level field must be a new alias.
(!parent && !(0, util_1.has)(projectionOperators, op)) ||
// $slice has two flavours in MongoDB, so we need to make sure we are looking at the correct one when first condition fails.
// if nested (i.e parent exists), then we know we are using the $slice from the expression operators.
(parent && op === "$slice") ||
// if no parent but op is $slice, we need to check the actual type to determine.
// validates for $slice as an expression operator.
(!parent &&
op === "$slice" &&
(0, util_1.isArray)(valObj["$slice"]) &&
!(0, util_1.isNumber)(valObj["$slice"][0]))) {
ancestor = undefined;
}
}
getDependentPaths(val, Object.assign(Object.assign({}, options), { __parent: ancestor })).forEach(v => result.add(v));
}

@@ -73,3 +150,3 @@ }

}
exports.extractKeyPaths = extractKeyPaths;
exports.getDependentPaths = getDependentPaths;
/**

@@ -76,0 +153,0 @@ * Determines if the selector has a common ancestor with any other in the set.

@@ -6,4 +6,4 @@ import { Query } from "mingo";

import { createUpdater } from "mingo/updater";
import { cloneDeep, isEqual, stringify } from "mingo/util";
import { cloneFrozen, extractKeyPaths, sameAncestor } from "./util";
import { assert, cloneDeep, isEqual, normalize, stringify } from "mingo/util";
import { cloneFrozen, getDependentPaths, isProjectExpression, sameAncestor } from "./util";
const NONE = Symbol();

@@ -30,3 +30,3 @@ /**

this.selectors = new Set();
this.hashIndex = new Map();
this.cache = new Map();
// signals for notifying selectors of changes.

@@ -41,2 +41,3 @@ this.signals = new Map();

* Creates a new observable for a view of the state.
*
* @param projection Fields of the state to view. Expressed as MongoDB projection query.

@@ -47,2 +48,8 @@ * @param condition Conditions to match for a valid state view. Expressed as MongoDB filter query.

select(projection, condition = {}) {
// disallow exclusions.
for (const v of Object.values(projection)) {
// validate projection expression immediately catch errors early.
assert(v !== 0 && v !== false, "field exclusion not allowed");
assert(isProjectExpression(v), `selector projection value must be an object, array, true, or 1: '${JSON.stringify(projection)}'`);
}
// ensure not modifiable. some guards for sanity

@@ -53,16 +60,24 @@ condition = cloneFrozen(condition);

const hash = stringify({ c: condition, p: projection });
if (this.hashIndex.has(hash)) {
if (this.cache.has(hash)) {
// anytime we pull selector from cache, we should mark it as dirty.
return this.hashIndex.get(hash);
return this.cache.get(hash);
}
// get expected paths to monitor for changes. use fields in both projection and condition
const [cond, proj] = [condition, projection].map(o => Array.from(extractKeyPaths(o)));
const expected = Array.from(new Set(cond.concat(proj)));
// get expected paths to monitor for changes.
// extract paths in condition expression
const expected = getDependentPaths(Object.entries(condition).reduce((m, [k, v]) => {
m[k] = normalize(v);
return m;
}, {}), { includeRootFields: true });
// extract path in projection expression
getDependentPaths(projection, { includeRootFields: false }).forEach(s => expected.add(s));
// create and add a new selector
const selector = new Selector(this.state, projection, new Query(condition, this.queryOptions), this.queryOptions);
const pred = sameAncestor.bind(null, expected);
// if no field is specified, select everything.
const pred = !expected.size
? () => true
: sameAncestor.bind(null, expected);
// function to detect changes and notify observers
const signal = (changed) => {
const isize = new Set(changed.concat(expected)).size; // intersection
const usize = expected.length + changed.length; // union
const isize = new Set(changed.concat(Array.from(expected))).size; // intersection
const usize = expected.size + changed.length; // union
const notify = isize < usize || changed.some(pred);

@@ -76,3 +91,3 @@ // notify listeners only when change is detected

this.signals.set(selector, signal);
this.hashIndex.set(hash, selector);
this.cache.set(hash, selector);
return selector;

@@ -82,3 +97,4 @@ }

* Dispatches an update expression to mutate the state. Triggers a notification to relevant selectors only.
* @param {RawObject} expr Update expression as a MongoDB update query.
*
* @param {UpdateExpression} expr Update expression as a MongoDB update query.
* @param {Array<RawObject>} arrayFilters Array filter expressions to filter elements to update.

@@ -99,7 +115,6 @@ * @param {RawObject} condition Condition to check before applying update.

const signal = this.signals.get(selector);
// take a snapshot of the size before sending the notification.
// this accounts for subscribers that may be removed after notification either from running only once or throwing an error.
const increment = selector.size;
// record the number of listeners before signalling which may remove a listener if it throws or is configured to run once.
const size = selector.size;
if (signal(fields)) {
notifyCount += increment;
notifyCount += size;
}

@@ -172,14 +187,15 @@ }

if (!isEqual(prev, val)) {
for (const cb of this.listeners) {
for (const f of this.listeners) {
/*eslint-disable*/
try {
cb(val);
f(val);
}
catch (_a) {
this.listeners.delete(cb);
// on error unsubscribe listener
this.listeners.delete(f);
}
finally {
if (this.onceOnly.has(cb)) {
this.listeners.delete(cb);
this.onceOnly.delete(cb);
// if runOnce, cleanup afterwards
if (this.onceOnly.delete(f)) {
this.listeners.delete(f);
}

@@ -196,61 +212,42 @@ }

this.listeners.clear();
this.onceOnly.clear();
}
/**
* Register a listener to be notified about state updates.
* @param listener The observer function to receive data.
* @returns {Callback} Function to unsubscribe listener.
* Subscribe a listener to be notified about state updates.
*
* @param listener The function to receive new data on update.
* @returns {Unsubscribe} Function to unsubscribe listener.
*/
listen(listener) {
subscribe(listener, options) {
// check if we are reregistering the same observer
if (this.onceOnly.has(listener)) {
throw new Error(`Already subscribed to listen once.`);
if (this.listeners.has(listener)) {
throw new Error("Listener already subscribed.");
}
if (!this.listeners.has(listener)) {
this.listeners.add(listener);
// setup to throw after first run.
if (options && options.runOnce) {
this.onceOnly.add(listener);
}
return () => {
this.listeners.add(listener);
const unsub = () => {
this.onceOnly.delete(listener);
this.listeners.delete(listener);
};
}
/**
* Like listen() but also immediately invoke the listener if a value is pending for selector.
* @param listener The observer function to receive data.
* @returns {Callback} Function to unsubscribe listener.
*/
listenNow(listener) {
// check if we are reregistering the same observer
const unsub = this.listen(listener);
// immediately invoke
const val = this.get();
if (val !== undefined) {
try {
listener(val);
if (options && options.runImmediately) {
// immediately invoke
const val = this.get();
if (val !== undefined) {
try {
listener(val);
}
catch (e) {
unsub();
throw e;
}
finally {
if (this.onceOnly.has(listener))
unsub();
}
}
catch (e) {
unsub();
throw e;
}
}
return unsub;
}
/**
* Like listen(), but invokes the listener only once and then automatically removes it.
* @param listener The observer functino to receive data.
* @returns {Callback} Function to unsubscribe listener explicitly before it is called.
*/
listenOnce(listener) {
// check if we are reregistering the same observer
if (this.listeners.has(listener) && !this.onceOnly.has(listener)) {
throw new Error(`Already subscribed to listen repeatedly.`);
}
if (!this.onceOnly.has(listener)) {
this.listeners.add(listener);
this.onceOnly.add(listener);
}
return () => {
this.listeners.delete(listener);
this.onceOnly.delete(listener);
};
}
}

@@ -1,2 +0,4 @@

import { getType, isObject, resolve } from "mingo/util";
import * as projectionOperators from "mingo/operators/projection";
import * as updateOperators from "mingo/operators/update";
import { getType, has, isArray, isNumber, isObject, isOperator, isString, resolve } from "mingo/util";
const KEYED_OPERATORS_MAP = Object.freeze({

@@ -12,7 +14,19 @@ $cond: {

});
const peekOperator = (o) => {
const keys = isObject(o) && Object.keys(o);
return keys && keys.length === 1 && isOperator(keys[0]) && keys[0];
};
/** checks that value is a valid project expression. */
export const isProjectExpression = (o) => o === 1 ||
o === true ||
!!peekOperator(o) ||
(isString(o) && o.startsWith("$"));
/**
* Extract all valid field paths used in the expression.
* Extract all dependent paths used in the expressions for project, match and update expressions.
*
* @param expr The expression.
* @param options Options to customize path retrieval.
*/
export function extractKeyPaths(expr, parent) {
export function getDependentPaths(expr, options = { includeRootFields: false }) {
const parent = options.__parent;
const result = new Set();

@@ -22,8 +36,9 @@ switch (getType(expr)) {

// exclude variables which all begin with "$$"
if (expr.startsWith("$$"))
if (expr.startsWith("$$")) {
return result;
}
else if (expr.startsWith("$")) {
result.add(expr.substring(1));
}
else if (parent) {
else if (parent && !isOperator(parent)) {
result.add(parent);

@@ -34,3 +49,3 @@ }

expr
.map(v => extractKeyPaths(v, parent))
.map(v => getDependentPaths(v, options))
.forEach(s => s.forEach(v => result.add(v)));

@@ -44,5 +59,6 @@ break;

// handle top-level boolean operators ($and, $or,..) and $expr.
if (key.startsWith("$")) {
if (isOperator(key)) {
let val2 = val;
// handle operators with keyed arguments.
// this ensure we process each leaf object correctly and don't treat the leaf itself as a field in our state.
const opts = KEYED_OPERATORS_MAP[key];

@@ -54,7 +70,44 @@ if (opts && typeof val === "object") {

}
extractKeyPaths(val2, parent).forEach(v => result.add(v));
getDependentPaths(val2, Object.assign(Object.assign({}, options), {
// for update expressions send the key as the parent, since they are always top-level.
__parent: !parent && has(updateOperators, key) ? key : parent })).forEach(v => result.add(v));
}
else {
const ancestor = parent ? parent + "." + key : key;
extractKeyPaths(val, ancestor).forEach(v => result.add(v));
// handle update operators first. we pass the operator as the parent since that should always be the top-level field.
// extracting fields for update expressions is not used yet, but may be leveraged for optimizations latter.
if (isOperator(parent) && has(updateOperators, parent)) {
getDependentPaths(val, Object.assign(Object.assign({}, options), { __parent: key })).forEach(v => result.add(v));
continue;
}
if (!options.includeRootFields &&
!parent &&
!isProjectExpression(val)) {
// skip if not a valid project expression or ignoring root fields.
continue;
}
let ancestor = parent ? parent + "." + key : key;
// Since this method is written to support both $project and $match expressions we need to specially handle projection operators $elemMatch and $slice.
// This avoids treating all top-level fields as valid within the state object which would not be true for projections based on expression operators.
// A user may reuse field names in the state object for their selectors. We want to avoid notifying listeners if the actual dependent state fields have not changed.
// To find out whether we have a projection operator, we peek into the value object to detect and operator and also check the current field is top-level.
// If the operator is not a projection and the field is top-level, we don't record it as a valid state field and pass an empty value further down the extractor.
const op = peekOperator(val);
const valObj = val;
if (op && !options.includeRootFields) {
if (
// expr is not a projection operator and not nested so the top-level field must be a new alias.
(!parent && !has(projectionOperators, op)) ||
// $slice has two flavours in MongoDB, so we need to make sure we are looking at the correct one when first condition fails.
// if nested (i.e parent exists), then we know we are using the $slice from the expression operators.
(parent && op === "$slice") ||
// if no parent but op is $slice, we need to check the actual type to determine.
// validates for $slice as an expression operator.
(!parent &&
op === "$slice" &&
isArray(valObj["$slice"]) &&
!isNumber(valObj["$slice"][0]))) {
ancestor = undefined;
}
}
getDependentPaths(val, Object.assign(Object.assign({}, options), { __parent: ancestor })).forEach(v => result.add(v));
}

@@ -61,0 +114,0 @@ }

import { Query } from "mingo";
import { Options as QueryOptions, UpdateOptions } from "mingo/core";
import { AnyVal, Callback, RawObject } from "mingo/types";
import { AnyVal, RawObject } from "mingo/types";
import { UpdateExpression } from "mingo/updater";
/** Observes a selector for changes in store and optionally return updates to apply. */
export type Listener<T extends RawObject> = Callback<void, T>;
export type Listener<T> = (data: T) => void;
/** Unsbuscribe from receiving further notifications */
export type Unsubscribe = () => void;
/** Options to pass on subscription. */
export interface SubscribeOptions {
/** Immediately run the listener when register. Any error will bubble up immediately. */
readonly runImmediately?: boolean;
/** Run only once. */
readonly runOnce?: boolean;
}
/** Result from update operation which returns useful details. */
export type UpdateResult = {
/** Indicates whether the state was modified */
export interface UpdateResult {
/** Represents whether the state was modified */
readonly modified: boolean;
/** Indicates the fields in the state that were changed if modified. */
/** The fields in the state object that were modified. */
readonly fields?: string[];
/** Indicates the number of listeners notified. */
/** The number of listeners notified. */
readonly notifyCount?: number;
};
}
/**

@@ -33,3 +42,3 @@ * Creates a new store object.

private readonly selectors;
private readonly hashIndex;
private readonly cache;
private readonly signals;

@@ -41,2 +50,3 @@ private readonly queryOptions;

* Creates a new observable for a view of the state.
*
* @param projection Fields of the state to view. Expressed as MongoDB projection query.

@@ -46,6 +56,7 @@ * @param condition Conditions to match for a valid state view. Expressed as MongoDB filter query.

*/
select<P extends RawObject>(projection: Record<keyof P, AnyVal>, condition?: RawObject): Selector<P>;
select<P extends RawObject>(projection: Record<keyof P, AnyVal> | RawObject, condition?: RawObject): Selector<P>;
/**
* Dispatches an update expression to mutate the state. Triggers a notification to relevant selectors only.
* @param {RawObject} expr Update expression as a MongoDB update query.
*
* @param {UpdateExpression} expr Update expression as a MongoDB update query.
* @param {Array<RawObject>} arrayFilters Array filter expressions to filter elements to update.

@@ -77,3 +88,3 @@ * @param {RawObject} condition Condition to check before applying update.

*/
constructor(state: RawObject, projection: Record<keyof T, AnyVal>, query: Query, options: QueryOptions);
constructor(state: RawObject, projection: Record<keyof T, AnyVal> | RawObject, query: Query, options: QueryOptions);
/** Returns the number of subscribers to this selector. */

@@ -97,19 +108,8 @@ get size(): number;

/**
* Register a listener to be notified about state updates.
* @param listener The observer function to receive data.
* @returns {Callback} Function to unsubscribe listener.
* Subscribe a listener to be notified about state updates.
*
* @param listener The function to receive new data on update.
* @returns {Unsubscribe} Function to unsubscribe listener.
*/
listen(listener: Listener<T>): Callback<void>;
/**
* Like listen() but also immediately invoke the listener if a value is pending for selector.
* @param listener The observer function to receive data.
* @returns {Callback} Function to unsubscribe listener.
*/
listenNow(listener: Listener<T>): Callback<void>;
/**
* Like listen(), but invokes the listener only once and then automatically removes it.
* @param listener The observer functino to receive data.
* @returns {Callback} Function to unsubscribe listener explicitly before it is called.
*/
listenOnce(listener: Listener<T>): Callback<void>;
subscribe(listener: Listener<T>, options?: SubscribeOptions): Unsubscribe;
}
import { AnyVal } from "mingo/types";
/** checks that value is a valid project expression. */
export declare const isProjectExpression: (o: AnyVal) => boolean;
export interface GetDependentPathOptions {
/** Assumes top-level fields are part of the state and includes them. */
includeRootFields: boolean;
/** used internally */
__parent?: string;
}
/**
* Extract all valid field paths used in the expression.
* Extract all dependent paths used in the expressions for project, match and update expressions.
*
* @param expr The expression.
* @param options Options to customize path retrieval.
*/
export declare function extractKeyPaths(expr: AnyVal, parent?: string): Set<string>;
export declare function getDependentPaths(expr: AnyVal, options?: GetDependentPathOptions): Set<string>;
/**

@@ -8,0 +18,0 @@ * Determines if the selector has a common ancestor with any other in the set.

{
"name": "adaka",
"version": "0.0.8",
"version": "0.0.9",
"description": "High-precision state management using MongoDB query language.",

@@ -5,0 +5,0 @@ "main": "./dist/cjs/index.js",

@@ -50,3 +50,3 @@ # adaka

// subcriber runs whenever name changes.
const unsubscribe = selector.listen(view => {
const unsubscribe = selector.subscribe(view => {
console.log("->", view);

@@ -93,3 +93,3 @@ });

selector.listen(data => {
selector.subscribe(data => {
console.log("->", data);

@@ -96,0 +96,0 @@ });

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