feat: Rework ResourceStore to return extra info

* feat: change return types in ResourceStore.ts

* feat: change return types in BaseResourceStore.ts

* feat: change return types in LockingResourceStore.ts

* feat: change return types in RoutingResourceStore.ts

* feat: change return types in MonitoringStore.ts

* feat: change return types in PassthroughStore.ts

* feat: change return types in ReadOnlyStore.ts

* feat: change return types in PatchHandler.ts

* feat: change return types in PatchingStore.ts

* feat: change return types in RepresentationPatchHandler.ts

* feat: create createResourceIdentifier() function for convenience

* feat: adapt PostOperationHandler.ts to new typing

* feat: change return types in RepresentationConvertingStore.ts

* feat: adapt DataAccessorBasedStore.ts implementation to new typings

* feat: adapt UnsecureWebSocketsProtocol.ts to new typing

* chore: add temporary comments

* fix: return correct Location header on POST request with slug

* fix: npm run lint command needs more packages

* fix: linting errors

* chore: revert ed9952b

* test: adapt PostOperationHandler tests

* test: adapt UnsecureWebSocketsProtocol tests

* test: adapt DataAccessorBasedStore tests

* fix: linting errors

* feat: emit specific created, deleted, updated events in MonitoringStore

* test: adapt RepresentationPatchHandler tests

* fix: revert UnsecureWebSocketsProtocol changes

* feat: emit extra parameter on changed

* test: adapt MonitoringStore tests

* fix: linting errors

* test: add test to MonitorStore.test for coverage

* fix: linting error

* chore: update doc in ResourceStore.ts

* test: improve MonitoringStore tests

* chore: update RELEASE_NOTES.md

* chore: add extra info about the MonitoringStore to documentation/resource-store.md

* chore: Update RELEASE_NOTES.md

Co-authored-by: Anton Wiklund <ixuz07@gmail.com>

* chore: Update documentation/resource-store.md

Co-authored-by: Anton Wiklund <ixuz07@gmail.com>

* chore: very small changes

* chore: simplify metadata creation

* fix: DataAccessorBasedStore improvement and bugfix

* chore: improve resource-store.md

* chore: adapt MonitoringStore event names, update docs and apply code suggestion

* chore: use ResourceStoreResponse type

* fix: typo

* chore: rename ResourceStoreResponse type to ChangeMap

* chore: adapt .gitignore to name change

Co-authored-by: Anton Wiklund <ixuz07@gmail.com>
This commit is contained in:
Arthur Joppart
2022-07-06 14:40:28 +02:00
committed by GitHub
parent 1c65b06392
commit e0954cf2a7
22 changed files with 468 additions and 217 deletions

View File

@@ -2,8 +2,10 @@ import { getLoggerFor } from '../../logging/LogUtil';
import type { ResourceStore } from '../../storage/ResourceStore';
import { BadRequestHttpError } from '../../util/errors/BadRequestHttpError';
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
import { AS, SOLID_AS } from '../../util/Vocabularies';
import { CreatedResponseDescription } from '../output/response/CreatedResponseDescription';
import type { ResponseDescription } from '../output/response/ResponseDescription';
import { createResourceIdentifier } from '../representation/ResourceIdentifier';
import type { OperationHandlerInput } from './OperationHandler';
import { OperationHandler } from './OperationHandler';
@@ -35,7 +37,10 @@ export class PostOperationHandler extends OperationHandler {
this.logger.warn('POST requests require the Content-Type header to be set');
throw new BadRequestHttpError('POST requests require the Content-Type header to be set');
}
const identifier = await this.store.addResource(operation.target, operation.body, operation.conditions);
return new CreatedResponseDescription(identifier);
const result = await this.store.addResource(operation.target, operation.body, operation.conditions);
const createdIdentifier = Object.entries(result).find(
([ , value ]): boolean => value.get(SOLID_AS.terms.Activity)?.value === AS.Create,
)![0];
return new CreatedResponseDescription(createResourceIdentifier(createdIdentifier));
}
}

