From 100eb59925c417b3bfeb8551c13817ec1c220199 Mon Sep 17 00:00:00 2001 From: nazeh Date: Thu, 16 Nov 2023 13:49:43 +0300 Subject: [PATCH] feat: add client.list() --- lib/client/index.js | 61 +++++++++++++++++++++++++++++++++ npm-shrinkwrap.json | 4 +-- test/client.js | 34 ++++++++++++++++++ test/server.js | 16 +++------ types/lib/client/index.d.ts | 28 +++++++++++++++ types/lib/client/index.d.ts.map | 2 +- types/lib/server/index.d.ts | 22 ++++++++++++ types/lib/server/index.d.ts.map | 2 +- 8 files changed, 154 insertions(+), 15 deletions(-) diff --git a/lib/client/index.js b/lib/client/index.js index dd763cb..4b5a08a 100644 --- a/lib/client/index.js +++ b/lib/client/index.js @@ -192,6 +192,67 @@ class Client { return () => this._supscriptions.delete(url, callback) } + /** + * Returns a list of items in a directory. + * + * @param {string} directory + * @param {object} [opts] + * @param {boolean} [opts.skipCache] - Skip the local cache and wait for the remote relay to respond with fresh data + * + * @returns {Promise>} + */ + async list (directory, opts = {}) { + if (!directory.endsWith('/')) directory += '/' + if (!directory.startsWith('/')) directory = '/' + directory + + if (opts.skipCache) { + return this._listRelay(directory) + } + + return this._listLocal(directory) + } + + /** + * Returns a list of items in a directory from local store. + * + * @param {string} directory + * + * @returns {Promise>} + */ + async _listLocal (directory) { + const range = { + gte: PREFIXES.RECORDS + this.id + directory, + // + lte: PREFIXES.RECORDS + this.id + directory.slice(0, directory.length - 1) + '~' + } + + const list = await this._store.iterator(range).all() + + return list.map(r => r[0].split(PREFIXES.RECORDS + this.id + directory)[1]) + } + + /** + * Returns a list of items in a directory from the relay. + * + * @param {string} directory + * + * @returns {Promise>} + */ + async _listRelay (directory) { + const path = this.id + directory + const url = this._relay + '/' + path + + const response = await fetch(url) + + if (!response.ok) { + throw new Error(`Failed to list director ${directory} from relay ${this._relay}, status: ${response.status}`) + } + + const text = await response.text() + + return text.split('\n').map(p => p.split(path)[1]).filter(Boolean) + } + /** * Return a url that can be shared by others to acess a file. * diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index ea82120..f6ba84d 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "@synonymdev/web-relay", - "version": "1.0.5", + "version": "1.0.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@synonymdev/web-relay", - "version": "1.0.5", + "version": "1.0.6", "license": "MIT", "dependencies": { "@synonymdev/slashtags-url": "^1.0.0", diff --git a/test/client.js b/test/client.js index 849a962..761270e 100644 --- a/test/client.js +++ b/test/client.js @@ -314,6 +314,40 @@ test('throw errors on server 4XX response if awaitRelaySync set to true', async relay.close() }) +test('list', async (t) => { + const relay = new Relay(tmpdir()) + + const address = await relay.listen() + + const content = b4a.from(JSON.stringify({ + name: 'Alice Bob Carl' + })) + + const client = new Client({ storage: tmpdir(), relay: address }) + + for (let i = 0; i < 10; i++) { + await client.put(`/dir/subdir/foo${i}.txt`, content, { awaitRelaySync: true }) + await client.put(`/dir/wrong/foo${i}.txt`, content, { awaitRelaySync: true }) + } + + const list = await client.list('/dir/subdir', { skipCache: true }) + + t.alike(list, [ + 'foo0.txt', + 'foo1.txt', + 'foo2.txt', + 'foo3.txt', + 'foo4.txt', + 'foo5.txt', + 'foo6.txt', + 'foo7.txt', + 'foo8.txt', + 'foo9.txt' + ]) + + relay.close() +}) + function tmpdir () { return path.join(os.tmpdir(), Math.random().toString(16).slice(2)) } diff --git a/test/server.js b/test/server.js index df608b3..4389cc8 100644 --- a/test/server.js +++ b/test/server.js @@ -127,9 +127,9 @@ test('missing header', async (t) => { const response = await fetch( address + '/' + ZERO_ID + '/test.txt', { - method: 'PUT', - body: 'ffff' - }) + method: 'PUT', + body: 'ffff' + }) t.is(response.status, 400) t.is(response.statusText, `Missing or malformed header: '${HEADERS.RECORD}'`) @@ -561,12 +561,6 @@ test('query all in a directory', async (t) => { await relay._recordsDB.put('/' + GREATER_ID + `/dir/subdir/foo${i}.txt`, bytes) } - // let result = relay._recordsDB.getRange({ start: "/" + ZERO_ID + "/", end: "/" + ZERO_ID + "0" }).asArray.map(({ key, value }) => { - // return { key, value: Record.deserialize(value) } - // }) - // - // console.log(result) - const headers = { [HEADERS.CONTENT_TYPE]: 'application/octet-stream' } @@ -598,11 +592,11 @@ test('query all in a directory', async (t) => { relay.close() }) -function tmpdir() { +function tmpdir () { return path.join(os.tmpdir(), Math.random().toString(16).slice(2)) } /** @param {number} ms */ -function sleep(ms) { +function sleep (ms) { return new Promise(resolve => setTimeout(resolve, ms)) } diff --git a/types/lib/client/index.d.ts b/types/lib/client/index.d.ts index 50ae317..a8796f3 100644 --- a/types/lib/client/index.d.ts +++ b/types/lib/client/index.d.ts @@ -100,6 +100,34 @@ declare class Client { * @returns {() => void} */ subscribe(path: string, onupdate: (value: Uint8Array | null) => any): () => void; + /** + * Returns a list of items in a directory. + * + * @param {string} directory + * @param {object} [opts] + * @param {boolean} [opts.skipCache] - Skip the local cache and wait for the remote relay to respond with fresh data + * + * @returns {Promise>} + */ + list(directory: string, opts?: { + skipCache?: boolean; + }): Promise>; + /** + * Returns a list of items in a directory from local store. + * + * @param {string} directory + * + * @returns {Promise>} + */ + _listLocal(directory: string): Promise>; + /** + * Returns a list of items in a directory from the relay. + * + * @param {string} directory + * + * @returns {Promise>} + */ + _listRelay(directory: string): Promise>; /** * Return a url that can be shared by others to acess a file. * diff --git a/types/lib/client/index.d.ts.map b/types/lib/client/index.d.ts.map index 17091cf..464d106 100644 --- a/types/lib/client/index.d.ts.map +++ b/types/lib/client/index.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../lib/client/index.js"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../lib/client/index.js"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/types/lib/server/index.d.ts b/types/lib/server/index.d.ts index ead70d5..7defc67 100644 --- a/types/lib/server/index.d.ts +++ b/types/lib/server/index.d.ts @@ -1,4 +1,5 @@ /// +/// export = Relay; declare class Relay { static SERVER_SIDE_RECORDS_METADATA: string; @@ -105,6 +106,25 @@ declare class Relay { * @param {http.ServerResponse} res */ _SUBSCRIBE(req: http.IncomingMessage, res: http.ServerResponse): Promise; + /** + * @param {string} id + * @param {string} directory + * @param {object} [options] + * @param {number} [options.limit] + * @param {number} [options.offset] + * + * @returns {lmdb.RangeIterable} + */ + _searchDirectory(id: string, directory: string, options?: { + limit?: number; + offset?: number; + }): lmdb.RangeIterable; + /** + * @param {http.IncomingMessage} _req + * @param {http.ServerResponse} res + * @param {URL} url + */ + _QUERY(_req: http.IncomingMessage, res: http.ServerResponse, url: URL): Promise; /** * Health check endpoint to provide server metrics. * @@ -121,4 +141,6 @@ declare class Relay { import http = require("http"); import Record = require("../record.js"); import Prometheus = require("prom-client"); +import lmdb = require("lmdb"); +import { URL } from "url"; //# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/types/lib/server/index.d.ts.map b/types/lib/server/index.d.ts.map index 9204606..ae551c3 100644 --- a/types/lib/server/index.d.ts.map +++ b/types/lib/server/index.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../lib/server/index.js"],"names":[],"mappings":";;AA8BA;IACE,4CAAkE;IAElE;;;;;;OAMG;IACH,sBANW,MAAM;QAEW,cAAc,GAA/B,MAAM;QAEW,cAAc,GAA/B,MAAM;OAmDhB;IAhDC,8EAAyD;IAEzD,wBAAyE;IAEzE,oBAAiD;IACjD,oBAA2D;IAC3D,oBAA2D;IAM3D,sDAAsD;IACtD,YADW,OAAO,MAAM,EAAE,YAAY,CAAC,UAAU,CAAC,CAKhD;IAEF,yDAAyD;IACzD,yCADoC,MAAM,KAAK,IAAI,GACpB;IAE/B;;;;OAIG;IACH,oBAFsB,IAAI,IAEL;IACrB,kCAA0H;IAO1H,4EAIE;IAEF,oEAIE;IAeJ,2BAUC;IAED;;OAEG;IACH,gBAGC;IAED;;;;OAIG;IACH,cAFW,MAAM,gBAUhB;IAHK,mBAA4B;IAKlC;;OAEG;IACH,8EAGC;IAED;;OAEG;IACH;;MAMC;IAID;;;OAGG;IACH,iBAHW,MAAM,GACJ,QAAQ,MAAM,GAAG,IAAI,CAAC,CAKlC;IAED;;;OAGG;IACH,aAHW,KAAK,eAAe,OACpB,KAAK,cAAc,QAkC7B;IAED;;;;;OAKG;IACH,2BAHW,KAAK,eAAe,OACpB,KAAK,cAAc,QA4B7B;IAED;;;;;OAKG;IACH,eAHW,KAAK,eAAe,OACpB,KAAK,cAAc,QAK7B;IAED;;;OAGG;IACH,UAHW,KAAK,eAAe,OACpB,KAAK,cAAc,iBAoH7B;IAED;;;;;;;OAOG;IACH,+BAFW,MAAM,QAWhB;IAED;;OAEG;IACH,sCAFW,MAAM,OAehB;IAED;;;OAGG;IACH,UAHW,KAAK,eAAe,OACpB,KAAK,cAAc,iBA8B7B;IAED;;;OAGG;IACH,gBAHW,KAAK,eAAe,OACpB,KAAK,cAAc,iBAiD7B;IAED;;;;;OAKG;IACH,mBAHW,KAAK,eAAe,OACpB,KAAK,cAAc,QAgD7B;IAED;;;OAGG;IACH,cAHW,KAAK,eAAe,OACpB,KAAK,cAAc,iBAM7B;CACF"} \ No newline at end of file +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../lib/server/index.js"],"names":[],"mappings":";;;AA8BA;IACE,4CAAkE;IAElE;;;;;;OAMG;IACH,sBANW,MAAM;QAEW,cAAc,GAA/B,MAAM;QAEW,cAAc,GAA/B,MAAM;OAmDhB;IAhDC,8EAAyD;IAEzD,wBAAyE;IAEzE,oBAAiD;IACjD,oBAA2D;IAC3D,oBAA2D;IAM3D,sDAAsD;IACtD,YADW,OAAO,MAAM,EAAE,YAAY,CAAC,UAAU,CAAC,CAKhD;IAEF,yDAAyD;IACzD,yCADoC,MAAM,KAAK,IAAI,GACpB;IAE/B;;;;OAIG;IACH,oBAFsB,IAAI,IAEL;IACrB,kCAA0H;IAO1H,4EAIE;IAEF,oEAIE;IAeJ,2BAUC;IAED;;OAEG;IACH,gBAGC;IAED;;;;OAIG;IACH,cAFW,MAAM,gBAUhB;IAHK,mBAA4B;IAKlC;;OAEG;IACH,8EAGC;IAED;;OAEG;IACH;;MAMC;IAID;;;OAGG;IACH,iBAHW,MAAM,GACJ,QAAQ,MAAM,GAAG,IAAI,CAAC,CAKlC;IAED;;;OAGG;IACH,aAHW,KAAK,eAAe,OACpB,KAAK,cAAc,QAkC7B;IAED;;;;;OAKG;IACH,2BAHW,KAAK,eAAe,OACpB,KAAK,cAAc,QAgC7B;IAED;;;;;OAKG;IACH,eAHW,KAAK,eAAe,OACpB,KAAK,cAAc,QAK7B;IAED;;;OAGG;IACH,UAHW,KAAK,eAAe,OACpB,KAAK,cAAc,iBAoH7B;IAED;;;;;;;OAOG;IACH,+BAFW,MAAM,QAWhB;IAED;;OAEG;IACH,sCAFW,MAAM,OAehB;IAED;;;OAGG;IACH,UAHW,KAAK,eAAe,OACpB,KAAK,cAAc,iBA8B7B;IAED;;;OAGG;IACH,gBAHW,KAAK,eAAe,OACpB,KAAK,cAAc,iBAiD7B;IAED;;;;;;;;OAQG;IACH,qBARW,MAAM,aACN,MAAM;QAEW,KAAK,GAAtB,MAAM;QACW,MAAM,GAAvB,MAAM;gCAQhB;IAED;;;;OAIG;IACH,aAJW,KAAK,eAAe,OACpB,KAAK,cAAc,OACnB,GAAG,iBAoBb;IAED;;;;;OAKG;IACH,mBAHW,KAAK,eAAe,OACpB,KAAK,cAAc,QAgD7B;IAED;;;OAGG;IACH,cAHW,KAAK,eAAe,OACpB,KAAK,cAAc,iBAM7B;CACF"} \ No newline at end of file