From 2bef4a03190937ae938ccfb14337f7462db120eb Mon Sep 17 00:00:00 2001 From: dualfroz Date: Sat, 3 Oct 2026 03:19:54 +0200 Subject: [PATCH] Find payloads by term id so they survive a .remove() --- plugins/payload/src/plugin.js | 84 +++++++++++++++++---------- plugins/payload/tests/payload.test.js | 23 ++++++++ 2 files changed, 77 insertions(+), 30 deletions(-) diff --git a/plugins/payload/src/plugin.js b/plugins/payload/src/plugin.js index 072c728..fbea83e 100644 --- a/plugins/payload/src/plugin.js +++ b/plugins/payload/src/plugin.js @@ -1,5 +1,48 @@ import debug from './debug.js' +// payloads are stored by sentence index, which goes stale after a .remove() +// so find each one again by the ids of its first and last terms +const findPayloads = function (view) { + const db = view.world.model.one.db || {} + const where = new Map() + view.document.forEach((terms, n) => { + terms.forEach((term, i) => where.set(term.id, [n, i])) + }) + const found = [] + Object.keys(db).forEach(k => { + db[k].forEach(obj => { + const [, , , startId, endId] = obj.ptr + const start = where.get(startId) + const end = where.get(endId) + // its words were removed + if (!start || !end || start[0] !== end[0]) { + return + } + found.push({ k, obj, ptr: [start[0], start[1], end[1] + 1, startId, endId] }) + }) + }) + return found +} + +// all payloads inside our current matches +const getPayloads = function (view) { + const payloads = findPayloads(view) + const res = [] + view.fullPointer.forEach(ptr => { + const seeking = view.update([ptr]) + payloads.forEach(found => { + if (found.ptr[0] !== ptr[0]) { + return + } + const m = view.update([found.ptr]) + if (seeking.has(m)) { + res.push({ ...found, match: m }) + } + }) + }) + return res +} + export default { //establish payload db mutate: function (world) { @@ -11,25 +54,12 @@ export default { api: function (View) { /** return any data on our given matches */ View.prototype.getPayloads = function () { - let res = [] - const db = this.world.model.one.db || {} - this.fullPointer.forEach(ptr => { - const n = ptr[0] - if (Object.hasOwn(db, n)) { - // look at all vals for this sentence - const seeking = this.update([ptr]) - db[n].forEach(obj => { - const m = this.update([obj.ptr]) - if (seeking.has(m)) { - res = res.concat({ - match: m, - val: obj.val, - }) - } - }) + return getPayloads(this).map(found => { + return { + match: found.match, + val: found.obj.val, } }) - return res } /** add data about our current matches */ @@ -56,21 +86,15 @@ export default { View.prototype.clearPayloads = function () { const db = this.world.model.one.db || {} // get each payload - const res = this.getPayloads() - res.forEach(obj => { - const ptr = obj.match.fullPointer[0] || [] - const [n, start, end] = ptr - db[n] ||= [] + getPayloads(this).forEach(({ k, obj }) => { + if (!Object.hasOwn(db, k)) { + return + } // remove it from our list of payloads - db[n] = db[n].filter(r => { - if (r.ptr[1] === start && r.ptr[2] === end) { - return false - } - return true - }) + db[k] = db[k].filter(r => r !== obj) // clean-up any empty arrays - if (db[n].length === 0) { - delete db[n] + if (db[k].length === 0) { + delete db[k] } }) return this diff --git a/plugins/payload/tests/payload.test.js b/plugins/payload/tests/payload.test.js index 341644b..6488c70 100644 --- a/plugins/payload/tests/payload.test.js +++ b/plugins/payload/tests/payload.test.js @@ -57,3 +57,26 @@ test('payload-fn', function (t) { t.equal(doc.getPayloads().length, 5, '5-now') t.end() }) + +test('payload-after-remove', function (t) { + let doc = nlp('one apple. two pears. three plums.') + doc.match('(apple|plums)').addPayload(m => ({ fruit: m.text('normal') })) + t.equal(doc.getPayloads().length, 2, 'before-remove') + + doc.remove('two pears') + let fruit = doc.getPayloads().map(p => p.val.fruit) + t.deepEqual(fruit, ['apple', 'plums'], 'after-remove') + t.equal(doc.match('plums').getPayloads().length, 1, 'plums-still-found') + + doc.match('plums').clearPayloads() + fruit = doc.getPayloads().map(p => p.val.fruit) + t.deepEqual(fruit, ['apple'], 'clear-after-remove') + + // removing the payload's own words drops it + doc = nlp('one apple. two pears. three plums.') + doc.match('(apple|pears|plums)').addPayload(m => ({ fruit: m.text('normal') })) + doc.remove('two pears') + fruit = doc.getPayloads().map(p => p.val.fruit) + t.deepEqual(fruit, ['apple', 'plums'], 'removed-payload-is-gone') + t.end() +})