Skip to content

Commit

Permalink
Fix reserved container properties history handling
Browse files Browse the repository at this point in the history
  • Loading branch information
NoelDeMartin committed Jan 28, 2025
1 parent ee68f64 commit c85765c
Show file tree
Hide file tree
Showing 12 changed files with 147 additions and 98 deletions.
55 changes: 40 additions & 15 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"eslint": "^7.20.0",
"jest": "^26.6.3",
"jest-summary-reporter": "0.0.2",
"soukai": "0.5.2-next.376c89f9aaf5d42e43c169445d10fd87ec6aab2d",
"soukai": "0.5.2-next.8da33ba6f17b4fdf885c4a88ad6284bb35f04580",
"ts-jest": "^26.5.2",
"typescript": "^4.1.5"
}
Expand Down
9 changes: 5 additions & 4 deletions src/engines/SolidEngine.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import RDFDocument from '@/solid/RDFDocument';
import RDFResourceProperty from '@/solid/RDFResourceProperty';
import RemovePropertyOperation from '@/solid/operations/RemovePropertyOperation';
import UpdatePropertyOperation from '@/solid/operations/UpdatePropertyOperation';
import { LDP_CONTAINER } from '@/solid/constants';
import type { Fetch } from '@/solid/SolidClient';

import SolidClientMock from '@/solid/__mocks__';
Expand Down Expand Up @@ -93,7 +94,7 @@ describe('SolidEngine', () => {
const properties = SolidClientMock.createDocumentSpy.mock.calls[0]?.[2];

expect(properties).toHaveLength(2);
expect(properties).toContainEqual(RDFResourceProperty.type(documentUrl, IRI('ldp:Container')));
expect(properties).toContainEqual(RDFResourceProperty.type(documentUrl, LDP_CONTAINER));
expect(properties).toContainEqual(RDFResourceProperty.literal(documentUrl, IRI('rdfs:label'), name));
});

