Socket
Socket
Sign inDemoInstall

@oada/list-lib

Package Overview
Dependencies
Maintainers
8
Versions
60
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@oada/list-lib - npm Package Compare versions

Comparing version 2.1.10 to 2.1.11

.history/src/conn-stub_20211202191849.ts

20

lib/conn-stub.d.ts

@@ -1,2 +0,18 @@

import sinon from 'sinon';
/**
* @license
* Copyright 2021 Open Ag Data Alliance
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// <reference types="sinon" />
import { OADAClient } from '@oada/client';

@@ -6,3 +22,3 @@ /**

*/
export declare function createStub(): sinon.SinonStubbedInstance<OADAClient>;
export declare function createStub(): import("sinon").SinonStubbedInstance<OADAClient>;
//# sourceMappingURL=conn-stub.d.ts.map

23

lib/conn-stub.js
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
/**
* @license
* Copyright 2021 Open Ag Data Alliance
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.createStub = void 0;
const sinon_1 = __importDefault(require("sinon"));
const client_1 = require("@oada/client");
const sinon_1 = require("sinon");
const emptyResp = {

@@ -20,3 +33,3 @@ requestId: 'testid',

function createStub() {
const conn = sinon_1.default.createStubInstance(client_1.OADAClient);
const conn = sinon_1.createStubInstance(client_1.OADAClient);
conn.get.resolves(emptyResp);

@@ -23,0 +36,0 @@ conn.head.resolves(emptyResp);

@@ -0,5 +1,21 @@

/**
* @license
* Copyright 2021 Open Ag Data Alliance
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { Link } from '@oada/types/oada/link/v1';
import type { Resource } from '@oada/types/oada/resource';
import type { Link } from '@oada/types/oada/link/v1';
import type V2Changes from '@oada/types/oada/change/v2';
import { Options, ItemState } from './Options';
import { ItemState, Options } from './Options';
/**

@@ -11,7 +27,4 @@ * Type for a single V2 OADA change (rather than the array)

export declare type Change = V2Changes[0];
export declare type TypeAssert<T> = (value: unknown) => asserts value is T;
/**
* @public
*/
export { Options, ItemState };
/**
* Type for the lists we can watch

@@ -33,19 +46,2 @@ */

/**
* The OADA path of the List being watched
*/
readonly path: string;
/**
* The JSON Path for the list items
*/
readonly itemsPath: string;
/**
* The OADA Tree for the List being watched
* @see path
*/
readonly tree?: object | undefined;
/**
* The unique name of this service/watch
*/
readonly name: string;
/**
* Callback to make ListWatch consider every `Item` new

@@ -72,2 +68,19 @@ *

};
/**
* The OADA path of the List being watched
*/
readonly path: string;
/**
* The JSON Path for the list items
*/
readonly itemsPath: string;
/**
* The OADA Tree for the List being watched
* @see path
*/
readonly tree?: Record<string, unknown> | undefined;
/**
* The unique name of this service/watch
*/
readonly name: string;
constructor({ path, itemsPath, tree, name, resume, conn, assertItem, onAddItem, onChangeItem, onItem, onRemoveItem, onNewList, onDeleteList, getItemState, }: Options<Item>);

@@ -87,8 +100,2 @@ /**

/**
* Ask lib user for state of this item
*
* This handles fetching the Item before invoking the callback if needed
*/
private getItemState;
/**
* Clean up metadata and unwatch list

@@ -102,6 +109,12 @@ */

persistMeta(): Promise<void>;
private handleNewItem;
private handleItemChange;
private handleListChange;
/**
* Ask lib user for state of this item
*
* This handles fetching the Item before invoking the callback if needed
*/
private _getItemState;
private _handleNewItem;
private _handleItemChange;
private _handleListChange;
/**
* Update the states of list items

@@ -111,8 +124,9 @@ *

*/
private updateItemState;
private _updateItemState;
/**
* Do async stuff for initializing ourself since constructors are syncronous
* Do async stuff for initializing ourself since constructors are synchronous
*/
private initialize;
private _initialize;
}
export { Options, ItemState } from './Options';
//# sourceMappingURL=index.d.ts.map
"use strict";
/**
* @license
* Copyright 2021 Open Ag Data Alliance
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {

@@ -18,11 +34,11 @@ if (kind === "m") throw new TypeError("Private method is not writable");

Object.defineProperty(exports, "__esModule", { value: true });
exports.ListWatch = exports.pathFromTree = exports.ItemState = void 0;
const path_1 = require("path");
exports.ItemState = exports.ListWatch = exports.pathFromTree = void 0;
/* eslint-disable unicorn/no-await-expression-member */
const node_path_1 = require("node:path");
const bluebird_1 = __importDefault(require("bluebird"));
const json_pointer_1 = __importDefault(require("json-pointer"));
const jsonpath_plus_1 = require("jsonpath-plus");
const p_queue_1 = __importDefault(require("p-queue"));
const debug_1 = __importDefault(require("debug"));
const json_pointer_1 = __importDefault(require("json-pointer"));
const Options_1 = require("./Options");
Object.defineProperty(exports, "ItemState", { enumerable: true, get: function () { return Options_1.ItemState; } });
const Metadata_1 = require("./Metadata");

@@ -36,4 +52,4 @@ const info = debug_1.default('oada-list-lib:info');

*/
function assertNever(val, mesg) {
throw new Error(mesg !== null && mesg !== void 0 ? mesg : `Bad value: ${val}`);
function assertNever(value, message) {
throw new Error(message !== null && message !== void 0 ? message : `Bad value: ${value}`);
}

@@ -56,3 +72,4 @@ /**

function getListItems(list, path) {
const pointers = jsonpath_plus_1.JSONPath({
// eslint-disable-next-line new-cap
return jsonpath_plus_1.JSONPath({
resultType: 'pointer',

@@ -64,4 +81,3 @@ path,

// Don't follow underscore keys
(p) => !/\/_/.test(p));
return pointers;
(p) => !p.includes('/_'));
}

@@ -72,3 +88,3 @@ /**

* @internal
* @experimental trees with multiple "paths" (excluing *)
* @experimental trees with multiple "paths" (excluding *)
*/

@@ -79,15 +95,17 @@ function pathFromTree(tree, root = '') {

const json = json_pointer_1.default.get(tree, root);
// eslint-disable-next-line no-constant-condition
while (true) {
// Get set of non underscore keys
const keys = [
...new Set(jsonpath_plus_1.JSONPath({
resultType: 'parentProperty',
path,
json,
}).filter((k) => !k.startsWith('_'))),
];
const keys = Array.from(new Set(
// eslint-disable-next-line new-cap
jsonpath_plus_1.JSONPath({
resultType: 'parentProperty',
path,
json,
}).filter((k) => !k.startsWith('_'))));
if (keys.length === 0) {
break;
}
outPath += '.' + (keys.length === 1 ? keys[0] : `[${keys.join(',')}]`);
// eslint-disable-next-line sonarjs/no-nested-template-literals
outPath += `.${keys.length === 1 ? keys[0] : `[${keys.join(',')}]`}`;
path += '.*';

@@ -109,5 +127,7 @@ }

// If no assert given, assume all items valid
// eslint-disable-next-line @typescript-eslint/no-empty-function
assertItem = () => { }, onAddItem, onChangeItem, onItem, onRemoveItem, onNewList, onDeleteList = async () => {
// TODO: Actually handle the list being deleted (redo watch?)
error('Unhandled delete of list %s', path);
// eslint-disable-next-line no-process-exit, unicorn/no-process-exit
process.exit();

@@ -146,11 +166,9 @@ },

}
else if (tree) {
// Assume items are at the leaves of tree
this.itemsPath = pathFromTree(tree, path);
}
else {
if (tree) {
// Asume items are at the leaves of tree
this.itemsPath = pathFromTree(tree, path);
}
else {
// Assume flat list
this.itemsPath = '$.*';
}
// Assume flat list
this.itemsPath = '$.*';
}

@@ -162,16 +180,15 @@ if (onNewList) {

// If no callback provided, ask client for states of pre-existing items
__classPrivateFieldSet(this, _ListWatch_onNewList, (ids) => {
return bluebird_1.default.map(ids, (id) => {
try {
return this.getItemState(id);
}
catch (err) {
error(err, 'Error getting item state');
}
});
}, "f");
__classPrivateFieldSet(this, _ListWatch_onNewList, async (ids) => Promise.all(ids.map(async (id) => {
try {
return await this._getItemState(id);
}
catch (cError) {
error(cError, 'Error getting item state');
throw cError;
}
})), "f");
}
__classPrivateFieldSet(this, _ListWatch_meta, new Metadata_1.Metadata({
// Don't persist metdata if service does not "resume"
//persistInterval: this.#resume ? persistInterval : 0,
// persistInterval: this.#resume ? persistInterval : 0,
conn: __classPrivateFieldGet(this, _ListWatch_resume, "f") ? __classPrivateFieldGet(this, _ListWatch_conn, "f") : undefined,

@@ -182,3 +199,3 @@ path,

}), "f");
this.initialize().catch(error);
this._initialize().catch(error);
}

