@amag-ch/sap_cap_common_objectstore
Version:
NodeJS library to communicate with an objectstore
132 lines (104 loc) • 3.75 kB
JavaScript
const path = require('node:path')
const fs = require('node:fs/promises')
const { createReadStream } = require('node:fs')
const Client = require('./Client')
const { ObjectStoreError, ObjectNotFound, ObjectStoreCommunicationError } = require('../errors')
module.exports = class LocalClient extends Client {
#folderName
constructor(credentials={}) {
super(credentials)
const { folder = '~/.cds-objectstore' } = credentials
this.#folderName = path.resolve(folder.replace(/^~/, () => require('os').userInfo().homedir))
}
/**
* Initialise.
*
* @returns {Promise<void>}
*/
init = async () => {
await fs.mkdir(this.#folderName, { recursive: true })
return super.init()
}
/**
* List all files in store.
*
* @returns {Promise<[{filename: string, modifiedAt: Date, contentLength: integer}]>}
*/
list = async () => {
const entries = await fs.readdir(this.#folderName, { withFileTypes: true })
const result = await Promise.all(entries.filter(entry => entry.isFile()).map(({ name: filename }) =>
fs.stat(this.#getAbsoluteName(filename)).then(stats => ({
filename,
modifiedAt: stats.mtime,
contentLength: stats.size
}))
))
return result
}
/**
* Read metadata from file.
*
* @param {string} filename
* @returns {Promise<{modifiedAt: Date, contentLength: integer}>}
*/
head = async (filename) => {
if (!filename) throw new Error('filename not provided')
try {
const stats = await fs.stat(this.#getAbsoluteName(filename))
if (!stats.isFile())
throw new ObjectNotFound(`File with name ${filename} not exists`)
await fs.access(this.#getAbsoluteName(filename), fs.constants.F_OK | fs.constants.R_OK )
return {
modifiedAt: stats.mtime,
contentLength: stats.size
}
} catch (err) {
if (err.code === 'ENOENT')
throw new ObjectNotFound(`File with name ${filename} not exists`)
if (err.code === 'EACCES')
throw new ObjectStoreCommunicationError(`File with name ${filename} is not readable`)
throw new ObjectStoreError(err.message, { cause: err })
}
}
/**
* Read file.
*
* @param {string} filename
* @returns {Promise<ReadableStream>}
*/
get = async (filename) => {
if (!filename) throw new Error('filename not provided')
await this.head(filename)
return createReadStream(this.#getAbsoluteName(filename))
}
/**
* Put new file to store.
*
* @param {string} filename
* @param {string|Readable|Blob|ReadableStream<any>|Uint8Array|Buffer} content
* @returns {Promise<void>}
*/
put = async (filename, content) => {
if (!filename) throw new Error('filename not provided')
if (!content) throw new Error('content not provided')
await fs.writeFile(this.#getAbsoluteName(filename), content)
}
/**
* Delete file in store.
*
* @param {string} filename
* @returns {Promise<void>}
*/
delete = async (filename) => {
if (!filename) throw new Error('filename not provided')
try {
await this.head(filename)
await fs.rm(this.#getAbsoluteName(filename))
} catch (err) {
if (err instanceof ObjectNotFound)
return
throw new ObjectStoreError(err.message, { cause: err })
}
}
#getAbsoluteName = (filename) => `${this.#folderName}/${filename}`
}