Resource
Resources are the fundamental units of Atomic Data. All data fetched using the store is represented as a resource.
In @tomic/lib resources are instances of the Resource
class.
Getting a resource
A resource can be obtained by either fetching it from the server or by creating a new one.
To fetch a resource, use the store.getResource
method.
const resource = await store.getResource('https://my-resource-subject.com');
Read more about fetching resources.
Creating a new resource is done using the store.newResource
method.
const resource = await store.newResource();
Read more about creating resources.
Typescript
Resources can be annotated with the subject of a class. This subject has to be known to @tomic/lib.
import { type Article } from './ontologies/article'; // File generated by @tomic/cli
const resource = await store.getResource<Article>(
'https://my-resource-subject.com',
);
Annotating resources opens up a lot of great dev experience improvements, such as autocompletion and type checking. Read more about generating ontologies with @tomic/cli.
Reading Data
How you read data from a resource depends on whether you've annotated the resource with a class or not.
For annotated resources, it's as easy as using the .props
accessor:
import { type Article } from './ontologies/article';
const resource = await store.getResource<Article>(
'https://my-atomicserver.com/my-resource',
);
console.log(resource.props.category); // string
console.log(resource.props.likesAmount); // number
for non annotated resources you can use the .get
method:
import { core } from '@tomic/lib';
const resource = await store.getResource(
'https://my-atomicserver.com/my-resource',
);
const description = resource.get(core.properties.description); // string | undefined
const category = resource.get(
'https://my-atomicserver.com/properties/category',
); // JSONValue
Writing Data
Writing data is done using the .set
method.
import { core } from '@tomic/lib';
// With Validation
await resource.set(core.properties.description, 'New description');
// Without Validation
resource.set(core.properties.description, 'New description', false);
await resource.save();
By default, .set
validates the value against the properties datatype.
You should await the method when validation is enabled because the property's resource might not be in the store yet and has to be fetched.
Note Setting validate to false only disables validation on the client. The server will always validate the data and respond with an error if the data is invalid.
Parameters
Name | Type | Description |
---|---|---|
property | string | Subject of the property to set |
value | JSONValue* | The value to set |
validate | boolean | Whether to validate the value against the property's datatype |
*When setting properties from known ontologies, you get automatic type-checking as a bonus.
Pushing to ResourceArrays
You can use the .push
method to push data to a ResourceArray
property.
import { socialmedia } from '../ontologies/socialmedia';
resource.push(socialmedia.properties.likedBy, [
'https://my-atomicserver.com/users/1',
]);
// Only add values that are not in the array already
resource.push(
socialmedia.properties.likedBy,
['https://my-atomicserver.com/users/1'],
true,
);
await resource.save();
Parameters
Name | Type | Description |
---|---|---|
property | string | Subject of the property to push to |
values | JSONArray | list of values to push |
unique | boolean | (Optional) When true, does not push values already contained in the list. (Defaults to false ) |
Removing properties
Removing properties is done using the .remove
method.
Alternatively, you can pass undefined as a value to .set
import { core } from '@tomic/lib';
resource.remove(core.properties.description);
// or
resource.set(core.properties.description, undefined);
await resource.save();
Saving
When using methods like .set()
and .push()
, the changes will be collected into a single commit.
This commit is only reflected locally and has to be sent to the server to make the changes permanent.
To do this, call the .save()
method.
await resource.save();
You can check if a resource has unsaved changes with the .hasUnsavedChanges()
method.
if (resource.hasUnsavedChanges()) {
// Show a save button
}
Deleting resources
Deleting resources is done using the .destroy()
method.
await resource.destroy();
When a resource is deleted, all children of the resource are also deleted.
Classes
Classes are an essential part of Atomic Data, therefore Resource has a few useful methods for reading and setting classes.
Reading classes
Resource
provides a .getClasses
method to get the classes of the resource.
const classes = resource.getClasses(); // string[]
// Syntactic sugar for:
// const classes = resource.get(core.properties.isA);
If you just want to know if a resource is of a certain class use: .hasClasses()
if (resource.hasClasses('https://my-atomicserver.com/classes/Article')) {
//...
}
// multiple classes (AND)
if (resource.hasClasses(core.classes.agent, dataBrowser.classes.folder)) {
// ...
}
There are often situations where you want the value of some variable to depend on the class of a resource.
An example would be a React component that renders different subcomponents based on the resource it is given.
The .matchClass()
method makes this easy.
// A React component that renders a resource inside a table cell.
function ResourceTableCell({ subject }: ResourceTableCellProps): JSX.Element {
// A react hook that fetches the resource
const resource = useResource(subject);
// Depending on the class of the resource, render a different component
const Comp = resource.matchClass(
{
[core.classes.agent]: AgentCell,
[server.classes.file]: FileCell,
},
BasicCell,
);
return <Comp resource={resource} />;
}
.matchClass()
takes an object that maps class subjects to values.
If the resource has a class that is a key in the object, the corresponding value is returned.
An optional fallback value can be provided as the second argument.
The order of the classes in the object is important, as the first match is returned.
Writing classes
To add classes to a resource, use the .addClasses()
method.
resource.addClasses('https://my-atomicserver.com/classes/Article');
// With multiple classes
resource.addClasses(
'https://my-atomicserver.com/classes/Article',
'https://my-atomicserver.com/classes/Comment',
);
Finally, there is the .removeClasses()
method to remove classes from a resource.
resource.removeClasses('https://my-atomicserver.com/classes/Article');
Access Rights
Sometimes you want to check if your agent has write access to a resource and maybe render a different UI based on that.
To check this use .canWrite()
.
if (await resource.canWrite()) {
// Render a UI with edit buttons
}
You can also get a list of all rights for the resource using .getRights()
.
const rights = await resource.getRights();
History and versions
AtomicServer keeps a record of all changes (commits) done to a resource. This allows you to roll back to anywhere in the history of the resource.
To get a list of each version of the resource use resource.getHistory()
.
When you've found a version you want to roll back to, use resource.setVersion()
.
const versions = await resource.getHistory();
const version = userPicksVersion(versions);
await resource.setVersion(version);
Useful methods and properties
Subject
resource.subject
is a get accessor that returns the subject of the resource.
Loading & Error states
If an error occurs while fetching the resource, resource.error
will be set.
Additionally, when a resource is fetched using store.getResourceLoading()
a resource is returned immediately that is not yet complete.
To check if the resource is not fully loaded yet use resource.loading
.
These properties are useful when you want to show a loading spinner or an error message while fetching a resource.
import { useResource } from '@tomic/react';
function ResourceCard({ subject }: ResourceCardProps): JSX.Element {
// Uses store.getResourceLoading internally.
const resource = useResource(subject);
if (resource.error) {
return <ErrorCard error={resource.error} />;
}
if (resource.loading) {
return <LoadingSpinner />;
}
return <Card>{resource.title}</Card>;
}
If you want to know if a resource has errored because the user was not authorised, use resource.isUnauthorized()
.
Title
resource.title
is a get accessor that returns the title of the resource.
This title is the value of either the name, shortname or filename of the resource, whichever is present first.
Falls back to the subject if none of these are present.
Children
resource.getChildrenCollection()
returns a Collection that has all the children of the resource.