@@ -197,11 +214,14 @@ /**

all = false) {
const { path } = this;
const { path, itemsPath } = this;
const conn = __classPrivateFieldGet(this, _ListWatch_conn, "f");
const { data: list } = (await conn.get({ path }));
//const items = Object.keys(list).filter((k) => !k.match(/^_/));
const items = getListItems(list, this.itemsPath);
//const { rev } = this.#meta;
await bluebird_1.default.map(items, async (id) => {
if (Buffer.isBuffer(list)) {
throw new TypeError('List is not a JSON object');
}
// Const items = Object.keys(list).filter((k) => !k.match(/^_/));
const items = getListItems(list, itemsPath);
// Const { rev } = this.#meta;
await Promise.all(items.map(async (id) => {
try {
if (!all && __classPrivateFieldGet(this, _ListWatch_meta, "f").handled(id)) {
if (!all && (await __classPrivateFieldGet(this, _ListWatch_meta, "f").handled(id))) {
// We think this item is handled

@@ -211,11 +231,26 @@ return;

// Ask lib user for state of this item
const state = await this.getItemState(id);
await this.updateItemState(list, id, state);
const state = await this._getItemState(id);
await this._updateItemState(list, id, state);
}
catch (err) {
error(err);
catch (cError) {
error(cError);
}
});
}));
}
/**
* Clean up metadata and unwatch list
*/
async stop() {
await __classPrivateFieldGet(this, _ListWatch_conn, "f").unwatch(__classPrivateFieldGet(this, _ListWatch_id, "f"));
await this.persistMeta();
// This.#meta.stop();
}
/**
* Persist relevant info to the `_meta` of the list.
* This preserves it across restarts.
*/
async persistMeta() {
// Await this.#meta.persist();
}
/**
* Ask lib user for state of this item

@@ -225,3 +260,3 @@ *

*/
async getItemState(id) {
async _getItemState(id) {
// Needed because TS is weird about asserts...

@@ -231,3 +266,3 @@ const assertItem = __classPrivateFieldGet(this, _ListWatch_assertItem, "f");

const { data: item } = await __classPrivateFieldGet(this, _ListWatch_conn, "f").get({
path: path_1.join(this.path, id),
path: node_path_1.join(this.path, id),
});

@@ -237,22 +272,5 @@ assertItem(item);

}
else {
return __classPrivateFieldGet(this, _ListWatch_getItemState, "f").call(this, id);
}
return __classPrivateFieldGet(this, _ListWatch_getItemState, "f").call(this, id);
}
/**
* Clean up metadata and unwatch list
*/
async stop() {
await __classPrivateFieldGet(this, _ListWatch_conn, "f").unwatch(__classPrivateFieldGet(this, _ListWatch_id, "f"));
await this.persistMeta();
//this.#meta.stop();
}
/**
* Persist relevant info to the `_meta` of the list.
* This preserves it across restarts.
*/
async persistMeta() {
//await this.#meta.persist();
}
async handleNewItem(rev, id, item) {
async _handleNewItem(rev, id, item) {
var _a, _b, _c, _d;

@@ -269,3 +287,3 @@ const { path } = this;

await (__classPrivateFieldGet(this, _ListWatch_onAddItem, "f") && __classPrivateFieldGet(this, _ListWatch_onAddItem, "f").call(this, item, id));
await __classPrivateFieldGet(this, _ListWatch_meta, "f").setHandled(id, { onAddItem: { rev: _rev + '' } });
await __classPrivateFieldGet(this, _ListWatch_meta, "f").setHandled(id, { onAddItem: { rev: `${_rev}` } });
}

@@ -278,13 +296,13 @@ }

