Skip to content

Commit

Permalink
Implement unset operations
Browse files Browse the repository at this point in the history
  • Loading branch information
NoelDeMartin committed Feb 14, 2025
1 parent 8158375 commit 51304d3
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 18 deletions.
14 changes: 7 additions & 7 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.1d8e6bf684a0244b9f2178ef0e8405278dec69e4",
"soukai": "0.5.2-next.7615eba9786f5eee389c3901738a327a6da2823a",
"ts-jest": "^26.5.2",
"typescript": "^4.1.5"
}
Expand Down
34 changes: 30 additions & 4 deletions src/models/SolidModel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import IRI from '@/solid/utils/IRI';
import PropertyOperation from '@/models/history/PropertyOperation';
import RemovePropertyOperation from '@/models/history/RemovePropertyOperation';
import SetPropertyOperation from '@/models/history/SetPropertyOperation';
import UnsetPropertyOperation from '@/models/history/UnsetPropertyOperation';
import { defineSolidModelSchema } from '@/models/schema';
import type { SolidMagicAttributes, SolidModelConstructor } from '@/models/inference';

Expand Down Expand Up @@ -1874,14 +1875,14 @@ describe('SolidModel', () => {
const person = await PersonWithHistory.create({ name: firstName });

await after({ ms: 100 });
await person.update({ name: secondName, lastName: firstLastName });
await person.update({ name: secondName, lastName: firstLastName, age: 42 });
await after({ ms: 100 });
await person.update({ lastName: secondLastName });
await person.update({ lastName: secondLastName, age: null });

// Assert
const operations = person.operations as Tuple<PropertyOperation, 4>;
const operations = person.operations as Tuple<PropertyOperation, 6>;

expect(operations).toHaveLength(4);
expect(operations).toHaveLength(6);

operations.forEach(operation => expect(operation.url.startsWith(person.url)).toBe(true));
operations.forEach(operation => expect(operation.resourceUrl).toEqual(person.url));
Expand All @@ -1907,10 +1908,22 @@ describe('SolidModel', () => {
});

assertInstanceOf(operations[3], SetPropertyOperation, operation => {
expect(operation.property).toEqual(IRI('foaf:age'));
expect(operation.value).toEqual(42);
expect(operation.date.getTime()).toBeGreaterThan(person.createdAt.getTime());
expect(operation.date.getTime()).toBeLessThan(person.updatedAt.getTime());
});

assertInstanceOf(operations[4], SetPropertyOperation, operation => {
expect(operation.property).toEqual(IRI('foaf:lastName'));
expect(operation.value).toEqual(secondLastName);
expect(operation.date).toEqual(person.updatedAt);
});

assertInstanceOf(operations[5], UnsetPropertyOperation, operation => {
expect(operation.property).toEqual(IRI('foaf:age'));
expect(operation.date).toEqual(person.updatedAt);
});
});