View File

@@ -9,8 +9,15 @@ export interface ResourceIdentifier {
}
/**
* Determines whether the object is a `ResourceIdentifier`.
* Determines whether the object is a {@link ResourceIdentifier}.
*/
export function isResourceIdentifier(object: any): object is ResourceIdentifier {
return object && (typeof object.path === 'string');
}
/**
* Factory function creating a {@link ResourceIdentifier} for convenience.
*/
export function createResourceIdentifier(resourcePath: string): ResourceIdentifier {
return { path: resourcePath };
}

View File

@@ -4,7 +4,7 @@ import type { RepresentationPreferences } from '../http/representation/Represent
import type { ResourceIdentifier } from '../http/representation/ResourceIdentifier';
import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError';
import type { Conditions } from './Conditions';
import type { ResourceStore } from './ResourceStore';
import type { ResourceStore, ChangeMap } from './ResourceStore';
/**
* Base implementation of ResourceStore for implementers of custom stores.
@@ -21,22 +21,22 @@ export class BaseResourceStore implements ResourceStore {
}
public async setRepresentation(identifier: ResourceIdentifier, representation: Representation,
conditions?: Conditions): Promise<ResourceIdentifier[]> {
conditions?: Conditions): Promise<ChangeMap> {
throw new NotImplementedHttpError();
}
public async addResource(container: ResourceIdentifier, representation: Representation,
conditions?: Conditions): Promise<ResourceIdentifier> {
conditions?: Conditions): Promise<ChangeMap> {
throw new NotImplementedHttpError();
}
public async deleteResource(identifier: ResourceIdentifier,
conditions?: Conditions): Promise<ResourceIdentifier[]> {
conditions?: Conditions): Promise<ChangeMap> {
throw new NotImplementedHttpError();
}
public async modifyResource(identifier: ResourceIdentifier, patch: Patch,
conditions?: Conditions): Promise<ResourceIdentifier[]> {
conditions?: Conditions): Promise<ChangeMap> {
throw new NotImplementedHttpError();
}
}

View File

@@ -6,7 +6,7 @@ import type { AuxiliaryStrategy } from '../http/auxiliary/AuxiliaryStrategy';
import { BasicRepresentation } from '../http/representation/BasicRepresentation';
import type { Patch } from '../http/representation/Patch';
import type { Representation } from '../http/representation/Representation';
import type { RepresentationMetadata } from '../http/representation/RepresentationMetadata';
import { RepresentationMetadata } from '../http/representation/RepresentationMetadata';
import type { ResourceIdentifier } from '../http/representation/ResourceIdentifier';
import { getLoggerFor } from '../logging/LogUtil';
import { INTERNAL_QUADS } from '../util/ContentTypes';
@@ -39,10 +39,12 @@ import {
SOLID_META,
PREFERRED_PREFIX_TERM,
CONTENT_TYPE_TERM,
SOLID_AS,
AS,
} from '../util/Vocabularies';
import type { DataAccessor } from './accessors/DataAccessor';
import type { Conditions } from './Conditions';
import type { ResourceStore } from './ResourceStore';
import type { ResourceStore, ChangeMap } from './ResourceStore';
/**
* ResourceStore which uses a DataAccessor for backend access.
@@ -138,7 +140,7 @@ export class DataAccessorBasedStore implements ResourceStore {
}
public async addResource(container: ResourceIdentifier, representation: Representation, conditions?: Conditions):
Promise<ResourceIdentifier> {
Promise<ChangeMap> {
this.validateIdentifier(container);
const parentMetadata = await this.getSafeNormalizedMetadata(container);
@@ -174,13 +176,11 @@ export class DataAccessorBasedStore implements ResourceStore {
}
// Write the data. New containers should never be made for a POST request.
await this.writeData(newID, representation, isContainer, false, false);
return newID;
return this.writeData(newID, representation, isContainer, false, false);
}
public async setRepresentation(identifier: ResourceIdentifier, representation: Representation,
conditions?: Conditions): Promise<ResourceIdentifier[]> {
conditions?: Conditions): Promise<ChangeMap> {
this.validateIdentifier(identifier);
// Check if the resource already exists
@@ -216,7 +216,7 @@ export class DataAccessorBasedStore implements ResourceStore {
}
public async modifyResource(identifier: ResourceIdentifier, patch: Patch,
conditions?: Conditions): Promise<ResourceIdentifier[]> {
conditions?: Conditions): Promise<never> {
if (conditions) {
let metadata: RepresentationMetadata | undefined;
try {
@@ -233,7 +233,7 @@ export class DataAccessorBasedStore implements ResourceStore {
throw new NotImplementedHttpError('Patches are not supported by the default store.');
}
public async deleteResource(identifier: ResourceIdentifier, conditions?: Conditions): Promise<ResourceIdentifier[]> {
public async deleteResource(identifier: ResourceIdentifier, conditions?: Conditions): Promise<ChangeMap> {
this.validateIdentifier(identifier);
const metadata = await this.accessor.getMetadata(identifier);
// Solid, §5.4: "When a DELETE request targets storages root container or its associated ACL resource,
@@ -266,22 +266,25 @@ export class DataAccessorBasedStore implements ResourceStore {
// Solid, §5.4: "When a contained resource is deleted,
// the server MUST also delete the associated auxiliary resources"
// https://solid.github.io/specification/protocol#deleting-resources
const deleted = [ identifier ];
const changes: ChangeMap = {};
if (!this.auxiliaryStrategy.isAuxiliaryIdentifier(identifier)) {
const auxiliaries = this.auxiliaryStrategy.getAuxiliaryIdentifiers(identifier);
deleted.push(...await this.safelyDeleteAuxiliaryResources(auxiliaries));
for (const deletedId of await this.safelyDeleteAuxiliaryResources(auxiliaries)) {
changes[deletedId.path] = this.createActivityMetadata(deletedId, AS.Delete);
}
}
if (!this.identifierStrategy.isRootContainer(identifier)) {
const container = this.identifierStrategy.getParentContainer(identifier);
deleted.push(container);
changes[container.path] = this.createActivityMetadata(container, AS.Update);
// Update modified date of parent
await this.updateContainerModifiedDate(container);
}
await this.accessor.deleteResource(identifier);
return deleted;
changes[identifier.path] = this.createActivityMetadata(identifier, AS.Delete);
return changes;
}
/**
@@ -359,7 +362,7 @@ export class DataAccessorBasedStore implements ResourceStore {
* @returns Identifiers of resources that were possibly modified.
*/
protected async writeData(identifier: ResourceIdentifier, representation: Representation, isContainer: boolean,
createContainers: boolean, exists: boolean): Promise<ResourceIdentifier[]> {
createContainers: boolean, exists: boolean): Promise<ChangeMap> {
// Make sure the metadata has the correct identifier and correct type quads
// Need to do this before handling container data to have the correct identifier
representation.metadata.identifier = DataFactory.namedNode(identifier.path);
@@ -382,18 +385,22 @@ export class DataAccessorBasedStore implements ResourceStore {
// Solid, §5.3: "Servers MUST create intermediate containers and include corresponding containment triples
// in container representations derived from the URI path component of PUT and PATCH requests."
// https://solid.github.io/specification/protocol#writing-resources
const modified = [];
let changes: ChangeMap = {};
if (!this.identifierStrategy.isRootContainer(identifier) && !exists) {
const container = this.identifierStrategy.getParentContainer(identifier);
const parent = this.identifierStrategy.getParentContainer(identifier);
if (!createContainers) {
modified.push(container);
changes[parent.path] = this.createActivityMetadata(parent, AS.Update);
} else {
const created = await this.createRecursiveContainers(container);
modified.push(...created.length === 0 ? [ container ] : created);
const createdContainers = await this.createRecursiveContainers(parent);
changes = { ...changes, ...createdContainers };
if (Object.keys(createdContainers).length === 0) {
changes[parent.path] = this.createActivityMetadata(parent, AS.Update);
}
}
// Parent container is also modified
await this.updateContainerModifiedDate(container);
await this.updateContainerModifiedDate(parent);
}
// Remove all generated metadata to prevent it from being stored permanently
@@ -403,7 +410,8 @@ export class DataAccessorBasedStore implements ResourceStore {
this.accessor.writeContainer(identifier, representation.metadata) :
this.accessor.writeDocument(identifier, representation.data, representation.metadata));
return [ ...modified, identifier ];
changes[identifier.path] = this.createActivityMetadata(identifier, exists ? AS.Update : AS.Create);
return changes;
}
/**
@@ -597,7 +605,7 @@ export class DataAccessorBasedStore implements ResourceStore {
* Will throw errors if the identifier of the last existing "container" corresponds to an existing document.
* @param container - Identifier of the container which will need to exist.
*/
protected async createRecursiveContainers(container: ResourceIdentifier): Promise<ResourceIdentifier[]> {
protected async createRecursiveContainers(container: ResourceIdentifier): Promise<ChangeMap> {
// Verify whether the container already exists
try {
const metadata = await this.getNormalizedMetadata(container);
@@ -609,7 +617,7 @@ export class DataAccessorBasedStore implements ResourceStore {
if (!isContainerPath(metadata.identifier.value)) {
throw new ForbiddenHttpError(`Creating container ${container.path} conflicts with an existing resource.`);
}
return [];
return {};
} catch (error: unknown) {
if (!NotFoundHttpError.isInstance(error)) {
throw error;
@@ -618,9 +626,14 @@ export class DataAccessorBasedStore implements ResourceStore {
// Create the container, starting with its parent
const ancestors = this.identifierStrategy.isRootContainer(container) ?
[] :
{} :
await this.createRecursiveContainers(this.identifierStrategy.getParentContainer(container));
await this.writeData(container, new BasicRepresentation([], container), true, false, false);
return [ ...ancestors, container ];
const changes = await this.writeData(container, new BasicRepresentation([], container), true, false, false);
return { ...changes, ...ancestors };
}
private createActivityMetadata(id: ResourceIdentifier, activity: string): RepresentationMetadata {
return new RepresentationMetadata(id, { [SOLID_AS.terms.Activity.value]: activity });
}
}

View File

@@ -10,7 +10,7 @@ import type { ExpiringReadWriteLocker } from '../util/locking/ExpiringReadWriteL
import { endOfStream } from '../util/StreamUtil';
import type { AtomicResourceStore } from './AtomicResourceStore';
import type { Conditions } from './Conditions';
import type { ResourceStore } from './ResourceStore';
import type { ResourceStore, ChangeMap } from './ResourceStore';
/**
* Store that for every call acquires a lock before executing it on the requested resource,
@@ -46,27 +46,27 @@ export class LockingResourceStore implements AtomicResourceStore {
}
public async addResource(container: ResourceIdentifier, representation: Representation,
conditions?: Conditions): Promise<ResourceIdentifier> {
conditions?: Conditions): Promise<ChangeMap> {
return this.locks.withWriteLock(this.getLockIdentifier(container),
async(): Promise<ResourceIdentifier> => this.source.addResource(container, representation, conditions));
async(): Promise<ChangeMap> => this.source.addResource(container, representation, conditions));
}
public async setRepresentation(identifier: ResourceIdentifier, representation: Representation,
conditions?: Conditions): Promise<ResourceIdentifier[]> {
conditions?: Conditions): Promise<ChangeMap> {
return this.locks.withWriteLock(this.getLockIdentifier(identifier),
async(): Promise<ResourceIdentifier[]> => this.source.setRepresentation(identifier, representation, conditions));
async(): Promise<ChangeMap> => this.source.setRepresentation(identifier, representation, conditions));
}
public async deleteResource(identifier: ResourceIdentifier,
conditions?: Conditions): Promise<ResourceIdentifier[]> {
conditions?: Conditions): Promise<ChangeMap> {
return this.locks.withWriteLock(this.getLockIdentifier(identifier),
async(): Promise<ResourceIdentifier[]> => this.source.deleteResource(identifier, conditions));
async(): Promise<ChangeMap> => this.source.deleteResource(identifier, conditions));
}
public async modifyResource(identifier: ResourceIdentifier, patch: Patch,
conditions?: Conditions): Promise<ResourceIdentifier[]> {
conditions?: Conditions): Promise<ChangeMap> {
return this.locks.withWriteLock(this.getLockIdentifier(identifier),
async(): Promise<ResourceIdentifier[]> => this.source.modifyResource(identifier, patch, conditions));
async(): Promise<ChangeMap> => this.source.modifyResource(identifier, patch, conditions));
}
/**

View File

@@ -3,8 +3,9 @@ import type { Patch } from '../http/representation/Patch';
import type { Representation } from '../http/representation/Representation';
import type { RepresentationPreferences } from '../http/representation/RepresentationPreferences';
import type { ResourceIdentifier } from '../http/representation/ResourceIdentifier';
import { AS, SOLID_AS } from '../util/Vocabularies';
import type { Conditions } from './Conditions';
import type { ResourceStore } from './ResourceStore';
import type { ResourceStore, ChangeMap } from './ResourceStore';
/**
* Store that notifies listeners of changes to its source
@@ -29,31 +30,34 @@ export class MonitoringStore<T extends ResourceStore = ResourceStore>
}
public async addResource(container: ResourceIdentifier, representation: Representation,
conditions?: Conditions): Promise<ResourceIdentifier> {
const identifier = await this.source.addResource(container, representation, conditions);
this.emitChanged([ container, identifier ]);
return identifier;
conditions?: Conditions): Promise<ChangeMap> {
return this.emitChanged(await this.source.addResource(container, representation, conditions));
}
public async deleteResource(identifier: ResourceIdentifier,
conditions?: Conditions): Promise<ResourceIdentifier[]> {
conditions?: Conditions): Promise<ChangeMap> {
return this.emitChanged(await this.source.deleteResource(identifier, conditions));
}
public async setRepresentation(identifier: ResourceIdentifier, representation: Representation,
conditions?: Conditions): Promise<ResourceIdentifier[]> {
conditions?: Conditions): Promise<ChangeMap> {
return this.emitChanged(await this.source.setRepresentation(identifier, representation, conditions));
}
public async modifyResource(identifier: ResourceIdentifier, patch: Patch,
conditions?: Conditions): Promise<ResourceIdentifier[]> {
conditions?: Conditions): Promise<ChangeMap> {
return this.emitChanged(await this.source.modifyResource(identifier, patch, conditions));
}
private emitChanged(identifiers: ResourceIdentifier[]): typeof identifiers {
for (const identifier of identifiers) {
this.emit('changed', identifier);
private emitChanged(changes: ChangeMap): ChangeMap {
for (const [ key, value ] of Object.entries(changes)) {
const activity = value.get(SOLID_AS.terms.Activity)?.value;
this.emit('changed', { path: key }, activity);
if (activity && [ AS.Create, AS.Delete, AS.Update ].includes(activity)) {
this.emit(activity, { path: key });
}
}
return identifiers;
return changes;
}
}

View File

@@ -3,7 +3,7 @@ import type { Representation } from '../http/representation/Representation';
import type { RepresentationPreferences } from '../http/representation/RepresentationPreferences';
import type { ResourceIdentifier } from '../http/representation/ResourceIdentifier';
import type { Conditions } from './Conditions';
import type { ResourceStore } from './ResourceStore';
import type { ResourceStore, ChangeMap } from './ResourceStore';
/**
* Store that calls the corresponding functions of the source Store.
@@ -27,22 +27,22 @@ export class PassthroughStore<T extends ResourceStore = ResourceStore> implement
}
public async addResource(container: ResourceIdentifier, representation: Representation,
conditions?: Conditions): Promise<ResourceIdentifier> {
conditions?: Conditions): Promise<ChangeMap> {
return this.source.addResource(container, representation, conditions);
}
public async deleteResource(identifier: ResourceIdentifier,
conditions?: Conditions): Promise<ResourceIdentifier[]> {
conditions?: Conditions): Promise<ChangeMap> {
return this.source.deleteResource(identifier, conditions);
}
public async modifyResource(identifier: ResourceIdentifier, patch: Patch,
conditions?: Conditions): Promise<ResourceIdentifier[]> {
conditions?: Conditions): Promise<ChangeMap> {
return this.source.modifyResource(identifier, patch, conditions);
}
public async setRepresentation(identifier: ResourceIdentifier, representation: Representation,
conditions?: Conditions): Promise<ResourceIdentifier[]> {
conditions?: Conditions): Promise<ChangeMap> {
return this.source.setRepresentation(identifier, representation, conditions);
}
}

View File

@@ -4,7 +4,7 @@ import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError'
import type { Conditions } from './Conditions';
import { PassthroughStore } from './PassthroughStore';
import type { PatchHandler } from './patch/PatchHandler';
import type { ResourceStore } from './ResourceStore';
import type { ResourceStore, ChangeMap } from './ResourceStore';
/**
* {@link ResourceStore} using decorator pattern for the `modifyResource` function.
@@ -20,7 +20,7 @@ export class PatchingStore<T extends ResourceStore = ResourceStore> extends Pass
}
public async modifyResource(identifier: ResourceIdentifier, patch: Patch,
conditions?: Conditions): Promise<ResourceIdentifier[]> {
conditions?: Conditions): Promise<ChangeMap> {
try {
return await this.source.modifyResource(identifier, patch, conditions);
} catch (error: unknown) {

View File

@@ -4,7 +4,7 @@ import type { ResourceIdentifier } from '../http/representation/ResourceIdentifi
import { ForbiddenHttpError } from '../util/errors/ForbiddenHttpError';
import type { Conditions } from './Conditions';
import { PassthroughStore } from './PassthroughStore';
import type { ResourceStore } from './ResourceStore';
import type { ResourceStore, ChangeMap } from './ResourceStore';
/**
* Store that only allow read operations on the underlying source.
@@ -16,22 +16,22 @@ export class ReadOnlyStore<T extends ResourceStore = ResourceStore> extends Pass
}
public async addResource(container: ResourceIdentifier, representation: Representation,
conditions?: Conditions): Promise<ResourceIdentifier> {
conditions?: Conditions): Promise<ChangeMap> {
throw new ForbiddenHttpError();
}
public async deleteResource(identifier: ResourceIdentifier,
conditions?: Conditions): Promise<ResourceIdentifier[]> {
conditions?: Conditions): Promise<ChangeMap> {
throw new ForbiddenHttpError();
}
public async modifyResource(identifier: ResourceIdentifier, patch: Patch,
conditions?: Conditions): Promise<ResourceIdentifier[]> {
conditions?: Conditions): Promise<ChangeMap> {
throw new ForbiddenHttpError();
}
public async setRepresentation(identifier: ResourceIdentifier, representation: Representation,
conditions?: Conditions): Promise<ResourceIdentifier[]> {
conditions?: Conditions): Promise<ChangeMap> {
throw new ForbiddenHttpError();
}
}

View File

@@ -6,7 +6,7 @@ import type { Conditions } from './Conditions';
import { PassthroughConverter } from './conversion/PassthroughConverter';
import type { RepresentationConverter } from './conversion/RepresentationConverter';
import { PassthroughStore } from './PassthroughStore';
import type { ResourceStore } from './ResourceStore';
import type { ResourceStore, ChangeMap } from './ResourceStore';
/**
* Store that provides (optional) conversion of incoming and outgoing {@link Representation}s.
@@ -40,7 +40,7 @@ export class RepresentationConvertingStore<T extends ResourceStore = ResourceSto
}
public async addResource(identifier: ResourceIdentifier, representation: Representation,
conditions?: Conditions): Promise<ResourceIdentifier> {
conditions?: Conditions): Promise<ChangeMap> {
// We can potentially run into problems here if we convert a turtle document where the base IRI is required,
// since we don't know the resource IRI yet at this point.
representation = await this.inConverter.handleSafe({ identifier, representation, preferences: this.inPreferences });
@@ -48,7 +48,7 @@ export class RepresentationConvertingStore<T extends ResourceStore = ResourceSto
}
public async setRepresentation(identifier: ResourceIdentifier, representation: Representation,
conditions?: Conditions): Promise<ResourceIdentifier[]> {
conditions?: Conditions): Promise<ChangeMap> {
representation = await this.inConverter.handleSafe({ identifier, representation, preferences: this.inPreferences });
return this.source.setRepresentation(identifier, representation, conditions);
}

View File

@@ -1,10 +1,18 @@
import type { Patch } from '../http/representation/Patch';
import type { Representation } from '../http/representation/Representation';
import type { RepresentationMetadata } from '../http/representation/RepresentationMetadata';
import type { RepresentationPreferences } from '../http/representation/RepresentationPreferences';
import type { ResourceIdentifier } from '../http/representation/ResourceIdentifier';
import type { Conditions } from './Conditions';
import type { ResourceSet } from './ResourceSet';
/**
* An object containing one property for each resource that was created, updated or deleted
* by this operation. Where the key of the property is the path of the resource (string) and the value is an
* instance of RepresentationMetadata containing extra information about the change of the resource.
*/
export type ChangeMap = Record<string, RepresentationMetadata>;
/**
* A ResourceStore represents a collection of resources.
* It has been designed such that each of its methods
@@ -38,13 +46,13 @@ export interface ResourceStore extends ResourceSet {
* @param representation - New representation of the resource.
* @param conditions - Optional conditions under which to proceed.
*
* @returns Identifiers of resources that were possibly modified.
* @returns A {@link ChangeMap}.
*/
setRepresentation: (
identifier: ResourceIdentifier,
representation: Representation,
conditions?: Conditions,
) => Promise<ResourceIdentifier[]>;
) => Promise<ChangeMap>;
/**
* Creates a new resource in the container.
@@ -52,25 +60,25 @@ export interface ResourceStore extends ResourceSet {
* @param representation - Representation of the new resource
* @param conditions - Optional conditions under which to proceed.
*
* @returns The identifier of the newly created resource.
* @returns A {@link ChangeMap}.
*/
addResource: (
container: ResourceIdentifier,
representation: Representation,
conditions?: Conditions,
) => Promise<ResourceIdentifier>;
) => Promise<ChangeMap>;
/**
* Deletes a resource.
* @param identifier - Identifier of resource to delete.
* @param conditions - Optional conditions under which to proceed.
*
* @returns Identifiers of resources that were possibly modified.
* @returns A {@link ChangeMap}.
*/
deleteResource: (
identifier: ResourceIdentifier,
conditions?: Conditions,
) => Promise<ResourceIdentifier[]>;
) => Promise<ChangeMap>;
/**
* Sets or updates the representation of a resource,
@@ -79,11 +87,11 @@ export interface ResourceStore extends ResourceSet {
* @param patch - Description of which parts to update.
* @param conditions - Optional conditions under which to proceed.
*
* @returns Identifiers of resources that were possibly modified.
* @returns A {@link ChangeMap}.
*/
modifyResource: (
identifier: ResourceIdentifier,
patch: Patch,
conditions?: Conditions,
) => Promise<ResourceIdentifier[]>;
) => Promise<ChangeMap>;
}

