mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
170 lines
5.8 KiB
TypeScript
170 lines
5.8 KiB
TypeScript
import { PassThrough } from 'stream';
|
|
import arrayifyStream from 'arrayify-stream';
|
|
import streamifyArray from 'streamify-array';
|
|
import type { Representation } from '../ldp/representation/Representation';
|
|
import { RepresentationMetadata } from '../ldp/representation/RepresentationMetadata';
|
|
import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
|
|
import { TEXT_TURTLE } from '../util/ContentTypes';
|
|
import { NotFoundHttpError } from '../util/errors/NotFoundHttpError';
|
|
import { CONTENT_TYPE } from '../util/UriConstants';
|
|
import { ensureTrailingSlash } from '../util/Util';
|
|
import type { ResourceStore } from './ResourceStore';
|
|
|
|
/**
|
|
* Resource store storing its data in an in-memory map.
|
|
* Current Solid functionality support is quite basic: containers are not really supported for example.
|
|
*/
|
|
export class InMemoryResourceStore implements ResourceStore {
|
|
private readonly store: { [id: string]: Representation };
|
|
private readonly base: string;
|
|
private index = 0;
|
|
|
|
/**
|
|
* @param base - Base that will be stripped of all incoming URIs
|
|
* and added to all outgoing ones to find the relative path.
|
|
*/
|
|
public constructor(base: string) {
|
|
this.base = ensureTrailingSlash(base);
|
|
|
|
const metadata = new RepresentationMetadata({ [CONTENT_TYPE]: TEXT_TURTLE });
|
|
this.store = {
|
|
// Default root entry (what you get when the identifier is equal to the base)
|
|
'': {
|
|
binary: true,
|
|
data: streamifyArray([]),
|
|
metadata,
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Stores the incoming data under a new URL corresponding to `container.path + number`.
|
|
* Slash added when needed.
|
|
* @param container - The identifier to store the new data under.
|
|
* @param representation - Data to store.
|
|
*
|
|
* @returns The newly generated identifier.
|
|
*/
|
|
public async addResource(container: ResourceIdentifier, representation: Representation): Promise<ResourceIdentifier> {
|
|
const containerPath = this.parseIdentifier(container);
|
|
this.checkPath(containerPath);
|
|
const newID = { path: `${ensureTrailingSlash(container.path)}${this.index}` };
|
|
const newPath = this.parseIdentifier(newID);
|
|
this.index += 1;
|
|
this.store[newPath] = await this.copyRepresentation(representation);
|
|
return newID;
|
|
}
|
|
|
|
/**
|
|
* Deletes the given resource.
|
|
* @param identifier - Identifier of resource to delete.
|
|
*/
|
|
public async deleteResource(identifier: ResourceIdentifier): Promise<void> {
|
|
const path = this.parseIdentifier(identifier);
|
|
this.checkPath(path);
|
|
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
delete this.store[path];
|
|
}
|
|
|
|
/**
|
|
* Returns the stored representation for the given identifier.
|
|
* Preferences will be ignored, data will be returned as it was received.
|
|
*
|
|
* @param identifier - Identifier to retrieve.
|
|
*
|
|
* @returns The corresponding Representation.
|
|
*/
|
|
public async getRepresentation(identifier: ResourceIdentifier): Promise<Representation> {
|
|
const path = this.parseIdentifier(identifier);
|
|
this.checkPath(path);
|
|
return this.generateRepresentation(path);
|
|
}
|
|
|
|
/**
|
|
* @throws Not supported.
|
|
*/
|
|
public async modifyResource(): Promise<void> {
|
|
throw new Error('Not supported.');
|
|
}
|
|
|
|
/**
|
|
* Puts the given data in the given location.
|
|
* @param identifier - Identifier to replace.
|
|
* @param representation - New Representation.
|
|
*/
|
|
public async setRepresentation(identifier: ResourceIdentifier, representation: Representation): Promise<void> {
|
|
const path = this.parseIdentifier(identifier);
|
|
this.store[path] = await this.copyRepresentation(representation);
|
|
}
|
|
|
|
/**
|
|
* Strips the base from the identifier and checks if it is valid.
|
|
* @param identifier - Incoming identifier.
|
|
*
|
|
* @throws {@link NotFoundHttpError}
|
|
* If the identifier doesn't start with the base ID.
|
|
*
|
|
* @returns A string representing the relative path.
|
|
*/
|
|
private parseIdentifier(identifier: ResourceIdentifier): string {
|
|
const path = identifier.path.slice(this.base.length);
|
|
if (!identifier.path.startsWith(this.base)) {
|
|
throw new NotFoundHttpError();
|
|
}
|
|
return path;
|
|
}
|
|
|
|
/**
|
|
* Checks if the relative path is in the store.
|
|
* @param path - Incoming identifier.
|
|
*
|
|
* @throws {@link NotFoundHttpError}
|
|
* If the path is not in the store.
|
|
*/
|
|
private checkPath(path: string): void {
|
|
if (!this.store[path]) {
|
|
throw new NotFoundHttpError();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Copies the Representation by draining the original data stream and creating a new one.
|
|
*
|
|
* @param source - Incoming Representation.
|
|
*/
|
|
private async copyRepresentation(source: Representation): Promise<Representation> {
|
|
return {
|
|
binary: source.binary,
|
|
data: streamifyArray(await arrayifyStream(source.data)),
|
|
metadata: source.metadata,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Generates a Representation that is identical to the one stored,
|
|
* but makes sure to duplicate the data stream so it stays readable for later calls.
|
|
*
|
|
* @param path - Path in store of Representation.
|
|
*
|
|
* @returns The resulting Representation.
|
|
*/
|
|
private async generateRepresentation(path: string): Promise<Representation> {
|
|
// Note: when converting to a complete ResourceStore and using readable-stream
|
|
// object mode should be set correctly here (now fixed due to Node 10)
|
|
const source = this.store[path];
|
|
const objectMode = { writableObjectMode: true, readableObjectMode: true };
|
|
const streamOutput = new PassThrough(objectMode);
|
|
const streamInternal = new PassThrough({ ...objectMode, highWaterMark: Number.MAX_SAFE_INTEGER });
|
|
source.data.pipe(streamOutput);
|
|
source.data.pipe(streamInternal);
|
|
|
|
source.data = streamInternal;
|
|
|
|
return {
|
|
binary: source.binary,
|
|
data: streamOutput,
|
|
metadata: source.metadata,
|
|
};
|
|
}
|
|
}
|