feat: Introduce internal storing mechanism

This commit is contained in:
Joachim Van Herwegen
2021-02-03 15:46:32 +01:00
parent b61d46900f
commit 59deb989ec
8 changed files with 333 additions and 0 deletions

View File

@@ -144,6 +144,12 @@ export * from './storage/conversion/RdfToQuadConverter';
export * from './storage/conversion/RepresentationConverter';
export * from './storage/conversion/TypedRepresentationConverter';
// Storage/KeyValueStorage
export * from './storage/keyvalue/JsonResourceStorage';
export * from './storage/keyvalue/KeyValueStorage';
export * from './storage/keyvalue/MemoryMapStorage';
export * from './storage/keyvalue/ResourceIdentifierStorage';
// Storage/Mapping
export * from './storage/mapping/BaseFileIdentifierMapper';
export * from './storage/mapping/ExtensionBasedMapper';

View File

@@ -0,0 +1,64 @@
import { BasicRepresentation } from '../../ldp/representation/BasicRepresentation';
import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier';
import { NotFoundHttpError } from '../../util/errors/NotFoundHttpError';
import { readableToString } from '../../util/StreamUtil';
import type { ResourceStore } from '../ResourceStore';
import type { KeyValueStorage } from './KeyValueStorage';
/**
* A {@link KeyValueStorage} for strings using a {@link ResourceStore} as backend.
*
* Values will be sent as data streams to the given identifiers,
* so how these are stored depend on the underlying store.
*
* All non-404 errors will be re-thrown.
*/
export class JsonResourceStorage implements KeyValueStorage<ResourceIdentifier, unknown> {
private readonly source: ResourceStore;
public constructor(source: ResourceStore) {
this.source = source;
}
public async get(identifier: ResourceIdentifier): Promise<unknown | undefined> {
try {
const representation = await this.source.getRepresentation(identifier, { type: { 'application/json': 1 }});
return JSON.parse(await readableToString(representation.data));
} catch (error: unknown) {
if (!NotFoundHttpError.isInstance(error)) {
throw error;
}
}
}
public async has(identifier: ResourceIdentifier): Promise<boolean> {
try {
const representation = await this.source.getRepresentation(identifier, { type: { 'application/json': 1 }});
representation.data.destroy();
return true;
} catch (error: unknown) {
if (!NotFoundHttpError.isInstance(error)) {
throw error;
}
return false;
}
}
public async set(identifier: ResourceIdentifier, value: unknown): Promise<this> {
const representation = new BasicRepresentation(JSON.stringify(value), identifier, 'application/json');
await this.source.setRepresentation(identifier, representation);
return this;
}
public async delete(identifier: ResourceIdentifier): Promise<boolean> {
try {
await this.source.deleteResource(identifier);
return true;
} catch (error: unknown) {
if (!NotFoundHttpError.isInstance(error)) {
throw error;
}
return false;
}
}
}

View File

@@ -0,0 +1,36 @@
/**
* A simple storage solution that can be used for internal values that need to be stored.
* In general storages taking objects as keys are expected to work with different instances
* of an object with the same values. Exceptions to this expectation should be clearly documented.
*/
export interface KeyValueStorage<TKey, TValue> {
/**
* Returns the value stored for the given identifier.
* `undefined` if no value is stored.
* @param identifier - Identifier to get the value for.
*/
get: (key: TKey) => Promise<TValue | undefined>;
/**
* Checks if there is a value stored for the given key.
* @param identifier - Identifier to check.
*/
has: (key: TKey) => Promise<boolean>;
/**
* Sets the value for the given key.
* @param key - Key to set/update.
* @param value - Value to store.
*
* @returns The storage.
*/
set: (key: TKey, value: TValue) => Promise<this>;
/**
* Deletes the value stored for the given key.
* @param key - Key to delete.
*
* @returns If there was a value to delete.
*/
delete: (key: TKey) => Promise<boolean>;
}

View File

@@ -0,0 +1,31 @@
import type { KeyValueStorage } from './KeyValueStorage';
/**
* A {@link KeyValueStorage} which uses a JavaScript Map for internal storage.
* Warning: Uses a Map object, which internally uses `Object.is` for key equality,
* so object keys have to be the same objects.
*/
export class MemoryMapStorage<TKey, TValue> implements KeyValueStorage<TKey, TValue> {
private readonly data: Map<TKey, TValue>;
public constructor() {
this.data = new Map<TKey, TValue>();
}
public async get(key: TKey): Promise<TValue | undefined> {
return this.data.get(key);
}
public async has(key: TKey): Promise<boolean> {
return this.data.has(key);
}
public async set(key: TKey, value: TValue): Promise<this> {
this.data.set(key, value);
return this;
}
public async delete(key: TKey): Promise<boolean> {
return this.data.delete(key);
}
}

View File

@@ -0,0 +1,33 @@
import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier';
import type { KeyValueStorage } from './KeyValueStorage';
/**
* Wrapper class that internally converts ResourceIdentifiers to strings so Storages
* that do not check value equivalence can be used with ResourceIdentifiers.
*
* Specifically: this makes it so a Storage based on a Map object can be used with ResourceIdentifiers.
*/
export class ResourceIdentifierStorage<T> implements KeyValueStorage<ResourceIdentifier, T> {
private readonly source: KeyValueStorage<string, T>;
public constructor(source: KeyValueStorage<string, T>) {
this.source = source;
}
public async get(key: ResourceIdentifier): Promise<T | undefined> {
return this.source.get(key.path);
}
public async has(key: ResourceIdentifier): Promise<boolean> {
return this.source.has(key.path);
}
public async set(key: ResourceIdentifier, value: T): Promise<this> {
await this.source.set(key.path, value);
return this;
}
public async delete(key: ResourceIdentifier): Promise<boolean> {
return this.source.delete(key.path);
}
}