diff --git a/archaeologist/src/background/search/similarity.ts b/archaeologist/src/background/search/similarity.ts index bfd42ec7..0183dbcf 100644 --- a/archaeologist/src/background/search/similarity.ts +++ b/archaeologist/src/background/search/similarity.ts @@ -179,7 +179,7 @@ export function addNode(node: TNode): void { } export async function register(storage: StorageApi) { - const iter = storage.node.iterate() + const iter = await storage.node.iterate() while (true) { const node = await iter.next() if (node) { diff --git a/archaeologist/src/background/suggestAssociations.ts b/archaeologist/src/background/suggestAssociations.ts index 8c9682ab..dfe39c68 100644 --- a/archaeologist/src/background/suggestAssociations.ts +++ b/archaeologist/src/background/suggestAssociations.ts @@ -7,7 +7,7 @@ export async function suggestAssociations( limit?: number ): Promise { const beagle = Beagle.fromString(phrase) - const iter = storage.node.iterate() + const iter = await storage.node.iterate() const suggested: TNode[] = [] limit = limit ?? 8 // FIXME(akindyakov): This is a dirty hack to limit time of search by limiting diff --git a/archaeologist/src/omnibox/omnibox.ts b/archaeologist/src/omnibox/omnibox.ts index 4226abb6..f87e9877 100644 --- a/archaeologist/src/omnibox/omnibox.ts +++ b/archaeologist/src/omnibox/omnibox.ts @@ -51,7 +51,7 @@ const lookUpAndSuggestFor = lodash.debounce( suggest: (suggestResults: browser.Omnibox.SuggestResult[]) => void ): Promise => { const beagle = Beagle.fromString(text) - const iter = storage.node.iterate() + const iter = await storage.node.iterate() const suggestions: browser.Omnibox.SuggestResult[] = [] for ( let node = await iter.next(); diff --git a/archaeologist/src/storage_api_browser_ext.ts b/archaeologist/src/storage_api_browser_ext.ts index b58d1566..a727444f 100644 --- a/archaeologist/src/storage_api_browser_ext.ts +++ b/archaeologist/src/storage_api_browser_ext.ts @@ -93,8 +93,23 @@ type GenericLav = { } } +/** + * Structure describes a node in minimalistic maner to build a full list of all + * nodes out of it. All node fields mentioned here are immutable throughout of + * a node life. + */ +type NodeTag = { + // A unique node identifier, there is no more than one item in the list with + // the same nid. + nid: Nid + + // The rest of the fields are added here for quick sorting purposes. + + // Node creation date, exact copy of the `created_at` fild of the `TNode`. + created_at: unixtime.Type +} type AllNidsYek = GenericYek<'all-nids', undefined> -type AllNidsLav = GenericLav<'all-nids', Nid[]> +type AllNidsLav = GenericLav<'all-nids', NodeTag[]> type NidToNodeYek = GenericYek<'nid->node', Nid> type NidToNodeLav = GenericLav< @@ -438,7 +453,12 @@ async function createNode( let records: YekLav[] = [ await store.prepareAppend( { yek: { kind: 'all-nids', key: undefined } }, - { lav: { kind: 'all-nids', value: [node.nid] } } + { + lav: { + kind: 'all-nids', + value: [{ nid: node.nid, created_at: node.created_at }], + }, + } ), { yek: { yek: { kind: 'nid->node', key: node.nid } }, @@ -714,27 +734,26 @@ async function bulkDeleteNodes( class Iterator implements INodeIterator { private store: YekLavStore - private nids: Promise + private nids: Nid[] private index: number - constructor(store: YekLavStore) { + constructor(store: YekLavStore, nids: Nid[]) { this.store = store this.index = 0 + this.nids = nids + } - const yek: AllNidsYek = { - yek: { kind: 'all-nids', key: undefined }, - } - this.nids = store - .get(yek) - .then((lav: AllNidsLav | undefined) => lav?.lav.value ?? []) + static async create(store: YekLavStore): Promise { + const nids = await getAllNids(store, {}) + return new Iterator(store, nids) } async next(): Promise { - const nids = await this.nids + const nids = this.nids if (this.index >= nids.length) { return null } - const nid: Nid = nids[nids.length - this.index - 1] + const nid: Nid = nids[0] const yek: NidToNodeYek = { yek: { kind: 'nid->node', key: nid } } const lav: NidToNodeLav | undefined = await this.store.get(yek) if (lav == null) { @@ -748,7 +767,7 @@ class Iterator implements INodeIterator { } abort(): void { this.index = 0 - this.nids = Promise.resolve([]) + this.nids = [] } } @@ -912,8 +931,11 @@ export async function getAllNids( if (lav == null) { return [] } - const value: Nid[] = lav.lav.value - return value.reverse() + const tags = lav?.lav.value ?? [] + // Sort tags by creation date, latest first + tags.sort((a, b) => b.created_at - a.created_at) + const nids = tags.map((t) => t.nid) + return nids } export function makeBrowserExtStorageApi( @@ -938,7 +960,7 @@ export function makeBrowserExtStorageApi( getAllNids: (args: NodeGetAllNidsArgs) => getAllNids(store, args), update: (args: NodeUpdateArgs) => updateNode(store, args), create: (args: NodeCreateArgs) => createNode(store, args, account), - iterate: () => new Iterator(store), + iterate: () => Iterator.create(store), delete: (args: NodeDeleteArgs) => deleteNode(store, args), bulkDelete: (args: NodeBulkDeleteArgs) => bulkDeleteNodes(store, args), batch: { diff --git a/elementary/src/grid/SearchGrid.tsx b/elementary/src/grid/SearchGrid.tsx index 1b08524a..7912aee2 100644 --- a/elementary/src/grid/SearchGrid.tsx +++ b/elementary/src/grid/SearchGrid.tsx @@ -1,6 +1,7 @@ /** @jsxImportSource @emotion/react */ import React, { useEffect, useRef, useState } from 'react' +import { useAsyncEffect } from 'use-async-effect' import styled from '@emotion/styled' @@ -73,9 +74,9 @@ export const SearchGrid = ({ iter: INodeIterator beagle: Beagle } | null>(null) - useEffect(() => { + useAsyncEffect(async () => { setUpSearch({ - iter: storage.node.iterate(), + iter: await storage.node.iterate(), beagle: Beagle.fromString(q || undefined), }) }, [q]) diff --git a/smuggler-api/src/api_datacenter.ts b/smuggler-api/src/api_datacenter.ts index 4c5c6af3..f1374c78 100644 --- a/smuggler-api/src/api_datacenter.ts +++ b/smuggler-api/src/api_datacenter.ts @@ -805,7 +805,7 @@ export function makeDatacenterStorageApi(): StorageApi { }, update: updateNode, create: createNode, - iterate: () => _getNodesSliceIter({}), + iterate: async () => _getNodesSliceIter({}), delete: deleteNode, bulkDelete: bulkDeleteNodes, batch: { diff --git a/smuggler-api/src/storage_api.ts b/smuggler-api/src/storage_api.ts index 2a49da15..854897b9 100644 --- a/smuggler-api/src/storage_api.ts +++ b/smuggler-api/src/storage_api.ts @@ -126,7 +126,7 @@ export type StorageApi = { args: NodeCreateArgs, signal?: AbortSignal ) => Promise - iterate: () => INodeIterator + iterate: () => Promise delete: (args: NodeDeleteArgs, signal?: AbortSignal) => Promise bulkDelete: ( args: NodeBulkDeleteArgs, diff --git a/smuggler-api/src/storage_api_msg_proxy.ts b/smuggler-api/src/storage_api_msg_proxy.ts index 568e3dc3..f5031d79 100644 --- a/smuggler-api/src/storage_api_msg_proxy.ts +++ b/smuggler-api/src/storage_api_msg_proxy.ts @@ -122,16 +122,23 @@ export type ForwardToRealImpl = ( ) => Promise class MsgProxyNodeIterator implements INodeIterator { - private nids: Promise + private nids: Nid[] private index: number private forward: ( payload: StorageApiMsgPayload ) => Promise - constructor(forward: ForwardToRealImpl) { + constructor(forward: ForwardToRealImpl, nids: Nid[]) { this.forward = forward + this.nids = nids + this.index = 0 + } + + static async create( + forward: ForwardToRealImpl + ): Promise { const apiName = 'node.getAllNids' - this.nids = this.forward({ apiName, args: {} }).then( + const nids = await forward({ apiName, args: {} }).then( (value: StorageApiMsgReturnValue) => { if (apiName !== value.apiName) { throw mismatchError(apiName, value.apiName) @@ -140,11 +147,11 @@ class MsgProxyNodeIterator implements INodeIterator { return ret } ) - this.index = 0 + return new MsgProxyNodeIterator(forward, nids) } async next(): Promise { - const nids = await this.nids + const nids = this.nids if (this.index >= nids.length) { return null } @@ -162,7 +169,7 @@ class MsgProxyNodeIterator implements INodeIterator { } abort(): void { this.index = 0 - this.nids = Promise.resolve([]) + this.nids = [] } } @@ -204,7 +211,7 @@ export function makeMsgProxyStorageApi(forward: ForwardToRealImpl): StorageApi { const ret: NewNodeResponse = resp.ret return ret }, - iterate: () => new MsgProxyNodeIterator(forward), + iterate: () => MsgProxyNodeIterator.create(forward), delete: async (args: NodeDeleteArgs) => { const apiName = 'node.delete' const resp = await forward({ apiName, args }) diff --git a/truthsayer/src/export/DownloadAsFile.tsx b/truthsayer/src/export/DownloadAsFile.tsx index f014882f..b6f51b5a 100644 --- a/truthsayer/src/export/DownloadAsFile.tsx +++ b/truthsayer/src/export/DownloadAsFile.tsx @@ -46,7 +46,7 @@ export function DownloadAsFile({ className }: { className?: string }) { const [processingStatus, setProcessingStatus] = useState(false) const saveAsPlainText = useCallback(async () => { setProcessingStatus(true) - const iter = ctx.storage.node.iterate() + const iter = await ctx.storage.node.iterate() const chunks: string[] = [] while (true) { const node = await iter.next() @@ -105,7 +105,7 @@ export function DownloadAsFile({ className }: { className?: string }) { }, [ctx.account, ctx.storage.node]) const saveAsJson = useCallback(async () => { setProcessingStatus(true) - const iter = ctx.storage.node.iterate() + const iter = await ctx.storage.node.iterate() const chunks: Record = {} while (true) { const node = await iter.next()