From 1b4a9c006da91c7f61923002415d0ea2461981af Mon Sep 17 00:00:00 2001 From: "guillem.cordoba" Date: Tue, 15 Oct 2024 13:41:41 +0200 Subject: [PATCH] queryLiveEntriesSignal --- packages/signals/package.json | 81 +++++++++++------------ packages/signals/src/holochain.ts | 105 ++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+), 42 deletions(-) diff --git a/packages/signals/package.json b/packages/signals/package.json index 066126b..740405e 100644 --- a/packages/signals/package.json +++ b/packages/signals/package.json @@ -1,43 +1,40 @@ { - "name": "@holochain-open-dev/signals", - "version": "0.300.7", - "description": "Holochain async-signals to build reusable holochain-open-dev modules", - "author": "guillem.cordoba@gmail.com", - "main": "dist/index.js", - "module": "dist/index.js", - "types": "dist/index.d.ts", - "files": [ - "dist", - "src" - ], - "exports": { - ".": "./dist/index.js", - "./dist/*": "./dist/*" - }, - "scripts": { - "start": "vite --open", - "build": "npm run lint && tsc", - "lint": "eslint src", - "test": "vitest run", - "prepublish": "npm run build" - }, - "dependencies": { - "@holochain-open-dev/utils": "^0.300.2", - "@holochain/client": "^0.17.1", - "@shoelace-style/shoelace": "^2.11.2", - "async-signals": "^0.1.10", - "lit-signal-watcher": "^0.1.1", - "signal-polyfill": "^0.1.1" - }, - "devDependencies": { - "@types/lodash-es": "^4.17.6", - "js-base64": "^3.7.7", - "typescript": "^5.4.0", - "vite": "^4.1.1", - "vitest": "^1.5.0" - }, - "publishConfig": { - "access": "public" - }, - "type": "module" -} \ No newline at end of file + "name": "@holochain-open-dev/signals", + "version": "0.300.8", + "description": "Holochain async-signals to build reusable holochain-open-dev modules", + "author": "guillem.cordoba@gmail.com", + "main": "dist/index.js", + "module": "dist/index.js", + "types": "dist/index.d.ts", + "files": ["dist", "src"], + "exports": { + ".": "./dist/index.js", + "./dist/*": "./dist/*" + }, + "scripts": { + "start": "vite --open", + "build": "npm run lint && tsc", + "lint": "eslint src", + "test": "vitest run", + "prepublish": "npm run build" + }, + "dependencies": { + "@holochain-open-dev/utils": "^0.300.2", + "@holochain/client": "^0.17.1", + "@shoelace-style/shoelace": "^2.11.2", + "async-signals": "^0.1.10", + "lit-signal-watcher": "^0.1.1", + "signal-polyfill": "^0.1.1" + }, + "devDependencies": { + "@types/lodash-es": "^4.17.6", + "js-base64": "^3.7.7", + "typescript": "^5.4.0", + "vite": "^4.1.1", + "vitest": "^1.5.0" + }, + "publishConfig": { + "access": "public" + }, + "type": "module" +} diff --git a/packages/signals/src/holochain.ts b/packages/signals/src/holochain.ts index eba97e7..a3405c0 100644 --- a/packages/signals/src/holochain.ts +++ b/packages/signals/src/holochain.ts @@ -837,3 +837,108 @@ export function deletedLinksSignal< return signal; } + +// Keeps an up to date list of the entries in the agent's source chain for the given entry type +export function queryLiveEntriesSignal< + T, + S extends ActionCommittedSignal & any, +>( + client: ZomeClient, + queryEntries: () => Promise>>, + entry_type: string, + pollIntervalMs: number = 20_000, +): AsyncSignal>> { + let active = false; + let unsubs: () => void | undefined; + let queriedEntries: Array> | undefined; + const signal = new Signal.State>>>( + { status: 'pending' }, + { + [Signal.subtle.watched]: () => { + active = true; + const fetch = async () => { + if (!active) return; + + const nQueriedEntries = await queryEntries().finally(() => { + if (active) { + setTimeout(() => fetch(), pollIntervalMs); + } + }); + if ( + queriedEntries === undefined || + !areArrayHashesEqual( + queriedEntries.map(r => r.actionHash), + nQueriedEntries.map(r => r.actionHash), + ) + ) { + queriedEntries = nQueriedEntries; + signal.set({ + status: 'completed', + value: queriedEntries, + }); + } + }; + fetch().catch(error => { + signal.set({ + status: 'error', + error, + }); + }); + unsubs = client.onSignal(async originalSignal => { + if (!active) return; + if (!(originalSignal as ActionCommittedSignal).type) return; + const hcSignal = originalSignal as ActionCommittedSignal; + + if ( + hcSignal.type === 'EntryCreated' && + hcSignal.app_entry.type === entry_type + ) { + const newEntry = new EntryRecord({ + entry: { + Present: { + entry_type: 'App', + entry: encode(hcSignal.app_entry), + }, + }, + signed_action: hcSignal.action, + }); + if (!queriedEntries) queriedEntries = []; + queriedEntries.push(newEntry); + signal.set({ + status: 'completed', + value: queriedEntries, + }); + } else if (hcSignal.type === 'EntryDeleted') { + if ( + queriedEntries?.find( + e => + e.actionHash.toString() === + hcSignal.action.hashed.content.deletes_address.toString(), + ) + ) { + queriedEntries = queriedEntries.filter( + e => + e.actionHash.toString() !== + hcSignal.action.hashed.content.deletes_address.toString(), + ); + signal.set({ + status: 'completed', + value: queriedEntries, + }); + } + } + }); + }, + [Signal.subtle.unwatched]: () => { + signal.set({ + status: 'pending', + }); + active = false; + queriedEntries = undefined; + unsubs(); + }, + }, + ); + + return signal; +}