@oada/list-lib
Advanced tools
Comparing version 2.1.10 to 2.1.11
@@ -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 |
"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 |
349
lib/index.js
"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'); |
625
src/index.ts
@@ -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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
2736841
188
81569
41
1
36
1
Updated@oada/client@^2.6.5
Updated@oada/types@^1.7.0
Updateddebug@^4.3.3
Updatedjson-pointer@^0.6.1