@solid/community-server
Version:
Community Solid Server: an open and modular implementation of the Solid specifications
135 lines • 5.77 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.JsonResourceStorage = void 0;
const global_logger_factory_1 = require("global-logger-factory");
const BasicRepresentation_1 = require("../../http/representation/BasicRepresentation");
const ErrorUtil_1 = require("../../util/errors/ErrorUtil");
const NotFoundHttpError_1 = require("../../util/errors/NotFoundHttpError");
const PathUtil_1 = require("../../util/PathUtil");
const StreamUtil_1 = require("../../util/StreamUtil");
const Vocabularies_1 = require("../../util/Vocabularies");
/**
* A {@link KeyValueStorage} for JSON-like objects using a {@link ResourceStore} as backend.
*
* Creates a base URL by joining the input base URL with the container string.
* The storage assumes it has ownership over all entries in the target container
* so no other classes should access resources there to prevent issues.
*
* Assumes the input keys can be safely used to generate identifiers,
* which will be appended to the stored base URL.
*
* All non-404 errors will be re-thrown.
*/
class JsonResourceStorage {
logger = (0, global_logger_factory_1.getLoggerFor)(this);
source;
container;
constructor(source, baseUrl, container) {
this.source = source;
this.container = (0, PathUtil_1.ensureTrailingSlash)((0, PathUtil_1.joinUrl)(baseUrl, container));
}
async get(key) {
try {
const identifier = this.keyToIdentifier(key);
// eslint-disable-next-line ts/naming-convention
const representation = await this.source.getRepresentation(identifier, { type: { 'application/json': 1 } });
// eslint-disable-next-line ts/no-unsafe-return
return JSON.parse(await (0, StreamUtil_1.readableToString)(representation.data));
}
catch (error) {
if (!NotFoundHttpError_1.NotFoundHttpError.isInstance(error)) {
throw error;
}
}
}
async has(key) {
const identifier = this.keyToIdentifier(key);
return this.source.hasResource(identifier);
}
async set(key, value) {
const identifier = this.keyToIdentifier(key);
const representation = new BasicRepresentation_1.BasicRepresentation(JSON.stringify(value), identifier, 'application/json');
await this.source.setRepresentation(identifier, representation);
return this;
}
async delete(key) {
try {
const identifier = this.keyToIdentifier(key);
await this.source.deleteResource(identifier);
return true;
}
catch (error) {
if (!NotFoundHttpError_1.NotFoundHttpError.isInstance(error)) {
throw error;
}
return false;
}
}
async *entries() {
yield* this.getResourceEntries({ path: this.container });
}
/**
* Recursively iterates through the container to find all documents.
*/
async *getResourceEntries(identifier) {
const representation = await this.safelyGetResource(identifier);
if (representation) {
if ((0, PathUtil_1.isContainerIdentifier)(identifier)) {
// Only need the metadata
representation.data.destroy();
const members = representation.metadata.getAll(Vocabularies_1.LDP.terms.contains).map((term) => term.value);
for (const path of members) {
yield* this.getResourceEntries({ path });
}
}
else {
try {
const json = JSON.parse(await (0, StreamUtil_1.readableToString)(representation.data));
yield [this.identifierToKey(identifier), json];
}
catch (error) {
this.logger.error(`Unable to parse ${identifier.path}. You should probably delete this resource manually. Error: ${(0, ErrorUtil_1.createErrorMessage)(error)}`);
}
}
}
}
/**
* Returns the representation for the given identifier.
* Returns undefined if a 404 error is thrown.
* Re-throws the error in all other cases.
*/
async safelyGetResource(identifier) {
let representation;
try {
// eslint-disable-next-line ts/naming-convention
const preferences = (0, PathUtil_1.isContainerIdentifier)(identifier) ? {} : { type: { 'application/json': 1 } };
representation = await this.source.getRepresentation(identifier, preferences);
}
catch (error) {
// Can happen if resource is deleted by this point.
// When using this for internal data this can specifically happen quite often with locks.
if (!NotFoundHttpError_1.NotFoundHttpError.isInstance(error)) {
throw error;
}
}
return representation;
}
/**
* Converts a key into an identifier for internal storage.
*/
keyToIdentifier(key) {
return { path: (0, PathUtil_1.joinUrl)(this.container, key) };
}
/**
* Converts an internal identifier to an external key.
*/
identifierToKey(identifier) {
// Due to the usage of `joinUrl` we don't know for sure if there was a preceding slash,
// so we always remove leading slashes one for consistency.
// In practice this only has an impact on the `entries` call
// and only if class calling this depends on a leading slash still being there.
return (0, PathUtil_1.trimLeadingSlashes)(identifier.path.slice(this.container.length));
}
}
exports.JsonResourceStorage = JsonResourceStorage;
//# sourceMappingURL=JsonResourceStorage.js.map