View File

@@ -5,7 +5,7 @@ import type { ResourceIdentifier } from '../http/representation/ResourceIdentifi
import { NotFoundHttpError } from '../util/errors/NotFoundHttpError';
import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError';
import type { Conditions } from './Conditions';
import type { ResourceStore } from './ResourceStore';
import type { ResourceStore, ChangeMap } from './ResourceStore';
import type { RouterRule } from './routing/RouterRule';
/**
@@ -31,22 +31,22 @@ export class RoutingResourceStore implements ResourceStore {
}
public async addResource(container: ResourceIdentifier, representation: Representation,
conditions?: Conditions): Promise<ResourceIdentifier> {
conditions?: Conditions): Promise<ChangeMap> {
return (await this.getStore(container, representation)).addResource(container, representation, conditions);
}
public async setRepresentation(identifier: ResourceIdentifier, representation: Representation,
conditions?: Conditions): Promise<ResourceIdentifier[]> {
conditions?: Conditions): Promise<ChangeMap> {
return (await this.getStore(identifier, representation)).setRepresentation(identifier, representation, conditions);
}
public async deleteResource(identifier: ResourceIdentifier,
conditions?: Conditions): Promise<ResourceIdentifier[]> {
conditions?: Conditions): Promise<ChangeMap> {
return (await this.getStore(identifier)).deleteResource(identifier, conditions);
}
public async modifyResource(identifier: ResourceIdentifier, patch: Patch,
conditions?: Conditions): Promise<ResourceIdentifier[]> {
conditions?: Conditions): Promise<ChangeMap> {
return (await this.getStore(identifier)).modifyResource(identifier, patch, conditions);
}

View File

@@ -1,7 +1,7 @@
import type { Patch } from '../../http/representation/Patch';
import type { ResourceIdentifier } from '../../http/representation/ResourceIdentifier';
import { AsyncHandler } from '../../util/handlers/AsyncHandler';
import type { ResourceStore } from '../ResourceStore';
import type { ResourceStore, ChangeMap } from '../ResourceStore';
export type PatchHandlerInput<T extends ResourceStore = ResourceStore> = {
source: T;
@@ -13,4 +13,4 @@ export type PatchHandlerInput<T extends ResourceStore = ResourceStore> = {
* Executes the given Patch.
*/
export abstract class PatchHandler<T extends ResourceStore = ResourceStore>
extends AsyncHandler<PatchHandlerInput<T>, ResourceIdentifier[]> {}
extends AsyncHandler<PatchHandlerInput<T>, ChangeMap> {}