// Double check this item is actually newer than last time
if (+_rev > +((_d = (_c = (_b = (await __classPrivateFieldGet(this, _ListWatch_meta, "f").handled(id))) === null || _b === void 0 ? void 0 : _b.onItem) === null || _c === void 0 ? void 0 : _c.rev) !== null && _d !== void 0 ? _d : 0)) {
// TODO: Why doesn't this.#onItem?.() work?
if (Number(_rev) > Number((_d = (_c = (_b = (await __classPrivateFieldGet(this, _ListWatch_meta, "f").handled(id))) === null || _b === void 0 ? void 0 : _b.onItem) === null || _c === void 0 ? void 0 : _c.rev) !== null && _d !== void 0 ? _d : 0)) {
await (__classPrivateFieldGet(this, _ListWatch_onItem, "f") && __classPrivateFieldGet(this, _ListWatch_onItem, "f").call(this, item, id));
await __classPrivateFieldGet(this, _ListWatch_meta, "f").setHandled(id, { onItem: { rev: _rev + '' } });
await __classPrivateFieldGet(this, _ListWatch_meta, "f").setHandled(id, { onItem: { rev: `${_rev}` } });
}
}
}
async handleItemChange(id, change) {
async _handleItemChange(id, change) {
var _a;
const { path } = this;
const conn = __classPrivateFieldGet(this, _ListWatch_conn, "f");
const rev = change.body._rev;
const rev = (_a = change.body) === null || _a === void 0 ? void 0 : _a._rev;
// TODO: How best to handle change to a descendant of an item?

@@ -295,3 +313,3 @@ info('Detected change to item %s in %s, rev %s', id, path, rev);

await (__classPrivateFieldGet(this, _ListWatch_onChangeItem, "f") && __classPrivateFieldGet(this, _ListWatch_onChangeItem, "f").call(this, change, id));
await __classPrivateFieldGet(this, _ListWatch_meta, "f").setHandled(id, { onChangeItem: { rev: _rev + '' } });
await __classPrivateFieldGet(this, _ListWatch_meta, "f").setHandled(id, { onChangeItem: { rev: `${_rev}` } });
}

@@ -303,32 +321,34 @@ finally {

const { data: item } = await conn.get({
path: path_1.join(path, id),
path: node_path_1.join(path, id),
});
assertItem(item);
await __classPrivateFieldGet(this, _ListWatch_onItem, "f").call(this, item, id);
await __classPrivateFieldGet(this, _ListWatch_meta, "f").setHandled(id, { onItem: { rev: _rev + '' } });
await __classPrivateFieldGet(this, _ListWatch_meta, "f").setHandled(id, { onItem: { rev: `${_rev}` } });
}
}
}
async handleListChange(list, type) {
const { path } = this;
async _handleListChange(list, type) {
const { path, itemsPath } = this;
const conn = __classPrivateFieldGet(this, _ListWatch_conn, "f");
const rev = list._rev;
// Ignore _ keys of OADA
//const items = Object.keys(list).filter((k) => !k.match(/^_/));
const items = getListItems(list, this.itemsPath);
// const items = Object.keys(list).filter((k) => !k.match(/^_/));
const items = getListItems(list, itemsPath);
trace(items, 'handleListChange');
switch (type) {
case 'merge':
await bluebird_1.default.map(items, async (id) => {
await Promise.all(items.map(async (id) => {
try {
trace('handleListChange: Processing item %s', id);
const lchange = json_pointer_1.default.get(list, id);
trace(lchange, 'handleListChange: lchange');
const ichang = json_pointer_1.default.get(list, id);
trace(ichang, 'handleListChange');
// If there is an _id this is a new link in the list right?
if (lchange._id) {
trace('handleListChange: lchange has an _id, getting it and handing to handleNewItem');
if (ichang._id) {
trace('handleListChange: change has an _id, getting it and handing to handleNewItem');
const { data: item } = (await conn.get({
path: path_1.join(path, id),
// Joining path and id fails when jobs get moved into success/failure. Instead get the _id
// path: join(path, id),
path: `/${id}`,
}));
await this.handleNewItem(rev + '', id, item);
await this._handleNewItem(`${rev}`, id, item);
}

@@ -340,13 +360,13 @@ else {

}
catch (err) {
catch (cError) {
// Log error with this item but continue map over other items
error(err, `Error processing change for ${id} at ${path}, rev ${rev}`);
error(cError, `Error processing change for ${id} at ${path}, rev ${rev}`);
}
});
}));
break;
case 'delete':
await bluebird_1.default.map(items, async (id) => {
await Promise.all(items.map(async (id) => {
try {
const lchange = json_pointer_1.default.get(list, id);
if (lchange === null) {
const lChange = json_pointer_1.default.get(list, id);
if (lChange === null) {
info('Detected removal of item %s from %s, rev %s', id, path, rev);

@@ -358,3 +378,3 @@ try {

// Mark for delete?
await __classPrivateFieldGet(this, _ListWatch_meta, "f").setHandled(id, undefined);
await __classPrivateFieldGet(this, _ListWatch_meta, "f").setHandled(id);
}

@@ -367,8 +387,10 @@ }

}
catch (err) {
catch (cError) {
// Log error with this item but continue map over other items
error(err, `Error processing change for ${id} at ${path}, rev ${rev}`);
error(cError, `Error processing change for ${id} at ${path}, rev ${rev}`);
}
});
}));
break;
default:
throw new TypeError(`Unknown change type ${type}`);
}

@@ -382,3 +404,3 @@ return items.length > 0;

*/
async updateItemState(list, ids, states) {
async _updateItemState(list, ids, states) {
const { path } = this;

@@ -388,4 +410,4 @@ const { rev } = __classPrivateFieldGet(this, _ListWatch_meta, "f");

const _states = (Array.isArray(states) ? states : [states]);
await bluebird_1.default.map(_ids, async (id, i) => {
const state = _states[i];
await Promise.all(_ids.map(async (id, index) => {
const state = _states[Number(index)];
try {

@@ -396,5 +418,5 @@ switch (state) {

const { data: item } = (await __classPrivateFieldGet(this, _ListWatch_conn, "f").get({
path: path_1.join(path, id),
path: node_path_1.join(path, id),
}));
await this.handleNewItem(list._rev + '', id, item);
await this._handleNewItem(`${list._rev}`, id, item);
}

@@ -405,3 +427,3 @@ break;

const { data: item } = await __classPrivateFieldGet(this, _ListWatch_conn, "f").get({
path: path_1.join(path, id),
path: node_path_1.join(path, id),
});

@@ -411,7 +433,7 @@ const change = {

path: '',
// TODO: what is the type the change??
type: 'merge',
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
body: item,
};
await this.handleItemChange(id, change);
await this._handleItemChange(id, change);
}

@@ -431,12 +453,12 @@ break;

}
catch (err) {
error(err, `Error processing item state "${state}" for item ${id}`);
catch (cError) {
error(cError, `Error processing item state "${state}" for item ${id}`);
}
});
}));
}
/**
* Do async stuff for initializing ourself since constructors are syncronous
* Do async stuff for initializing ourself since constructors are synchronous
*/
async initialize() {
const { path, tree } = this;
async _initialize() {
const { path, tree, itemsPath } = this;
const conn = __classPrivateFieldGet(this, _ListWatch_conn, "f");

@@ -447,4 +469,5 @@ info('Ensuring %s exists', path);

}
catch (err) {
if (err.status === 403 || err.status === 404) {
catch (cError) {
// @ts-expect-error darn errors
if ((cError === null || cError === void 0 ? void 0 : cError.status) === 403 || (cError === null || cError === void 0 ? void 0 : cError.status) === 404) {
// Create it

@@ -455,4 +478,4 @@ await conn.put({ path, tree, data: {} });

else {
error(err);
throw err;
error(cError);
throw cError;
}

@@ -468,5 +491,5 @@ }

}));
//const items = Object.keys(list).filter((k) => !k.match(/^_/));
const items = getListItems(list, this.itemsPath);
// ask for states of pre-existing items
// Const items = Object.keys(list).filter((k) => !k.match(/^_/));
const items = getListItems(list, itemsPath);
// Ask for states of pre-existing items
trace('Calling onNewList');

@@ -476,3 +499,3 @@ const states = await __classPrivateFieldGet(this, _ListWatch_onNewList, "f").call(this, items);

trace('Updating item states based on callback result');
await this.updateItemState(list, items, states);
await this._updateItemState(list, items, states);
}

@@ -485,2 +508,3 @@ // Setup watch on the path

const changeQueue = new p_queue_1.default({ concurrency: 1 });
// eslint-disable-next-line security/detect-non-literal-fs-filename
__classPrivateFieldSet(this, _ListWatch_id, await conn.watch({

@@ -490,3 +514,3 @@ path,

type: 'tree',
watchCallback: (changes) => changeQueue.add(async () => {
watchCallback: async (changes) => changeQueue.add(async () => {
var _a;

@@ -497,3 +521,4 @@ // Get root change?

await bluebird_1.default.each(changes, async (change) => {
const { type, path: changePath, body, ...ctx } = change;
var _a;
const { type, path: changePath, body, ...context } = change;
if (body === null && type === 'delete' && changePath === '') {

@@ -505,5 +530,5 @@ // The list itself was deleted

}
const rev = body._rev;
const rev = (_a = body) === null || _a === void 0 ? void 0 : _a._rev;
trace(change, 'Received change');
let itemsFound = !!changePath;
let itemsFound = Boolean(changePath);
let listChange = body;

@@ -518,15 +543,16 @@ try {

// Reconstruct change to list?
const changeObj = {};
const changeObject = {};
let isListChange = false;
if (this.itemsPath) {
// just put true here for now to check if path matches
json_pointer_1.default.set(changeObj, changePath, true);
if (itemsPath) {
// Just put true here for now to check if path matches
json_pointer_1.default.set(changeObject, changePath, true);
// eslint-disable-next-line new-cap
const pathmatches = jsonpath_plus_1.JSONPath({
resultType: 'pointer',
path: this.itemsPath,
json: changeObj,
path: itemsPath,
json: changeObject,
preventEval: true,
});
if ((pathmatches === null || pathmatches === void 0 ? void 0 : pathmatches.length) === 0) {
// if it does not match, this must be above the items
// If it does not match, this must be above the items
isListChange = true;

@@ -536,31 +562,32 @@ trace('Have a write to the list under itemsPath rather than to any of the items');

}
// now put the actual change body in place of the true
json_pointer_1.default.set(changeObj, changePath, body);
// Now put the actual change body in place of the true
json_pointer_1.default.set(changeObject, changePath, body);
// Find items involved in the change
const itemsChanged = getListItems(changeObj, this.itemsPath);
const itemsChanged = getListItems(changeObject, itemsPath);
// The change was to items of the list (or their descendants)
if (!isListChange && itemsChanged.length >= 1) {
return bluebird_1.default.map(itemsChanged, (item) => {
const body = json_pointer_1.default.get(changeObj, item);
if (!isListChange && itemsChanged.length > 0) {
return await Promise.all(itemsChanged.map((item) => {
const itemBody = json_pointer_1.default.get(changeObject, item);
// Make change start at item instead of the list
const path = changePath.slice(item.length);
const change = {
...ctx,
const itemPath = changePath.slice(item.length);
const itemChange = {
...context,
type,
path,
body,
path: itemPath,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
body: itemBody,
};
// Check that it is a resource change?
if (!body._rev) {
warn(change, 'Ignoring unexpected (as in the body does not have a _rev) change');
if (!(typeof itemBody === 'object' &&
itemBody &&
'_rev' in itemBody)) {
warn(itemChange, 'Ignoring unexpected (as in the body does not have a _rev) change');
return;
}
return this.handleItemChange(item, change);
});
return this._handleItemChange(item, itemChange);
}));
}
else {
// The change is between the list and items
// (multiple link levels)
listChange = changeObj;
}
// The change is between the list and items
// (multiple link levels)
listChange = changeObject;
}

@@ -570,6 +597,6 @@ trace('Change was to the list itself because changePath is empty, calling handleListChange');

itemsFound =
(await this.handleListChange(listChange, type)) || itemsFound;
(await this._handleListChange(listChange, type)) || itemsFound;
}
catch (err) {
error(err, `Error processing change at ${path}, rev ${rev}`);
catch (cError) {
error(cError, `Error processing change at ${path}, rev ${rev}`);
}

@@ -579,3 +606,3 @@ });

trace('Received change to root of list, updating handled rev in our _meta records');
__classPrivateFieldGet(this, _ListWatch_meta, "f").rev = ((_a = rootChange.body) === null || _a === void 0 ? void 0 : _a._rev) + '';
__classPrivateFieldGet(this, _ListWatch_meta, "f").rev = `${(_a = rootChange.body) === null || _a === void 0 ? void 0 : _a._rev}`;
}

@@ -595,2 +622,3 @@ }),

*/
// eslint-disable-next-line @typescript-eslint/naming-convention
ListWatch.AssumeNew = assumeItemState(Options_1.ItemState.New);

@@ -604,6 +632,9 @@ /**

*/
// eslint-disable-next-line @typescript-eslint/naming-convention
ListWatch.AssumeHandled = assumeItemState(Options_1.ItemState.Handled);
function stateCBnoItem(cb) {
return cb.length < 2;
function stateCBnoItem(callback) {
return callback.length < 2;
}
var Options_2 = require("./Options");
Object.defineProperty(exports, "ItemState", { enumerable: true, get: function () { return Options_2.ItemState; } });
//# sourceMappingURL=index.js.map

@@ -0,2 +1,18 @@

/**
* @license
* Copyright 2021 Open Ag Data Alliance
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export {};
//# sourceMappingURL=index.spec.d.ts.map
"use strict";
/**
* @license
* Copyright 2021 Open Ag Data Alliance
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {

@@ -6,5 +22,7 @@ return (mod && mod.__esModule) ? mod : { "default": mod };

Object.defineProperty(exports, "__esModule", { value: true });
const bluebird_1 = __importDefault(require("bluebird"));
const sinon_1 = require("sinon");
const ava_1 = __importDefault(require("ava"));
const sinon_1 = __importDefault(require("sinon"));
const bluebird_1 = __importDefault(require("bluebird"));
// TODO: Fix this
// Import { Change } from '@oada/types/oada/change/v2';
const conn_stub_1 = require("./conn-stub");

@@ -20,2 +38,3 @@ const _1 = require("./");

thing: {
// eslint-disable-next-line sonarjs/no-duplicate-string
_type: 'application/json',

@@ -89,2 +108,3 @@ _rev: 0,

const path = '/bookmarks/foo/bar';
// eslint-disable-next-line no-new
new _1.ListWatch({ path, name, conn });

@@ -114,6 +134,6 @@ t.plan(1);

// A Change from adding an item to a list
// TODO: Better way to do this test without actually runnig oada?
// TODO: Better way to do this test without actually running oada?
const path = '/bookmarks';
const id = 'resources/foo';
// @ts-ignore
// @ts-expect-error test
conn.get.resolves({ data: { _id: id } });

@@ -125,2 +145,3 @@ const change = [

body: {
// eslint-disable-next-line no-secrets/no-secrets
'1e6XB0Hy7XJICbi3nMzCtl4QLpC': {

@@ -139,5 +160,5 @@ _id: id,

];
// @ts-ignore
// @ts-expect-error test
conn.get.resolves({ data: { _rev: 4 } });
const opts = {
const options = {
path,

@@ -147,16 +168,17 @@ name,

// Create spies to see which callbacks run
onAddItem: sinon_1.default.spy(),
onChangeItem: sinon_1.default.spy(),
onItem: sinon_1.default.spy(),
onRemoveItem: sinon_1.default.spy(),
onAddItem: sinon_1.spy(),
onChangeItem: sinon_1.spy(),
onItem: sinon_1.spy(),
onRemoveItem: sinon_1.spy(),
};
new _1.ListWatch(opts);
// eslint-disable-next-line no-new
new _1.ListWatch(options);
// TODO: How to do this right in ava?
await bluebird_1.default.delay(delay);
const cb = (_c = (_b = (_a = conn.watch.firstCall) === null || _a === void 0 ? void 0 : _a.args) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.watchCallback;
await cb(change);
t.is(opts.onAddItem.callCount, 1);
t.is(opts.onItem.callCount, 1);
t.is(opts.onChangeItem.callCount, 0);
t.is(opts.onRemoveItem.callCount, 0);
const callback = (_c = (_b = (_a = conn.watch.firstCall) === null || _a === void 0 ? void 0 : _a.args) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.watchCallback;
await callback(change);
t.is(options.onAddItem.callCount, 1);
t.is(options.onItem.callCount, 1);
t.is(options.onChangeItem.callCount, 0);
t.is(options.onRemoveItem.callCount, 0);
});

@@ -167,3 +189,3 @@ ava_1.default('it should detect removed item', async (t) => {

// A Change from adding an item to a list
// TODO: Better way to do this test without actually runnig oada?
// TODO: Better way to do this test without actually running oada?
const path = '/bookmarks';

@@ -175,2 +197,3 @@ const change = [

body: {
// eslint-disable-next-line no-secrets/no-secrets, unicorn/no-null
'1e6XB0Hy7XJICbi3nMzCtl4QLpC': null,

@@ -187,3 +210,3 @@ '_meta': {

];
const opts = {
const options = {
path,

@@ -193,18 +216,19 @@ name,

// Create spies to see which callbacks run
onAddItem: sinon_1.default.spy(),
onChangeItem: sinon_1.default.spy(),
onItem: sinon_1.default.spy(),
onRemoveItem: sinon_1.default.spy(),
onAddItem: sinon_1.spy(),
onChangeItem: sinon_1.spy(),
onItem: sinon_1.spy(),
onRemoveItem: sinon_1.spy(),
};
new _1.ListWatch(opts);
// eslint-disable-next-line no-new
new _1.ListWatch(options);
// TODO: How to do this right in ava?
await bluebird_1.default.delay(delay);
const cb = (_c = (_b = (_a = conn.watch.firstCall) === null || _a === void 0 ? void 0 : _a.args) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.watchCallback;
await cb(change);
t.is(opts.onAddItem.callCount, 0);
t.is(opts.onItem.callCount, 0);
t.is(opts.onChangeItem.callCount, 0);
t.is(opts.onRemoveItem.callCount, 1);
const callback = (_c = (_b = (_a = conn.watch.firstCall) === null || _a === void 0 ? void 0 : _a.args) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.watchCallback;
await callback(change);
t.is(options.onAddItem.callCount, 0);
t.is(options.onItem.callCount, 0);
t.is(options.onChangeItem.callCount, 0);
t.is(options.onRemoveItem.callCount, 1);
});
ava_1.default.todo('it should detect modified item');
//# sourceMappingURL=index.spec.js.map

@@ -0,2 +1,18 @@

/**
* @license
* Copyright 2021 Open Ag Data Alliance
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export {};
//# sourceMappingURL=Metadata.d.ts.map
"use strict";
/**
* @license
* Copyright 2021 Open Ag Data Alliance
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {

@@ -19,3 +35,3 @@ if (kind === "m") throw new TypeError("Private method is not writable");

exports.Metadata = void 0;
const path_1 = require("path");
const node_path_1 = require("node:path");
const bluebird_1 = __importDefault(require("bluebird"));

@@ -46,3 +62,3 @@ const clone_deep_1 = __importDefault(require("clone-deep"));

__classPrivateFieldSet(this, _Metadata_conn, conn, "f");
__classPrivateFieldSet(this, _Metadata_path, path_1.join(path, '_meta', Metadata.META_KEY, name), "f");
__classPrivateFieldSet(this, _Metadata_path, node_path_1.join(path, '_meta', Metadata.META_KEY, name), "f");
__classPrivateFieldSet(this, _Metadata_tree, clone_deep_1.default(tree), "f");

@@ -65,3 +81,3 @@ if (__classPrivateFieldGet(this, _Metadata_tree, "f")) {

}
//console.dir(this.#tree, { depth: null });
// Console.dir(this.#tree, { depth: null });
__classPrivateFieldSet(this, _Metadata_wait, bluebird_1.default.fromCallback((done) => {

@@ -78,2 +94,3 @@ __classPrivateFieldSet(this, _Metadata_done, done, "f");

tree: __classPrivateFieldGet(this, _Metadata_tree, "f"),
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
data: { rev: __classPrivateFieldGet(this, _Metadata_rev, "f") },

@@ -83,4 +100,11 @@ })), "f");

}
/**
* @todo: Where in _meta to keep stuff?
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
static get META_KEY() {
return 'oada-list-lib';
}
get rev() {
return __classPrivateFieldGet(this, _Metadata_rev, "f") + '';
return `${__classPrivateFieldGet(this, _Metadata_rev, "f")}`;
}

@@ -111,3 +135,4 @@ set rev(rev) {

tree: __classPrivateFieldGet(this, _Metadata_tree, "f"),
data: data,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
data,
}));

@@ -117,5 +142,5 @@ }

// Unset info?
await ((_b = __classPrivateFieldGet(this, _Metadata_conn, "f")) === null || _b === void 0 ? void 0 : _b.delete({ path: path_1.join(__classPrivateFieldGet(this, _Metadata_path, "f"), 'handled', path) }));
await ((_b = __classPrivateFieldGet(this, _Metadata_conn, "f")) === null || _b === void 0 ? void 0 : _b.delete({ path: node_path_1.join(__classPrivateFieldGet(this, _Metadata_path, "f"), 'handled', path) }));
}
//this.#updated = true;
// This.#updated = true;
}

@@ -133,3 +158,3 @@ /**

const { data } = await __classPrivateFieldGet(this, _Metadata_conn, "f").get({
path: path_1.join(__classPrivateFieldGet(this, _Metadata_path, "f"), 'handled', path),
path: node_path_1.join(__classPrivateFieldGet(this, _Metadata_path, "f"), 'handled', path),
});

@@ -158,3 +183,3 @@ return data;

const { data: rev } = await __classPrivateFieldGet(this, _Metadata_conn, "f").get({
path: path_1.join(__classPrivateFieldGet(this, _Metadata_path, "f"), 'rev'),
path: node_path_1.join(__classPrivateFieldGet(this, _Metadata_path, "f"), 'rev'),
});

@@ -165,3 +190,3 @@ __classPrivateFieldSet(this, _Metadata_rev, rev, "f");

}
catch (err) {
catch {
// Create our metadata?

@@ -175,3 +200,3 @@ const { headers: { 'content-location': location }, } = await __classPrivateFieldGet(this, _Metadata_conn, "f").post({

tree: __classPrivateFieldGet(this, _Metadata_tree, "f"),
data: { _id: location.substring(1) },
data: { _id: location.slice(1) },
});

@@ -181,2 +206,3 @@ await __classPrivateFieldGet(this, _Metadata_conn, "f").put({

tree: __classPrivateFieldGet(this, _Metadata_tree, "f"),
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
data: {

@@ -190,5 +216,5 @@ rev: __classPrivateFieldGet(this, _Metadata_rev, "f"),

}
catch (err) {
__classPrivateFieldGet(this, _Metadata_done, "f").call(this, err);
throw err;
catch (error) {
__classPrivateFieldGet(this, _Metadata_done, "f").call(this, error);
throw error;
}

@@ -199,6 +225,2 @@ }

_Metadata_rev = new WeakMap(), _Metadata_conn = new WeakMap(), _Metadata_path = new WeakMap(), _Metadata_tree = new WeakMap(), _Metadata_timeout = new WeakMap(), _Metadata_done = new WeakMap(), _Metadata_wait = new WeakMap();
/**
* @todo: Where in _meta to keep stuff?
*/
Metadata.META_KEY = 'oada-list-lib';
//# sourceMappingURL=Metadata.js.map

@@ -0,2 +1,18 @@

/**
* @license
* Copyright 2021 Open Ag Data Alliance
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export {};
//# sourceMappingURL=Metadata.spec.d.ts.map
"use strict";
/**
* @license
* Copyright 2021 Open Ag Data Alliance
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {

@@ -6,5 +22,7 @@ return (mod && mod.__esModule) ? mod : { "default": mod };

Object.defineProperty(exports, "__esModule", { value: true });
const bluebird_1 = __importDefault(require("bluebird"));
const sinon_1 = require("sinon");
const ava_1 = __importDefault(require("ava"));
const sinon_1 = __importDefault(require("sinon"));
const bluebird_1 = __importDefault(require("bluebird"));
// TODO: Fix this
// Import { Change } from '@oada/types/oada/change/v2';
const conn_stub_1 = require("./conn-stub");

@@ -17,8 +35,8 @@ const _1 = require("./");

// A Change from adding an item to a list
// TODO: Better way to do this test without actually runnig oada?
// TODO: Better way to do this test without actually running oada?
const path = '/bookmarks';
const rev = '766';
// @ts-ignore
// @ts-expect-error test
conn.get.resolves({ data: rev });
const opts = {
const options = {
path,

@@ -29,8 +47,9 @@ name,

// Create spies to see which callbacks run
onAddItem: sinon_1.default.spy(),
onChangeItem: sinon_1.default.spy(),
onItem: sinon_1.default.spy(),
onRemoveItem: sinon_1.default.spy(),
onAddItem: sinon_1.spy(),
onChangeItem: sinon_1.spy(),
onItem: sinon_1.spy(),
onRemoveItem: sinon_1.spy(),
};
new _1.ListWatch(opts);
// eslint-disable-next-line no-new
new _1.ListWatch(options);
// TODO: How to do this right in ava?

@@ -44,3 +63,3 @@ await bluebird_1.default.delay(5);

// A Change from adding an item to a list
// TODO: Better way to do this test without actually runnig oada?
// TODO: Better way to do this test without actually running oada?
const path = '/bookmarks';

@@ -52,2 +71,3 @@ const change = [

body: {
// eslint-disable-next-line no-secrets/no-secrets
'1e6XB0Hy7XJICbi3nMzCtl4QLpC': {

@@ -66,3 +86,3 @@ _id: '',

];
const opts = {
const options = {
path,

@@ -74,14 +94,15 @@ name,

// Create spies to see which callbacks run
onAddItem: sinon_1.default.spy(),
onChangeItem: sinon_1.default.spy(),
onItem: sinon_1.default.spy(),
onRemoveItem: sinon_1.default.spy(),
onAddItem: sinon_1.spy(),
onChangeItem: sinon_1.spy(),
onItem: sinon_1.spy(),
onRemoveItem: sinon_1.spy(),
};
// @ts-ignore
// @ts-expect-error test
conn.get.resolves({ data: {} });
new _1.ListWatch(opts);
// eslint-disable-next-line no-new
new _1.ListWatch(options);
// TODO: How to do this right in ava?
await bluebird_1.default.delay(5);
const cb = (_c = (_b = (_a = conn.watch.firstCall) === null || _a === void 0 ? void 0 : _a.args) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.watchCallback;
await cb(change);
const callback = (_c = (_b = (_a = conn.watch.firstCall) === null || _a === void 0 ? void 0 : _a.args) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.watchCallback;
await callback(change);
await bluebird_1.default.delay(500);

@@ -88,0 +109,0 @@ t.assert(conn.put.calledWithMatch({

@@ -1,10 +0,25 @@

import type { TypeAssert } from '@oada/types';
import type { Change } from './';
/**
* @license
* Copyright 2021 Open Ag Data Alliance
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { Change, TypeAssert } from './';
import type { OADAClient } from '@oada/client';
/**
* Type that can be either T or a Promise which resovles to T
* Type that can be either T or a Promise which resolves to T
*/
declare type AllowPromise<T> = T | Promise<T>;
/**
* The type for the object given to the construtor
* The type for the object given to the constructor
*

@@ -34,3 +49,3 @@ * @public

*/
tree?: object;
tree?: Record<string, unknown>;
/**

@@ -43,3 +58,3 @@ *, 'data'> A persistent name/id for this instance (can just be random string)

/**
* true: "resume" change feed for list from last processed rev
* True: "resume" change feed for list from last processed rev
* false: just start from current state of the list

@@ -46,0 +61,0 @@ *

"use strict";
/**
* @license
* Copyright 2021 Open Ag Data Alliance
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });

@@ -3,0 +19,0 @@ exports.ItemState = void 0;

{
"name": "@oada/list-lib",
"version": "2.1.10",
"version": "2.1.11",
"description": "Library for processing items in an OADA list",

@@ -18,3 +18,3 @@ "main": "lib/index.js",

"scripts": {
"build": "tsc",
"build": "tsc -b",
"pretest": "npm run build",

@@ -44,3 +44,3 @@ "test": "ava",

"devDependencies": {
"@ava/typescript": "^2.0.0",
"@ava/typescript": "^3.0.1",
"@tsconfig/node12": "^1.0.9",

@@ -51,22 +51,49 @@ "@types/bluebird": "^3.5.36",

"@types/json-pointer": "^1.0.31",
"@types/node": "^14.17.9",
"@types/jsonpath-plus": "^5.0.2",
"@types/node": "^16.11.11",
"@types/object-assign-deep": "^0.4.0",
"@types/sinon": "^10.0.2",
"@yarnpkg/sdks": "^2.4.1-rc.4",
"@types/sinon": "^10.0.6",
"@typescript-eslint/eslint-plugin": "^5.5.0",
"@typescript-eslint/parser": "^5.5.0",
"@yarnpkg/sdks": "^2.5.1-rc.4",
"ava": "^3.15.0",
"prettier": "^2.3.2",
"sinon": "^11.1.2",
"eslint": "^8.3.0",
"eslint-config-prettier": "^8.3.0",
"eslint-config-xo": "^0.39.0",
"eslint-config-xo-typescript": "^0.47.1",
"eslint-formatter-pretty": "^4.1.0",
"eslint-import-resolver-node": "^0.3.6",
"eslint-plugin-array-func": "^3.1.7",
"eslint-plugin-eslint-comments": "^3.2.0",
"eslint-plugin-filenames": "^1.3.2",
"eslint-plugin-github": "^4.3.5",
"eslint-plugin-i18n-text": "^1.0.1",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-no-constructor-bind": "^2.0.4",
"eslint-plugin-no-only-tests": "^2.6.0",
"eslint-plugin-no-secrets": "^0.8.9",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-notice": "^0.9.10",
"eslint-plugin-optimize-regex": "^1.2.1",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-promise": "^5.2.0",
"eslint-plugin-regexp": "^1.5.1",
"eslint-plugin-security": "^1.4.0",
"eslint-plugin-sonarjs": "^0.11.0",
"eslint-plugin-unicorn": "^39.0.0",
"prettier": "^2.5.0",
"sinon": "^12.0.1",
"typescript": "^4.3.5"
},
"dependencies": {
"@oada/client": "^2.6.1",
"@oada/types": "^1.2.0",
"@oada/client": "^2.6.5",
"@oada/types": "^1.7.0",
"bluebird": "^3.7.2",
"clone-deep": "^4.0.1",
"debug": "^4.3.1",
"json-pointer": "^0.6.0",
"debug": "^4.3.3",
"json-pointer": "^0.6.1",
"jsonpath-plus": "^6.0.1",
"p-queue": "^6.6.2"
},
"packageManager": "yarn@3.0.0"
"packageManager": "yarn@3.1.1"
}

@@ -34,3 +34,3 @@ # OADA/list-lib

While the `ListWatch` class is generic,
you will typically not want to specify a type paramter in your code.
you will typically not want to specify a type parameter in your code.
If you supply an `assertItem` function,

@@ -44,5 +44,5 @@ the type of `Item` will be inferred from it.

In more advanced use-cases, you might want to prompt the libray to re-check
In more advanced use-cases, you might want to prompt the library to re-check
all the items in the list.
For this reason, `ListWatch` has a `forceRecheck` method.
Calling this will cause the library to check all the current list items.

@@ -1,4 +0,21 @@

import sinon from 'sinon';
import { OADAClient, ConnectionResponse } from '@oada/client';
/**
* @license
* Copyright 2021 Open Ag Data Alliance
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ConnectionResponse, OADAClient } from '@oada/client';
import { createStubInstance } from 'sinon';
const emptyResp: ConnectionResponse = {

@@ -16,3 +33,3 @@ requestId: 'testid',

export function createStub() {
const conn = sinon.createStubInstance(OADAClient);
const conn = createStubInstance(OADAClient);

@@ -19,0 +36,0 @@ conn.get.resolves(emptyResp);

@@ -0,12 +1,28 @@

/**
* @license
* Copyright 2021 Open Ag Data Alliance
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Bluebird from 'bluebird';
import { spy } from 'sinon';
import test from 'ava';
import sinon from 'sinon';
import Bluebird from 'bluebird';
//import { Change } from '@oada/types/oada/change/v2';
// TODO: Fix this
import { Change } from './';
// Import { Change } from '@oada/types/oada/change/v2';
import { createStub } from './conn-stub';
import { ListWatch, Tree, pathFromTree } from './';
import { Change, ListWatch, Tree, pathFromTree } from './';

@@ -23,2 +39,3 @@ const name = 'oada-list-lib-test';

thing: {
// eslint-disable-next-line sonarjs/no-duplicate-string
_type: 'application/json',

@@ -101,2 +118,3 @@ _rev: 0,

// eslint-disable-next-line no-new
new ListWatch({ path, name, conn });

@@ -132,6 +150,6 @@ t.plan(1);

// A Change from adding an item to a list
// TODO: Better way to do this test without actually runnig oada?
// TODO: Better way to do this test without actually running oada?
const path = '/bookmarks';
const id = 'resources/foo';
// @ts-ignore
// @ts-expect-error test
conn.get.resolves({ data: { _id: id } });

@@ -143,2 +161,3 @@ const change: Change[] = [

body: {
// eslint-disable-next-line no-secrets/no-secrets
'1e6XB0Hy7XJICbi3nMzCtl4QLpC': {

@@ -149,3 +168,3 @@ _id: id,

modifiedBy: 'users/default:users_sam_321',
modified: 1593642877.725,
modified: 1_593_642_877.725,
_rev: 4,

@@ -158,6 +177,6 @@ },

];
// @ts-ignore
// @ts-expect-error test
conn.get.resolves({ data: { _rev: 4 } });
const opts = {
const options = {
path,

@@ -167,21 +186,22 @@ name,

// Create spies to see which callbacks run
onAddItem: sinon.spy(),
onChangeItem: sinon.spy(),
onItem: sinon.spy(),
onRemoveItem: sinon.spy(),
onAddItem: spy(),
onChangeItem: spy(),
onItem: spy(),
onRemoveItem: spy(),
};
new ListWatch(opts);
// eslint-disable-next-line no-new
new ListWatch(options);
// TODO: How to do this right in ava?
await Bluebird.delay(delay);
const cb = conn.watch.firstCall?.args?.[0]?.watchCallback as (
const callback = conn.watch.firstCall?.args?.[0]?.watchCallback as (
change: readonly Change[]
) => Promise<void>;
await cb(change);
await callback(change);
t.is(opts.onAddItem.callCount, 1);
t.is(opts.onItem.callCount, 1);
t.is(opts.onChangeItem.callCount, 0);
t.is(opts.onRemoveItem.callCount, 0);
t.is(options.onAddItem.callCount, 1);
t.is(options.onItem.callCount, 1);
t.is(options.onChangeItem.callCount, 0);
t.is(options.onRemoveItem.callCount, 0);
});

@@ -191,3 +211,3 @@ test('it should detect removed item', async (t) => {

// A Change from adding an item to a list
// TODO: Better way to do this test without actually runnig oada?
// TODO: Better way to do this test without actually running oada?
const path = '/bookmarks';

@@ -199,6 +219,7 @@ const change: Change[] = [

body: {
// eslint-disable-next-line no-secrets/no-secrets, unicorn/no-null
'1e6XB0Hy7XJICbi3nMzCtl4QLpC': null,
'_meta': {
modifiedBy: 'users/default:users_sam_321',
modified: 1593642877.725,
modified: 1_593_642_877.725,
_rev: 4,

@@ -212,3 +233,3 @@ },

const opts = {
const options = {
path,

@@ -218,22 +239,23 @@ name,

// Create spies to see which callbacks run
onAddItem: sinon.spy(),
onChangeItem: sinon.spy(),
onItem: sinon.spy(),
onRemoveItem: sinon.spy(),
onAddItem: spy(),
onChangeItem: spy(),
onItem: spy(),
onRemoveItem: spy(),
};
new ListWatch(opts);
// eslint-disable-next-line no-new
new ListWatch(options);
// TODO: How to do this right in ava?
await Bluebird.delay(delay);
const cb = conn.watch.firstCall?.args?.[0]?.watchCallback as (
const callback = conn.watch.firstCall?.args?.[0]?.watchCallback as (
change: readonly Change[]
) => Promise<void>;
await cb(change);
await callback(change);
t.is(opts.onAddItem.callCount, 0);
t.is(opts.onItem.callCount, 0);
t.is(opts.onChangeItem.callCount, 0);
t.is(opts.onRemoveItem.callCount, 1);
t.is(options.onAddItem.callCount, 0);
t.is(options.onItem.callCount, 0);
t.is(options.onChangeItem.callCount, 0);
t.is(options.onRemoveItem.callCount, 1);
});
test.todo('it should detect modified item');

@@ -1,16 +0,34 @@

import { join } from 'path';
/**
* @license
* Copyright 2021 Open Ag Data Alliance
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable unicorn/no-await-expression-member */
import { join } from 'node:path';
import Bluebird from 'bluebird';
import pointer from 'json-pointer';
import { JSONPath } from 'jsonpath-plus';
import PQueue from 'p-queue';
import debug from 'debug';
import pointer from 'json-pointer';
import type { TypeAssert } from '@oada/types';
import type { ConnectionResponse } from '@oada/client';
import type { Link } from '@oada/types/oada/link/v1';
import type { Resource } from '@oada/types/oada/resource';
import type { Link } from '@oada/types/oada/link/v1';
import type V2Changes from '@oada/types/oada/change/v2';
import type { ConnectionResponse } from '@oada/client';
import { Options, ItemState } from './Options';
import { ItemState, Options } from './Options';
import { Metadata } from './Metadata';

@@ -33,3 +51,2 @@

*/
export { Options, ItemState };

@@ -52,7 +69,9 @@ /**

export type TypeAssert<T> = (value: unknown) => asserts value is T;
/**
* Tell TS we should never reach here (i.e., this should never be called)
*/
function assertNever(val: never, mesg?: string): never {
throw new Error(mesg ?? `Bad value: ${val}`);
function assertNever(value: never, message?: string): never {
throw new Error(message ?? `Bad value: ${value}`);
}

@@ -73,2 +92,3 @@

}
return state;

@@ -83,28 +103,8 @@ }

*/
export type List = Resource & {
[key: string]: Link | List;
};
// eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style
export type List = Resource & { [key: string]: Link | List };
/**
* @internal
*/
declare module 'jsonpath-plus' {
interface JSONPathCallable {
(
options: JSONPathOptions & {
resultType: 'path' | 'pointer' | 'parentProperty';
wrap?: true;
}
): string[];
(
path: JSONPathOptions['path'],
json: JSONPathOptions['json'],
callback?: JSONPathOptions['callback'],
otherTypeCallback?: JSONPathOptions['otherTypeCallback']
): any[];
}
}
function getListItems(list: Partial<List>, path: string) {
const pointers = JSONPath({
function getListItems(list: DeepPartial<List>, path: string) {
// eslint-disable-next-line new-cap
return JSONPath<string[]>({
resultType: 'pointer',

@@ -116,6 +116,4 @@ path,

// Don't follow underscore keys
(p) => !/\/_/.test(p)
(p) => !p.includes('/_')
);
return pointers;
}

@@ -131,8 +129,4 @@

_rev?: number;
} & (
| {
[key: string]: Tree;
}
| {}
);
// eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style
} & { [key: string]: Tree };

@@ -143,3 +137,3 @@ /**

* @internal
* @experimental trees with multiple "paths" (excluing *)
* @experimental trees with multiple "paths" (excluding *)
*/

@@ -150,8 +144,10 @@ export function pathFromTree(tree: Tree, root = ''): string {

const json = pointer.get(tree, root);
const json = pointer.get(tree, root) as Tree;
// eslint-disable-next-line no-constant-condition
while (true) {
// Get set of non underscore keys
const keys = [
...new Set(
JSONPath({
const keys = Array.from(
new Set(
// eslint-disable-next-line new-cap
JSONPath<string[]>({
resultType: 'parentProperty',

@@ -161,4 +157,4 @@ path,

}).filter((k) => !k.startsWith('_'))
),
];
)
);
if (keys.length === 0) {

@@ -168,3 +164,4 @@ break;

outPath += '.' + (keys.length === 1 ? keys[0] : `[${keys.join(',')}]`);
// eslint-disable-next-line sonarjs/no-nested-template-literals
outPath += `.${keys.length === 1 ? keys[0] : `[${keys.join(',')}]`}`;

@@ -187,2 +184,21 @@ path += '.*';

/**
* Callback to make ListWatch consider every `Item` new
*
* @see getItemState
* @see onNewList
* @see ItemState.New
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
public static readonly AssumeNew = assumeItemState(ItemState.New);
/**
* Callback to make ListWatch consider every `Item` handled
*
* @see getItemState
* @see onNewList
* @see ItemState.Handled
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
public static readonly AssumeHandled = assumeItemState(ItemState.Handled);
/**
* The OADA path of the List being watched

@@ -205,19 +221,2 @@ */

/**
* Callback to make ListWatch consider every `Item` new
*
* @see getItemState
* @see onNewList
* @see ItemState.New
*/
public static readonly AssumeNew = assumeItemState(ItemState.New);
/**
* Callback to make ListWatch consider every `Item` handled
*
* @see getItemState
* @see onNewList
* @see ItemState.Handled
*/
public static readonly AssumeHandled = assumeItemState(ItemState.Handled);
#resume;

@@ -248,2 +247,3 @@ #conn;

// If no assert given, assume all items valid
// eslint-disable-next-line @typescript-eslint/no-empty-function
assertItem = () => {},

@@ -258,2 +258,3 @@ onAddItem,

error('Unhandled delete of list %s', path);
// eslint-disable-next-line no-process-exit, unicorn/no-process-exit
process.exit();

@@ -280,10 +281,8 @@ },

this.itemsPath = itemsPath;
} else if (tree) {
// Assume items are at the leaves of tree
this.itemsPath = pathFromTree(tree as Tree, path);
} else {
if (tree) {
// Asume items are at the leaves of tree
this.itemsPath = pathFromTree(tree, path);
} else {
// Assume flat list
this.itemsPath = '$.*';
}
// Assume flat list
this.itemsPath = '$.*';
}

@@ -295,11 +294,13 @@

// If no callback provided, ask client for states of pre-existing items
this.#onNewList = (ids: readonly string[]) => {
return Bluebird.map(ids, (id) => {
try {
return this.getItemState(id);
} catch (err) {
error(err, 'Error getting item state');
}
});
};
this.#onNewList = async (ids: readonly string[]) =>
Promise.all(
ids.map(async (id) => {
try {
return await this._getItemState(id);
} catch (cError: unknown) {
error(cError, 'Error getting item state');
throw cError;
}
})
);
}

@@ -309,3 +310,3 @@

// Don't persist metdata if service does not "resume"
//persistInterval: this.#resume ? persistInterval : 0,
// persistInterval: this.#resume ? persistInterval : 0,
conn: this.#resume ? this.#conn : undefined,

@@ -316,3 +317,3 @@ path,

});
this.initialize().catch(error);
this._initialize().catch(error);
}

@@ -333,25 +334,48 @@

) {
const { path } = this;
const { path, itemsPath } = this;
const conn = this.#conn;
const { data: list } = (await conn.get({ path })) as GetResponse<List>;
//const items = Object.keys(list).filter((k) => !k.match(/^_/));
const items = getListItems(list, this.itemsPath);
if (Buffer.isBuffer(list)) {
throw new TypeError('List is not a JSON object');
}
//const { rev } = this.#meta;
await Bluebird.map(items, async (id) => {
try {
if (!all && this.#meta.handled(id)) {
// We think this item is handled
return;
// Const items = Object.keys(list).filter((k) => !k.match(/^_/));
const items = getListItems(list as DeepPartial<List>, itemsPath);
// Const { rev } = this.#meta;
await Promise.all(
items.map(async (id) => {
try {
if (!all && (await this.#meta.handled(id))) {
// We think this item is handled
return;
}
// Ask lib user for state of this item
const state = await this._getItemState(id);
await this._updateItemState(list, id, state);
} catch (cError: unknown) {
error(cError);
}
})
);
}
// Ask lib user for state of this item
const state = await this.getItemState(id);
/**
* Clean up metadata and unwatch list
*/
public async stop() {
await this.#conn.unwatch(this.#id!);
await this.persistMeta();
// This.#meta.stop();
}
await this.updateItemState(list, id, state);
} catch (err: unknown) {
error(err);
}
});
/**
* Persist relevant info to the `_meta` of the list.
* This preserves it across restarts.
*/
public async persistMeta() {
// Await this.#meta.persist();
}

@@ -364,3 +388,3 @@

*/
private async getItemState(id: string): Promise<ItemState> {
private async _getItemState(id: string): Promise<ItemState> {
// Needed because TS is weird about asserts...

@@ -375,25 +399,8 @@ const assertItem: TypeAssert<Item> = this.#assertItem;

return this.#getItemState(id, item);
} else {
return this.#getItemState(id);
}
}
/**
* Clean up metadata and unwatch list
*/
public async stop() {
await this.#conn.unwatch(this.#id!);
await this.persistMeta();
//this.#meta.stop();
return this.#getItemState(id);
}
/**
* Persist relevant info to the `_meta` of the list.
* This preserves it across restarts.
*/
public async persistMeta() {
//await this.#meta.persist();
}
private async handleNewItem(rev: string, id: string, item: Resource) {
private async _handleNewItem(rev: string, id: string, item: Resource) {
const { path } = this;

@@ -411,3 +418,3 @@ // Needed because TS is weird about asserts...

await (this.#onAddItem && this.#onAddItem(item, id));
await this.#meta.setHandled(id, { onAddItem: { rev: _rev + '' } });
await this.#meta.setHandled(id, { onAddItem: { rev: `${_rev}` } });
}

@@ -421,6 +428,7 @@ } finally {

// Double check this item is actually newer than last time
if (+_rev > +((await this.#meta.handled(id))?.onItem?.rev ?? 0)) {
// TODO: Why doesn't this.#onItem?.() work?
if (
Number(_rev) > Number((await this.#meta.handled(id))?.onItem?.rev ?? 0)
) {
await (this.#onItem && this.#onItem(item, id));
await this.#meta.setHandled(id, { onItem: { rev: _rev + '' } });
await this.#meta.setHandled(id, { onItem: { rev: `${_rev}` } });
}

@@ -430,6 +438,6 @@ }

private async handleItemChange(id: string, change: Change) {
private async _handleItemChange(id: string, change: Change) {
const { path } = this;
const conn = this.#conn;
const rev = change.body._rev as string;
const rev = change.body?._rev;

@@ -439,6 +447,6 @@ // TODO: How best to handle change to a descendant of an item?

const { _rev } = change.body;
const { _rev } = change.body as Resource;
try {
await (this.#onChangeItem && this.#onChangeItem(change, id));
await this.#meta.setHandled(id, { onChangeItem: { rev: _rev + '' } });
await this.#meta.setHandled(id, { onChangeItem: { rev: `${_rev}` } });
} finally {

@@ -454,3 +462,3 @@ if (this.#onItem) {

await this.#onItem(item, id);
await this.#meta.setHandled(id, { onItem: { rev: _rev + '' } });
await this.#meta.setHandled(id, { onItem: { rev: `${_rev}` } });
}

@@ -460,12 +468,12 @@ }

private async handleListChange(
private async _handleListChange(
list: DeepPartial<List>,
type: Change['type']
): Promise<boolean> {
const { path } = this;
const { path, itemsPath } = this;
const conn = this.#conn;
const rev = list._rev;
// Ignore _ keys of OADA
//const items = Object.keys(list).filter((k) => !k.match(/^_/));
const items = getListItems(list as List, this.itemsPath);
// const items = Object.keys(list).filter((k) => !k.match(/^_/));
const items = getListItems(list, itemsPath);
trace(items, 'handleListChange');

@@ -475,72 +483,79 @@

case 'merge':
await Bluebird.map(items, async (id) => {
try {
trace('handleListChange: Processing item %s', id);
const lchange = pointer.get(list, id) as Partial<Link>;
trace(lchange, 'handleListChange: lchange');
await Promise.all(
items.map(async (id) => {
try {
trace('handleListChange: Processing item %s', id);
const ichang = pointer.get(list, id) as Partial<Link>;
trace(ichang, 'handleListChange');
// If there is an _id this is a new link in the list right?
if (lchange._id) {
trace(
'handleListChange: lchange has an _id, getting it and handing to handleNewItem'
// If there is an _id this is a new link in the list right?
if (ichang._id) {
trace(
'handleListChange: change has an _id, getting it and handing to handleNewItem'
);
const { data: item } = (await conn.get({
// Joining path and id fails when jobs get moved into success/failure. Instead get the _id
// path: join(path, id),
path: `/${id}`,
})) as GetResponse<Resource>;
await this._handleNewItem(`${rev}`, id, item);
} else {
// TODO: What should we do now??
trace(
'Ignoring non-link key added to list %s, rev %s',
path,
rev
);
}
} catch (cError: unknown) {
// Log error with this item but continue map over other items
error(
cError,
`Error processing change for ${id} at ${path}, rev ${rev}`
);
const { data: item } = (await conn.get({
//joining path and id fails when jobs get moved into success/failure. Instead get the _id
// path: join(path, id),
path: `/${_id}`,
})) as GetResponse<Resource>;
await this.handleNewItem(rev + '', id, item);
} else {
// TODO: What should we do now??
trace(
'Ignoring non-link key added to list %s, rev %s',
path,
rev
);
}
} catch (err: unknown) {
// Log error with this item but continue map over other items
error(
err,
`Error processing change for ${id} at ${path}, rev ${rev}`
);
}
});
})
);
break;
case 'delete':
await Bluebird.map(items, async (id) => {
try {
const lchange = pointer.get(list, id);
await Promise.all(
items.map(async (id) => {
try {
const lChange = pointer.get(list, id) as Partial<Link>;
if (lchange === null) {
info(
'Detected removal of item %s from %s, rev %s',
id,
path,
rev
);
try {
await (this.#onRemoveItem && this.#onRemoveItem(id));
} finally {
// Mark for delete?
await this.#meta.setHandled(id, undefined);
if (lChange === null) {
info(
'Detected removal of item %s from %s, rev %s',
id,
path,
rev
);
try {
await (this.#onRemoveItem && this.#onRemoveItem(id));
} finally {
// Mark for delete?
await this.#meta.setHandled(id);
}
} else {
// TODO: What does this mean??
trace(
'Ignoring non-link key added to list %s, rev %s',
path,
rev
);
}
} else {
// TODO: What does this mean??
trace(
'Ignoring non-link key added to list %s, rev %s',
path,
rev
} catch (cError: unknown) {
// Log error with this item but continue map over other items
error(
cError,
`Error processing change for ${id} at ${path}, rev ${rev}`
);
}
} catch (err: unknown) {
// Log error with this item but continue map over other items
error(
err,
`Error processing change for ${id} at ${path}, rev ${rev}`
);
}
});
})
);
break;
default:
throw new TypeError(`Unknown change type ${type}`);
}

@@ -556,3 +571,3 @@

*/
private async updateItemState(
private async _updateItemState(
list: List,

@@ -567,51 +582,58 @@ ids: string | readonly string[],

const _states = (Array.isArray(states) ? states : [states]) as ItemState[];
await Bluebird.map(_ids, async (id, i) => {
const state = _states[i];
try {
switch (state) {
case ItemState.New:
{
const { data: item } = (await this.#conn.get({
path: join(path, id),
})) as GetResponse<Resource>;
await this.handleNewItem(list._rev + '', id, item);
}
break;
case ItemState.Modified:
{
const { data: item } = await this.#conn.get({
path: join(path, id),
await Promise.all(
_ids.map(async (id, index) => {
const state = _states[Number(index)];
try {
switch (state) {
case ItemState.New:
{
const { data: item } = (await this.#conn.get({
path: join(path, id),
})) as GetResponse<Resource>;
await this._handleNewItem(`${list._rev}`, id, item);
}
break;
case ItemState.Modified:
{
const { data: item } = await this.#conn.get({
path: join(path, id),
});
const change: Change = {
resource_id: pointer.get(list, id)._id as string,
path: '',
type: 'merge',
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
body: item as any,
};
await this._handleItemChange(id, change);
}
break;
case ItemState.Handled:
info('Recording item %s as handled for %s', id, path);
// Mark handled for all callbacks?
await this.#meta.setHandled(id, {
onAddItem: { rev },
onItem: { rev },
});
const change: Change = {
resource_id: pointer.get(list, id)._id,
path: '',
// TODO: what is the type the change??
type: 'merge',
body: item as {},
};
await this.handleItemChange(id, change);
}
break;
case ItemState.Handled:
info('Recording item %s as handled for %s', id, path);
// Mark handled for all callbacks?
await this.#meta.setHandled(id, {
onAddItem: { rev },
onItem: { rev },
});
break;
default:
assertNever(state);
break;
default:
assertNever(state);
}
} catch (cError: unknown) {
error(
cError,
`Error processing item state "${state}" for item ${id}`
);
}
} catch (err: unknown) {
error(err, `Error processing item state "${state}" for item ${id}`);
}
});
})
);
}
/**
* Do async stuff for initializing ourself since constructors are syncronous
* Do async stuff for initializing ourself since constructors are synchronous
*/
private async initialize() {
const { path, tree } = this;
private async _initialize() {
const { path, tree, itemsPath } = this;
const conn = this.#conn;

@@ -622,4 +644,5 @@

await conn.head({ path });
} catch (err) {
if (err.status === 403 || err.status === 404) {
} catch (cError: unknown) {
// @ts-expect-error darn errors
if (cError?.status === 403 || cError?.status === 404) {
// Create it

@@ -629,4 +652,4 @@ await conn.put({ path, tree, data: {} });

} else {
error(err);
throw err;
error(cError);
throw cError;
}

@@ -643,6 +666,6 @@ }

})) as GetResponse<List>;
//const items = Object.keys(list).filter((k) => !k.match(/^_/));
const items = getListItems(list, this.itemsPath);
// Const items = Object.keys(list).filter((k) => !k.match(/^_/));
const items = getListItems(list as DeepPartial<List>, itemsPath);
// ask for states of pre-existing items
// Ask for states of pre-existing items
trace('Calling onNewList');

@@ -652,3 +675,3 @@ const states = await this.#onNewList(items);

trace('Updating item states based on callback result');
await this.updateItemState(list, items, states);
await this._updateItemState(list, items, states);
}

@@ -660,4 +683,6 @@

}
// Queue to handle changes in order
const changeQueue = new PQueue({ concurrency: 1 });
// eslint-disable-next-line security/detect-non-literal-fs-filename
this.#id = await conn.watch({

@@ -667,3 +692,3 @@ path,

type: 'tree',
watchCallback: (changes) =>
watchCallback: async (changes) =>
changeQueue.add(async () => {

@@ -675,3 +700,3 @@ // Get root change?

await Bluebird.each(changes, async (change) => {
const { type, path: changePath, body, ...ctx } = change;
const { type, path: changePath, body, ...context } = change;

@@ -686,7 +711,7 @@ if (body === null && type === 'delete' && changePath === '') {

const rev = (body as Change['body'])._rev as string;
const rev = (body as Change['body'])?._rev;
trace(change, 'Received change');
let itemsFound = !!changePath;
let itemsFound = Boolean(changePath);
let listChange = body as DeepPartial<List>;

@@ -702,15 +727,16 @@ try {

// Reconstruct change to list?
const changeObj = {};
const changeObject = {};
let isListChange = false;
if (this.itemsPath) {
// just put true here for now to check if path matches
pointer.set(changeObj, changePath, true);
const pathmatches = JSONPath({
if (itemsPath) {
// Just put true here for now to check if path matches
pointer.set(changeObject, changePath, true);
// eslint-disable-next-line new-cap
const pathmatches = JSONPath<string[]>({
resultType: 'pointer',
path: this.itemsPath,
json: changeObj,
path: itemsPath,
json: changeObject,
preventEval: true,
});
if (pathmatches?.length === 0) {
// if it does not match, this must be above the items
// If it does not match, this must be above the items
isListChange = true;

@@ -723,34 +749,45 @@ trace(

// now put the actual change body in place of the true
pointer.set(changeObj, changePath, body);
// Now put the actual change body in place of the true
pointer.set(changeObject, changePath, body);
// Find items involved in the change
const itemsChanged = getListItems(changeObj, this.itemsPath);
const itemsChanged = getListItems(changeObject, itemsPath);
// The change was to items of the list (or their descendants)
if (!isListChange && itemsChanged.length >= 1) {
return Bluebird.map(itemsChanged, (item) => {
const body = pointer.get(changeObj, item);
// Make change start at item instead of the list
const path = changePath.slice(item.length);
const change: Change = {
...ctx,
type,
path,
body,
};
// Check that it is a resource change?
if (!body._rev) {
warn(
change,
'Ignoring unexpected (as in the body does not have a _rev) change'
);
return;
}
return this.handleItemChange(item, change);
});
} else {
// The change is between the list and items
// (multiple link levels)
listChange = changeObj;
if (!isListChange && itemsChanged.length > 0) {
return await Promise.all(
itemsChanged.map((item) => {
const itemBody: unknown = pointer.get(changeObject, item);
// Make change start at item instead of the list
const itemPath = changePath.slice(item.length);
const itemChange: Change = {
...context,
type,
path: itemPath,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
body: itemBody as any,
};
// Check that it is a resource change?
if (
!(
typeof itemBody === 'object' &&
itemBody &&
'_rev' in itemBody
)
) {
warn(
itemChange,
'Ignoring unexpected (as in the body does not have a _rev) change'
);
return;
}
return this._handleItemChange(item, itemChange);
})
);
}
// The change is between the list and items
// (multiple link levels)
listChange = changeObject;
}
trace(

@@ -761,5 +798,5 @@ 'Change was to the list itself because changePath is empty, calling handleListChange'

itemsFound =
(await this.handleListChange(listChange, type)) || itemsFound;
} catch (err: unknown) {
error(err, `Error processing change at ${path}, rev ${rev}`);
(await this._handleListChange(listChange, type)) || itemsFound;
} catch (cError: unknown) {
error(cError, `Error processing change at ${path}, rev ${rev}`);
}

@@ -772,3 +809,3 @@ });

);
this.#meta.rev = (rootChange.body as Resource)?._rev + '';
this.#meta.rev = `${(rootChange.body as Resource)?._rev}`;
}

@@ -783,5 +820,7 @@ }),

function stateCBnoItem<Item>(
cb: ItemStateNoItemCB | NonNullable<Options<Item>['getItemState']>
): cb is ItemStateNoItemCB {
return cb.length < 2;
callback: ItemStateNoItemCB | NonNullable<Options<Item>['getItemState']>
): callback is ItemStateNoItemCB {
return callback.length < 2;
}
export { Options, ItemState } from './Options';

@@ -0,12 +1,28 @@

/**
* @license
* Copyright 2021 Open Ag Data Alliance
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Bluebird from 'bluebird';
import { spy } from 'sinon';
import test from 'ava';
import sinon from 'sinon';
import Bluebird from 'bluebird';
//import { Change } from '@oada/types/oada/change/v2';
// TODO: Fix this
import { Change } from './';
// Import { Change } from '@oada/types/oada/change/v2';
import { createStub } from './conn-stub';
import { ListWatch } from './';
import { Change, ListWatch } from './';
import { PUTRequest } from '@oada/client';

@@ -19,10 +35,10 @@

// A Change from adding an item to a list
// TODO: Better way to do this test without actually runnig oada?
// TODO: Better way to do this test without actually running oada?
const path = '/bookmarks';
const rev = '766';
// @ts-ignore
// @ts-expect-error test
conn.get.resolves({ data: rev });
const opts = {
const options = {
path,

@@ -33,9 +49,10 @@ name,

// Create spies to see which callbacks run
onAddItem: sinon.spy(),
onChangeItem: sinon.spy(),
onItem: sinon.spy(),
onRemoveItem: sinon.spy(),
onAddItem: spy(),
onChangeItem: spy(),
onItem: spy(),
onRemoveItem: spy(),
};
new ListWatch(opts);
// eslint-disable-next-line no-new
new ListWatch(options);
// TODO: How to do this right in ava?

@@ -49,3 +66,3 @@ await Bluebird.delay(5);

// A Change from adding an item to a list
// TODO: Better way to do this test without actually runnig oada?
// TODO: Better way to do this test without actually running oada?
const path = '/bookmarks';

@@ -57,2 +74,3 @@ const change: Change[] = [

body: {
// eslint-disable-next-line no-secrets/no-secrets
'1e6XB0Hy7XJICbi3nMzCtl4QLpC': {

@@ -63,6 +81,6 @@ _id: '',

modifiedBy: 'users/default:users_sam_321',
modified: 1593642877.725,
modified: 1_593_642_877.725,
_rev: '4',
},
'_rev': '4',
'_rev': 4,
},

@@ -73,3 +91,3 @@ type: 'merge',

const opts = {
const options = {
path,

@@ -81,19 +99,20 @@ name,

// Create spies to see which callbacks run
onAddItem: sinon.spy(),
onChangeItem: sinon.spy(),
onItem: sinon.spy(),
onRemoveItem: sinon.spy(),
onAddItem: spy(),
onChangeItem: spy(),
onItem: spy(),
onRemoveItem: spy(),
};
// @ts-ignore
// @ts-expect-error test
conn.get.resolves({ data: {} });
new ListWatch(opts);
// eslint-disable-next-line no-new
new ListWatch(options);
// TODO: How to do this right in ava?
await Bluebird.delay(5);
const cb = conn.watch.firstCall?.args?.[0]?.watchCallback as (
const callback = conn.watch.firstCall?.args?.[0]?.watchCallback as (
change: readonly Change[]
) => Promise<void>;
await cb(change);
await callback(change);

@@ -100,0 +119,0 @@ await Bluebird.delay(500);

@@ -1,3 +0,20 @@

import { join } from 'path';
/**
* @license
* Copyright 2021 Open Ag Data Alliance
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { join } from 'node:path';
import Bluebird from 'bluebird';

@@ -17,10 +34,8 @@ import clone from 'clone-deep';

*/
export interface Item {
/**
* The callback which ran on item
*/
[callback: string]: {
export type Item = Record<
string,
{
rev: string;
};
}
}
>;

@@ -32,10 +47,4 @@ /**

*/
export type Items = {
/**
* The list item(s) which ran
*
* Items can be nested
*/
[key: string]: undefined | Item | Items;
};
// eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style
export type Items = { [key: string]: undefined | Item | Items };

@@ -51,3 +60,6 @@ /**

*/
public static readonly META_KEY = 'oada-list-lib';
// eslint-disable-next-line @typescript-eslint/naming-convention
public static get META_KEY() {
return 'oada-list-lib';
}

@@ -62,11 +74,67 @@ /**

#path;
#tree?: object;
#timeout;
#tree?: Record<string, unknown>;
#timeout: NodeJS.Timeout;
// Init stuff?
#done!: (err?: any) => void;
#done!: (error?: unknown) => void;
#wait: Promise<unknown>;
constructor({
conn,
path,
tree,
name,
}: {
/**
* The path to the resource with which to associate this metadata
*/
path: string;
/**
* Optional OADA tree corresponding to `path`
*/
tree?: Record<string, unknown>;
name: string;
conn?: Conn;
}) {
this.#conn = conn;
this.#path = join(path, '_meta', Metadata.META_KEY, name);
this.#tree = clone(tree);
if (this.#tree) {
// Replicate list tree under handled key?
const listTree: unknown = clone(pointer.get(this.#tree, path));
pointer.set(this.#tree, this.#path, {
_type: 'application/json',
handled: listTree,
});
} else {
// Make up a tree? idk man
this.#tree = {};
pointer.set(this.#tree, this.#path, {
_type: 'application/json',
handled: { '*': {} },
});
}
// Console.dir(this.#tree, { depth: null });
this.#wait = Bluebird.fromCallback((done) => {
this.#done = done;
});
// TODO: Use timeouts for all updates?
this.#timeout = setTimeout(async () => {
await this.#wait;
trace('Recording rev %s', this.#rev);
this.#wait = Promise.resolve(
this.#conn?.put({
path: this.#path,
tree: this.#tree,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
data: { rev: this.#rev } as any,
})
);
}, 100);
}
get rev(): string {
return this.#rev + '';
return `${this.#rev}`;
}
set rev(rev) {

@@ -77,2 +145,3 @@ if (this.#rev === rev) {

}
trace(`Updating local rev to ${rev}`);

@@ -89,3 +158,3 @@ this.#rev = rev;

*/
async setHandled(path: string, item: Item | undefined) {
async setHandled(path: string, item?: Item) {
if (item) {

@@ -100,3 +169,4 @@ // Merge with current info

tree: this.#tree,
data: data,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
data,
});

@@ -107,3 +177,3 @@ } else {

}
//this.#updated = true;
// This.#updated = true;
}

@@ -131,55 +201,2 @@

constructor({
conn,
path,
tree,
name,
}: {
/**
* The path to the resource with which to associate this metadata
*/
path: string;
/**
* Optional OADA tree corresponding to `path`
*/
tree?: object;
name: string;
conn?: Conn;
}) {
this.#conn = conn;
this.#path = join(path, '_meta', Metadata.META_KEY, name);
this.#tree = clone(tree);
if (this.#tree) {
// Replicate list tree under handled key?
const listTree = clone(pointer.get(this.#tree, path));
pointer.set(this.#tree, this.#path, {
_type: 'application/json',
handled: listTree,
});
} else {
// Make up a tree? idk man
this.#tree = {};
pointer.set(this.#tree, this.#path, {
_type: 'application/json',
handled: { '*': {} },
});
}
//console.dir(this.#tree, { depth: null });
this.#wait = Bluebird.fromCallback((done) => {
this.#done = done;
});
// TODO: Use timeouts for all updates?
this.#timeout = setTimeout(async () => {
await this.#wait;
trace('Recording rev %s', this.#rev);
this.#wait = Promise.resolve(
this.#conn?.put({
path: this.#path,
tree: this.#tree,
data: { rev: this.#rev } as any,
})
);
}, 100);
}
/**

@@ -198,2 +215,3 @@ * Initialize the connection to the meta resource

}
// Try to get our metadata about this list

@@ -207,3 +225,3 @@ try {

return true;
} catch (err: unknown) {
} catch {
// Create our metadata?

@@ -219,3 +237,3 @@ const {

tree: this.#tree,
data: { _id: location.substring(1) },
data: { _id: location.slice(1) },
});

@@ -225,2 +243,3 @@ await this.#conn.put({

tree: this.#tree,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
data: {

@@ -233,7 +252,7 @@ rev: this.#rev,

}
} catch (err: unknown) {
this.#done(err);
throw err;
} catch (error: unknown) {
this.#done(error);
throw error;
}
}
}

@@ -1,10 +0,27 @@

import type { TypeAssert } from '@oada/types';
//import type { Change } from '@oada/types/oada/change/v2';
/**
* @license
* Copyright 2021 Open Ag Data Alliance
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// TODO: Fix this
import type { Change } from './';
// Import type { Change } from '@oada/types/oada/change/v2';
import type { Change, TypeAssert } from './';
import type { OADAClient } from '@oada/client';
/**
* Type that can be either T or a Promise which resovles to T
* Type that can be either T or a Promise which resolves to T
*/

@@ -14,3 +31,3 @@ type AllowPromise<T> = T | Promise<T>;

/**
* The type for the object given to the construtor
* The type for the object given to the constructor
*

@@ -40,3 +57,3 @@ * @public

*/
tree?: object;
tree?: Record<string, unknown>;
/**

@@ -49,3 +66,3 @@ *, 'data'> A persistent name/id for this instance (can just be random string)

/**
* true: "resume" change feed for list from last processed rev
* True: "resume" change feed for list from last processed rev
* false: just start from current state of the list

@@ -52,0 +69,0 @@ *

@@ -16,3 +16,3 @@ {

},
"exclude": ["lib"]
"exclude": ["lib", "**/*.spec.ts"]
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

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