it('Tracks history properly for array fields', async () => {
Expand Down Expand Up @@ -2105,6 +2118,7 @@ describe('SolidModel', () => {
name: faker.random.word(),
lastName: faker.random.word(),
givenName: faker.random.word(),
nickName: faker.random.word(),
friendUrls: [
faker.internet.url(),
faker.internet.url(),
Expand All @@ -2124,6 +2138,12 @@ describe('SolidModel', () => {
date: createdAt,
});

person.relatedOperations.attachSetOperation({
property: Person.getFieldRdfProperty('nickName'),
value: faker.random.word(),
date: createdAt,
});

person.relatedOperations.attachSetOperation({
property: Person.getFieldRdfProperty('friendUrls'),
value: initialFriends.map(url => new ModelKey(url)),
Expand All @@ -2143,6 +2163,11 @@ describe('SolidModel', () => {
date: updatedAt,
});

person.relatedOperations.attachUnsetOperation({
property: Person.getFieldRdfProperty('nickName'),
date: updatedAt,
});

person.relatedOperations.attachRemoveOperation({
property: Person.getFieldRdfProperty('friendUrls'),
value: removedFriends.map(url => new ModelKey(url)),
Expand Down Expand Up @@ -2174,6 +2199,7 @@ describe('SolidModel', () => {
expect(person.isSoftDeleted()).toBe(true);
expect(person.name).toEqual(name);
expect(person.lastName).toEqual(lastName);
expect(person.nickName).toBeUndefined();
expect(person.givenName).toBeUndefined();
expect(person.friendUrls).toEqual(
arrayWithout(
Expand Down
18 changes: 14 additions & 4 deletions src/models/SolidModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -613,11 +613,11 @@ export class SolidModel extends SolidModelBase {
primaryKey: string,
timestamps: TimestampFieldValue[],
fieldDefinitions: SolidBootedFieldsDefinition,
): BootedFieldsDefinition {
): { fields: BootedFieldsDefinition; fieldAliases: Record<string, string> } {
const rdfContexts = getSchemaUpdateContext(this) ?? this.rdfContexts;
const defaultRdfContext = this.getDefaultRdfContext(rdfContexts);

super.bootFields(fields, primaryKey, timestamps, fieldDefinitions);
const { fieldAliases } = super.bootFields(fields, primaryKey, timestamps, fieldDefinitions);

if (timestamps.includes(TimestampField.CreatedAt)) {
delete fieldDefinitions[TimestampField.CreatedAt];
Expand All @@ -644,7 +644,10 @@ export class SolidModel extends SolidModelBase {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
delete (fieldDefinitions as any)[primaryKey]?.rdfPropertyAliases;

return fieldDefinitions;
return {
fields: fieldDefinitions,
fieldAliases,
};
}

protected static getDefaultRdfContext(rdfContexts?: RDFContexts): string {
Expand Down Expand Up @@ -1652,7 +1655,14 @@ export class SolidModel extends SolidModelBase {
continue;
}

// TODO handle unset operations
if (value === null || value === undefined) {
this.relatedOperations.attachUnsetOperation({
property: this.static().requireFieldRdfProperty(field),
date: this.metadata.updatedAt,
});

continue;
}

this.relatedOperations.attachSetOperation({
property: this.static().requireFieldRdfProperty(field),
Expand Down
2 changes: 1 addition & 1 deletion src/models/history/UnsetPropertyOperation.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ import { defineSolidModelSchema } from '@/models/schema';
import PropertyOperation from './PropertyOperation';

export default defineSolidModelSchema(PropertyOperation, {
rdfsClass: 'UnSetPropertyOperation',
rdfsClass: 'UnsetPropertyOperation',
});
6 changes: 6 additions & 0 deletions src/models/history/UnsetPropertyOperation.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import type { SolidModel } from '@/models/SolidModel';

import Model from './UnsetPropertyOperation.schema';

export default class UnsetPropertyOperation extends Model {

protected applyPropertyUpdate(model: SolidModel, field: string): void {
model.unsetAttribute(field);
}

}
7 changes: 6 additions & 1 deletion src/models/internals/JsonLDModelSerializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,15 +303,20 @@ export default class JsonLDModelSerializer {
private castJsonLDValue(value: unknown, fieldDefinition: SolidBootedFieldDefinition): unknown {
switch (fieldDefinition.type) {
case FieldType.Any: {
if (value === null || value === undefined) {
return new EmptyJsonLDValue();
}

const inferredFieldDefinition = inferFieldDefinition(
value,
fieldDefinition.rdfProperty,
fieldDefinition.rdfPropertyAliases,
fieldDefinition.required,
);

if (inferredFieldDefinition.type === FieldType.Any)
if (inferredFieldDefinition.type === FieldType.Any) {
throw new SoukaiError('Couldn\'t infer field definition for a field declared as any');
}

return this.castJsonLDValue(value, inferredFieldDefinition);
}
Expand Down

0 comments on commit 51304d3

Please sign in to comment.