View File

@@ -1,7 +1,7 @@
import type { Representation } from '../../http/representation/Representation';
import type { ResourceIdentifier } from '../../http/representation/ResourceIdentifier';
import { getLoggerFor } from '../../logging/LogUtil';
import { NotFoundHttpError } from '../../util/errors/NotFoundHttpError';
import type { ChangeMap } from '../ResourceStore';
import type { PatchHandlerInput } from './PatchHandler';
import { PatchHandler } from './PatchHandler';
import type { RepresentationPatcher } from './RepresentationPatcher';
@@ -23,7 +23,7 @@ export class RepresentationPatchHandler extends PatchHandler {
this.patcher = patcher;
}
public async handle({ source, patch, identifier }: PatchHandlerInput): Promise<ResourceIdentifier[]> {
public async handle({ source, patch, identifier }: PatchHandlerInput): Promise<ChangeMap> {
// Get the representation from the store
let representation: Representation | undefined;
try {

View File

@@ -71,6 +71,12 @@ export const ACL = createUriAndTermNamespace('http://www.w3.org/ns/auth/acl#',
'Control',
);
export const AS = createUriAndTermNamespace('https://www.w3.org/ns/activitystreams#',
'Create',
'Delete',
'Update',
);
export const AUTH = createUriAndTermNamespace('urn:solid:auth:',
'userMode',
'publicMode',
@@ -144,6 +150,10 @@ export const SOLID = createUriAndTermNamespace('http://www.w3.org/ns/solid/terms
'InsertDeletePatch',
);
export const SOLID_AS = createUriAndTermNamespace('http://www.w3.org/ns/solid/activitystreams#',
'Activity',
);
export const SOLID_ERROR = createUriAndTermNamespace('urn:npm:solid:community-server:error:',
'disallowedMethod',
'errorResponse',