Expand All @@ -117,7 +118,7 @@ describe('SolidEngine', () => {
const name = faker.name.firstName();

await SolidClientMock.createDocument(parentUrl, documentUrl, [
RDFResourceProperty.type(documentUrl, IRI('ldp:Container')),
RDFResourceProperty.type(documentUrl, LDP_CONTAINER),
RDFResourceProperty.literal(documentUrl, IRI('rdfs:label'), name),
]);

Expand Down Expand Up @@ -250,7 +251,7 @@ describe('SolidEngine', () => {
const containerUrl = urlResolveDirectory(parentUrl, stringToSlug(containerName));

await SolidClientMock.createDocument(parentUrl, containerUrl, [
RDFResourceProperty.type(containerUrl, IRI('ldp:Container')),
RDFResourceProperty.type(containerUrl, LDP_CONTAINER),
RDFResourceProperty.literal(containerUrl, IRI('rdfs:label'), containerName),
]);

Expand All @@ -264,7 +265,7 @@ describe('SolidEngine', () => {
await expect(documents[containerUrl]).toEqualJsonLD({
'@graph': [{
'@id': containerUrl,
'@type': IRI('ldp:Container'),
'@type': LDP_CONTAINER,
[IRI('rdfs:label')]: containerName,
}],
});
Expand Down
6 changes: 3 additions & 3 deletions src/engines/SolidEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import RDFResourceProperty from '@/solid/RDFResourceProperty';
import RemovePropertyOperation from '@/solid/operations/RemovePropertyOperation';
import SolidClient from '@/solid/SolidClient';
import UpdatePropertyOperation from '@/solid/operations/UpdatePropertyOperation';
import IRI from '@/solid/utils/IRI';
import { LDP_CONTAINER } from '@/solid/constants';
import { usingExperimentalActivityPods } from '@/experimental';
import type { Fetch } from '@/solid/SolidClient';
import type { LiteralValue } from '@/solid/RDFResourceProperty';
Expand Down Expand Up @@ -194,7 +194,7 @@ export class SolidEngine implements Engine {

return filters.$in
? await this.getDocumentsFromUrls(filters.$in.map(toString), rdfsClasses)
: await this.client.getDocuments(collection, rdfsClasses.includes(IRI('ldp:Container')));
: await this.client.getDocuments(collection, rdfsClasses.includes(LDP_CONTAINER));
}

private async getDocumentsFromUrls(urls: string[], rdfsClasses: string[]): Promise<RDFDocument[]> {
Expand All @@ -218,7 +218,7 @@ export class SolidEngine implements Engine {
this.config.globbingBatchSize !== null &&
this.config.globbingBatchSize <= documentUrls.length
)
return this.client.getDocuments(containerUrl, rdfsClasses.includes(IRI('ldp:Container')));
return this.client.getDocuments(containerUrl, rdfsClasses.includes(LDP_CONTAINER));

const documentPromises = documentUrls.map(url => this.getDocument(url));
const documents = await Promise.all(documentPromises);
Expand Down
3 changes: 2 additions & 1 deletion src/models/SolidContainer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { EngineDocument } from 'soukai';
import type { Tuple } from '@noeldemartin/utils';

import IRI from '@/solid/utils/IRI';
import { LDP_CONTAINER } from '@/solid/constants';

import { stubMovieJsonLD, stubMoviesCollectionJsonLD, stubSolidDocumentJsonLD } from '@/testing/lib/stubs/helpers';
import Movie from '@/testing/lib/stubs/Movie';
Expand All @@ -27,7 +28,7 @@ describe('SolidContainer', () => {

bootModels({ StubModel });

expect(StubModel.rdfsClasses).toEqual([IRI('ldp:Container')]);
expect(StubModel.rdfsClasses).toEqual([LDP_CONTAINER]);
});

it('adds resourceUrls field', () => {
Expand Down
13 changes: 13 additions & 0 deletions src/models/SolidContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import { findContainerRegistrations } from '@noeldemartin/solid-utils';
import type { ModelConstructor, Relation } from 'soukai';

import { LDP_CONTAINS } from '@/solid/constants';
import { SolidEngine } from '@/engines/SolidEngine';

import SolidContainerDocumentsRelation from './relations/SolidContainerDocumentsRelation';
Expand Down Expand Up @@ -54,6 +55,14 @@ export default class SolidContainer extends Model {
return new SolidContainerDocumentsRelation(this);
}

protected markAttributeDirty(field: string, originalValue: unknown, newValue: unknown): boolean {
if (field === 'resourceUrls' && this.usingSolidEngine()) {
return false;
}

return super.markAttributeDirty(field, originalValue, newValue);
}

public async register(
typeIndex: string | SolidTypeIndex,
childrenModelClasses: typeof SolidModel | Array<typeof SolidModel>,
Expand Down Expand Up @@ -102,4 +111,8 @@ export default class SolidContainer extends Model {
return urlResolveDirectory(requireUrlParentDirectory(url), `${directoryName}-${shortId()}`);
}

protected ignoreRdfPropertyHistory(rdfProperty: string): boolean {
return this.usingSolidEngine() && rdfProperty === LDP_CONTAINS;
}

}
61 changes: 46 additions & 15 deletions src/models/SolidModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,13 +185,11 @@ export class SolidModel extends SolidModelBase {
}

public static requireFetch(): Fetch {
const engine = this.requireFinalEngine();

if (!(engine instanceof SolidEngine)) {
if (!this.usingSolidEngine()) {
throw new SoukaiError(`Could not get fetch from ${this.modelName} model`);
}

return engine.getFetch();
return (this.requireFinalEngine() as SolidEngine).getFetch();
}

public static requireFieldRdfProperty(field: string): string {
Expand Down Expand Up @@ -345,7 +343,7 @@ export class SolidModel extends SolidModelBase {
// This is necessary because a SolidEngine behaves differently than other engines.
// Even if a document is stored using compacted IRIs, a SolidEngine will need them expanded
// because it's ultimately stored in turtle, not json-ld.
const compactIRIs = !(this.requireFinalEngine() instanceof SolidEngine);
const compactIRIs = !this.usingSolidEngine();

return this.instance().convertEngineFiltersToJsonLD(filters, compactIRIs);
}
Expand Down Expand Up @@ -424,20 +422,24 @@ export class SolidModel extends SolidModelBase {
}

public static async synchronize<T extends SolidModel>(this: SolidModelConstructor<T>, a: T, b: T): Promise<void> {
if (this !== a.static())
if (this !== a.static()) {
return a.static().synchronize(a, b);
}

if (a.getPrimaryKey() !== b.getPrimaryKey())
if (a.getPrimaryKey() !== b.getPrimaryKey()) {
throw new SoukaiError('Can\'t synchronize different models');
}

await a.loadRelationIfUnloaded('operations');
await b.loadRelationIfUnloaded('operations');

if (a.operations.length === 0 && b.operations.length === 0)
if (a.operations.length === 0 && b.operations.length === 0) {
return;
}

if (a.getHistoryHash() === b.getHistoryHash())
if (a.getHistoryHash() === b.getHistoryHash()) {
return;
}

a.addHistoryOperations(b.operations);
b.addHistoryOperations(a.operations);
Expand Down Expand Up @@ -578,6 +580,10 @@ export class SolidModel extends SolidModelBase {
return this.rdfContexts.default ?? '';
}

protected static usingSolidEngine(): boolean {
return this.requireFinalEngine() instanceof SolidEngine;
}

// TODO this should be optional
public url!: string;

Expand Down Expand Up @@ -1194,6 +1200,10 @@ export class SolidModel extends SolidModelBase {
return `solid://${collection}/`;
}

protected usingSolidEngine(): boolean {
return this.requireFinalEngine() instanceof SolidEngine;
}

protected async createFromEngineDocument(
id: Key,
document: EngineDocument,
Expand Down Expand Up @@ -1499,7 +1509,9 @@ export class SolidModel extends SolidModelBase {
continue;
}

if (this.static().getFieldRdfProperty(field) === IRI('ldp:contains')) {
const rdfProperty = this.static().getFieldRdfProperty(field);

if (rdfProperty && this.ignoreRdfPropertyHistory(rdfProperty)) {
continue;
}

Expand All @@ -1512,8 +1524,15 @@ export class SolidModel extends SolidModelBase {
}

for (const [field, value] of Object.entries(this._dirtyAttributes)) {
if (field in this._trackedDirtyAttributes && this._trackedDirtyAttributes[field] === value)
if (field in this._trackedDirtyAttributes && this._trackedDirtyAttributes[field] === value) {
continue;
}

const rdfProperty = this.static().getFieldRdfProperty(field);

if (rdfProperty && this.ignoreRdfPropertyHistory(rdfProperty)) {
continue;
}

if (Array.isArray(value)) {
this.addArrayHistoryOperations(field, value, this._originalAttributes[field]);
Expand Down Expand Up @@ -1545,15 +1564,21 @@ export class SolidModel extends SolidModelBase {
.reduce((fieldProperties, field) => {
const rdfProperty = this.static().getFieldRdfProperty(field);

if (rdfProperty)
if (rdfProperty) {
fieldProperties[rdfProperty] = field;
}

return fieldProperties;
}, {} as Record<string, string>);

for (const operation of operations) {
if (knownOperationUrls.has(operation.url))
if (knownOperationUrls.has(operation.url)) {
continue;
}

if (operation instanceof PropertyOperation && this.ignoreRdfPropertyHistory(operation.property)) {
continue;
}

const newOperation = operation.clone();

Expand All @@ -1562,8 +1587,9 @@ export class SolidModel extends SolidModelBase {

newOperations.push(newOperation);

if ((operation instanceof PropertyOperation) && operation.property in fieldPropertiesMap)
if ((operation instanceof PropertyOperation) && operation.property in fieldPropertiesMap) {
trackedDirtyProperties.add(operation.property);
}
}

this.setRelationModels('operations', arraySorted([...this.operations, ...newOperations], ['date', 'url']));
Expand All @@ -1577,6 +1603,11 @@ export class SolidModel extends SolidModelBase {
}
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
protected ignoreRdfPropertyHistory(rdfProperty: string): boolean {
return false;
}

protected removeDuplicatedHistoryOperations(): void {
const PropertyOperation = operationClass('PropertyOperation');
const inceptionProperties: string[] = [];
Expand Down Expand Up @@ -1704,7 +1735,7 @@ export class SolidModel extends SolidModelBase {
// This is necessary because a SolidEngine behaves differently than other engines.
// Even if a document is stored using compacted IRIs, a SolidEngine will need them expanded
// because it's ultimately stored in turtle, not json-ld.
const compactIRIs = !(this.requireFinalEngine() instanceof SolidEngine);
const compactIRIs = !this.usingSolidEngine();

graphUpdates.push({
$updateItems: {
Expand Down
Loading

0 comments on commit c85765c

Please sign in to comment.