diff --git a/cli.mjs b/cli.mjs index fa4d65b..4dedaff 100644 --- a/cli.mjs +++ b/cli.mjs @@ -14,5 +14,5 @@ if (args.length < 2) { } const hostName = args[2] -const getFunc = hostName === undefined ? getLocal : getRemote(hostName) +const getFunc = hostName === undefined ? getLocal({}) : getRemote({})(hostName) getFunc(node)([args[0], args[1]]) \ No newline at end of file diff --git a/forest/index.mjs b/forest/index.mjs index 9bf4d96..7b76947 100644 --- a/forest/index.mjs +++ b/forest/index.mjs @@ -55,9 +55,13 @@ const { tailToNodeId } = nodeId * @typedef { ForestNodeState[] } State */ +/** + * @typedef {(forestNodeId: ForestNodeId) => Promise} ForestGet +*/ + /** * @typedef {{ - * readonly read: (address: ForestNodeId) => Promise, + * readonly read: ForestGet, * readonly write: (buffer: Uint8Array) => Promise, * }} Provider */ diff --git a/index.mjs b/index.mjs index 8dcaaf5..d97f47c 100644 --- a/index.mjs +++ b/index.mjs @@ -1,3 +1,5 @@ +import nodeId from './cdt/node-id.mjs' +import forest from './forest/index.mjs' import getModule from './forest/index.mjs' /** @typedef {import('./cdt/main-tree.mjs').State} StateTree */ /** @@ -5,8 +7,11 @@ import getModule from './forest/index.mjs' * @typedef {import('./cdt/sub-tree.mjs').Nullable} Nullable */ /** @typedef {import('./forest/index.mjs').ForestNodeId} ForestNodeId */ +/** @typedef {import('./forest/index.mjs').ForestGet} ForestGet */ /** @typedef {import('./io/io.mjs').IO} IO */ +/** @typedef {{[index: string] : Uint8Array | undefined}} Cache */ + const { get } = getModule /** @type {(forestNodeId: ForestNodeId) => string} */ @@ -17,14 +22,28 @@ const getPath = ([forestNodeId, isRoot]) => { /** @type {(hostName: string) => (io: IO) => (forestNodeId: ForestNodeId) => Promise} */ const fetchRead = hostName => ({ fetch }) => forestNodeId => fetch(`https://${hostName}/${getPath(forestNodeId)}`) - .then(async (resp) => resp.arrayBuffer().then(buffer => new Uint8Array(buffer))) + .then(async (resp) => resp.arrayBuffer().then(buffer => new Uint8Array(buffer))) + +/** @type {(mem: Cache) => (forestGet: ForestGet) => ForestGet} */ +const cache = mem => forestGet => { + return async (nodeId) => { + const nodeIdString = `${nodeId[0]}${nodeId[1]}` + let buffer = mem[nodeIdString] + if (buffer !== undefined) { + return buffer + } + buffer = await forestGet(nodeId) + mem[nodeIdString] = buffer + return buffer + } +} -/** @type {(io: IO) => (root: [string, string]) => Promise} */ -const getLocal = io => async ([root, file]) => { +/** @type {(mem: Cache) => (io: IO) => (root: [string, string]) => Promise} */ +const getLocal = mem => io => async ([root, file]) => { const tempFile = `_temp_${root}` await io.write(tempFile, new Uint8Array()) - /** @type {(forestNodeId: ForestNodeId) => Promise} */ - const read = forestNodeId => io.read(getPath(forestNodeId)) + /** @type {ForestGet} */ + const read = cache(mem)(forestNodeId => io.read(getPath(forestNodeId))) /** @type {(buffer: Uint8Array) => Promise} */ const write = buffer => io.append(tempFile, buffer) const error = await get({ read, write })(root) @@ -36,12 +55,12 @@ const getLocal = io => async ([root, file]) => { return 0 } -/** @type {(host: string) => (io: IO) => (root: [string, string]) => Promise} */ -const getRemote = host => io => async ([root, file]) => { +/** @type {(mem: Cache) => (host: string) => (io: IO) => (root: [string, string]) => Promise} */ +const getRemote = mem => host => io => async ([root, file]) => { const tempFile = `_temp_${root}` await io.write(tempFile, new Uint8Array()) - /** @type {(forestNodeId: ForestNodeId) => Promise} */ - const read = fetchRead(host)(io) + /** @type {ForestGet} */ + const read = cache(mem)(fetchRead(host)(io)) /** @type {(buffer: Uint8Array) => Promise} */ const write = buffer => io.append(tempFile, buffer) const error = await get({ read, write })(root) diff --git a/io/virtual.mjs b/io/virtual.mjs new file mode 100644 index 0000000..adaa467 --- /dev/null +++ b/io/virtual.mjs @@ -0,0 +1,56 @@ +/** @typedef {import('./io.mjs').IO} IO */ + +const notImplemented = () => { throw 'not implemented' } + +/** + * @typedef {{[index: string]: Uint8Array}} FileSystem + */ + +/** @type {(fs: FileSystem) => (path: string) => Promise} */ +const read = fs => async (path) => { + const buffer = fs[path] + if (buffer === undefined) { + throw 'file not found' + } + return buffer +} + +/** @type {(fs: FileSystem) => (path: string, buffer: Uint8Array) => Promise} */ +const append = fs => async (path, buffer) => { + const cur = fs[path] + if (buffer === undefined) { + throw 'file not found' + } + fs[path] = new Uint8Array([...cur, ...buffer]) +} + +/** @type {(fs: FileSystem) => (path: string, buffer: Uint8Array) => Promise} */ +const write = fs => async (path, buffer) => { + fs[path] = buffer +} + +/** @type {(fs: FileSystem) => (oldPath: string, newPath: string) => Promise} */ +const rename = fs => async (oldPath, newPath) => { + const buffer = fs[oldPath] + if (buffer === undefined) { + throw 'file not found' + } + delete fs[oldPath] + fs[newPath] = buffer +} + +/** @type {(fs: FileSystem) => IO} */ +const virtual = fs => { + return { + read: read(fs), + append: append(fs), + write: write(fs), + rename: rename(fs), + fetch: notImplemented, + document: undefined + } +} + +export default { + virtual +} \ No newline at end of file diff --git a/test.mjs b/test.mjs index 32ffba0..3f7d640 100644 --- a/test.mjs +++ b/test.mjs @@ -7,9 +7,12 @@ import index from './index.mjs' import ioNode from './io/node.mjs' import fs from 'node:fs' import fsPromises from 'node:fs/promises' +import ioVirtual from './io/virtual.mjs' /** @typedef {import('./cdt/sub-tree.mjs').State} StateSubTree */ /** @typedef {import('./cdt/main-tree.mjs').State} StateTree */ /** @typedef {import('./io/io.mjs').IO} IO */ +/** @typedef {import('./io/virtual.mjs').FileSystem} FileSystem */ +/** @typedef {import('./index.mjs').Cache} Cache */ const { toBase32Hash, getParityBit } = base32 const { compress } = sha224 const { merge, byteToNodeId, len } = nodeId @@ -17,6 +20,7 @@ const { highestOne256, height, push: pushSubTree } = subTree const { push: pushTree, end: endTree } = mainTree const { getLocal, getRemote } = index const { node, nodeSync } = ioNode +const { virtual } = ioVirtual console.log(`test start`) @@ -201,6 +205,30 @@ console.log(`test start`) } } +const virtualFsTest = async () => { + /** @type {FileSystem} */ + const fs = {} + const io = virtual(fs) + await io.write('test', new Uint8Array([0, 1, 2])) + let buffer = await io.read('test') + if (buffer.toString() !== '0,1,2') { throw buffer } + + await io.write('test', new Uint8Array([3, 4, 5])) + buffer = await io.read('test') + if (buffer.toString() !== '3,4,5') { throw buffer } + + await io.append('test', new Uint8Array([6, 7, 8])) + buffer = await io.read('test') + if (buffer.toString() !== '3,4,5,6,7,8') { throw buffer } + + await io.rename('test', 'test-new') + //buffer = await io.read('test') //catch error + buffer = await io.read('test-new') + if (buffer.toString() !== '3,4,5,6,7,8') { throw buffer } +} + +virtualFsTest() + { const data = fs.readFileSync(`examples/small.txt`) /** @type {StateTree} */ @@ -271,12 +299,14 @@ const runTestsGet = io => async (getFunc) => { } const mainTestAsync = async () => { + /** @type {Cache} */ + const mem = {} console.log('sync provider') - await runTestsGet(nodeSync)(getLocal) + await runTestsGet(nodeSync)(getLocal(mem)) console.log('async provider') - await runTestsGet(node)(getLocal) + await runTestsGet(node)(getLocal(mem)) console.log('fetch provider') - await runTestsGet(node)(getRemote('410f5a49.blockset-js-test.pages.dev')) + await runTestsGet(node)(getRemote({})('410f5a49.blockset-js-test.pages.dev')) } mainTestAsync() \ No newline at end of file