Store

The Store class is a central component in the @tomic/lib library that provides a convenient interface for managing and interacting with atomic data resources. It allows you to fetch resources, subscribe to changes, create new resources, perform full-text searches, and more.

Setting up a store

Creating a store is done with the Store constructor.

const store = new Store();

It takes an object with the following options

NameTypeDescription
serverUrlstringURL of your atomic server
agentAgent(optional) The agent the store should use to fetch resources and to sign commits when editing resources, defaults to a public agent
const store = new Store({
  serverUrl: 'https://my-atomic-server.com',
  agent: Agent.fromSecret('my-agent-secret'),
});

NOTE
You can always change or set both the serverUrl and agent at a later time using store.setServerUrl() and store.setAgent() respectively.

One vs Many Stores

Generally in a client application with one authenticated user, you'll want to have a single instance of a Store that is shared throughout the app. This way you'll never fetch resources more than once while still receiving updates via websocket messages. If store is used on the server however, you might want to consider creating a new store for each request as a store can only have a single agent associated with it and changing the agent will reauthenticate all websocket connections.

Fetching resources

NOTE:
If you're using atomic in a frontend library like React or Svelte there might be other ways to fetch resources that are better suited to those libraries. Check @tomic/react or @tomic/svelte

Fetching resources is generally done using the store.getResource() method.

const resource = await store.getResource('https://my-resource-subject');

getResource takes the subject of the resource as a parameter and returns a promise that resolves to the requested resource. The store will cache the resource in memory and subscribe to the server for changes to the resource, subsequent requests for the resource will not fetch over the network but return the cached version.

Subscribe to changes

Atomic makes it easy to build real-time applications. When you subscribe to a subject you get notified every time the resource changes on the server.

store.subscribe('https://my-resource-subject', myResource => {
  // do something with the changed resource.
  console.log(`${myResource.title} changed!`);
});

Unsubscribing

You should not forget to unsubscribe your listeners as this can lead to a growing memory footprint (just like DOM event listeners). To unsubscribe you can either use the returned unsubscribe function or call store.unsubscribe(subject, callback).

const unsubscribe = store.subscribe(
  'https://my-resource-subject',
  myResource => {
    // ...
  },
);

unsubscribe();
const callback = myResource => {
  // ...
};

store.subscribe('https://my-resource-subject', callback);

store.unsubscribe('https://my-resource-subject', callback);

Creating new resources

Creating resources is done using the store.newResource method. It takes an options object with the following properties:

NameTypeDescription
subjectstring(optional) The subject the new resource should have, by default a random subject is generated
parentstring(optional) The parent of the new resource, defaults to the store's serverUrl
isAstring | string[](optional) The 'type' of the resource. determines what class it is. Supports multiple classes.
propValsRecord<string, JSONValue>(optional) Any additional properties you want to set on the resource. Should be an object with subjects of properties as keys
// Basic:
const resource = await store.newResource();

await resource.save();
// With options:
import { core } from '@tomic/lib';

const resource = await store.newResource({
  parent: 'https://myatomicserver.com/some-folder',
  isA: 'https://myatomicserver.com/article',
  propVals: {
    [core.properties.name]: 'How to create new resources',
    [core.properties.description]: 'lorem ipsum dolor sit amet',
    'https://myatomicserver.com/written-by':
      'https://myatomicserver.com/agents/superman',
  },
});

await resource.save();

Generating random subjects

In some cases you might need a subject before you have created the resource with that subject. To generate a random subject, use the store.createSubject() method. This method generates a new subject with the current serverURL as hostname and a random lowercased ULID string as the path.

The method also allows you to pass a parent subject to generate a subject under that parent.

const subject = store.createSubject();
// Result: https://myserver.com/01hw30e1w6t9y0y5aqg0aghhf4


// With parent subject
const subject = store.createSubject(parent.subject);

Keep in mind that subjects never change once they are set, even if the parent changes. This means you can't reliably infer the parent from the subject.

AtomicServer comes included with a full-text search API. Using this API is very easy in @tomic/lib.

const results = await store.search('lorem ipsum');

To further refine your query you can pass an options object with the following properties:

NameTypeDescription
includeboolean(optional) If true sends full resources in the response instead of just the subjects
limitnumber(optional) The max number of results to return, defaults to 30.
parentsstring[](optional) Only include resources that have these given parents somewhere as an ancestor
filtersRecord<string, string>(optional) Only include resources that have matching values for the given properties. Should be an object with subjects of properties as keys

Example: search AtomicServer for all files with 'holiday-1995' in their name:

import { core, server } from '@tomic/lib';

const results = store.search('holiday-1995', {
  filters: {
    [core.properties.isA]: server.classes.file,
  },
});

Preloading resources

Sometimes you might want to prefetch a resource and all the resources it depends on ahead of time. For example when using @tomic/react or @tomic/svelte on the server. This can be done using the store.preloadResourceTree() method. The method takes a subject and an object representing a tree of referenced resources that need to be preloaded.


// Preload a blog post, its header image, the author and the author's profile image
await store.preloadResourceTree('https://myapp.com/my-blog-post', {
  [myOntology.properties.headerImage]: true,
  [myOntology.properties.author]: {
    [myOntology.properties.profileImage]: true,
  },
});

The tree does not have to be complete and can contain any property, this way you can preload any resource even if you're not sure of what type it might be.

(Advanced) Fetching resources in render code

NOTE:
The following is mostly intended for library authors.

When building frontends it is often critical to render as soon as possible, waiting for requests to finish leads to a sluggish UI. Store provides the store.getResourceLoading method that immediately returns an empty resource with resource.loading set to true. You can then subscribe to the subject and rerender when the resource changes.

// some component in a hypothetical framework
function renderSomeComponent(subject: string) {
  const resource = store.getResourceLoading(subject);

  store.subscribe(subject, () => {
    rerender();
  });

  return (
    <div>
      <h1>{resource.loading ? 'loading...' : resource.title}</h1>
      <p> other UI that does not rely on the resource being ready</p>
    </div>
  );
}

For a real-world example check out how we use it inside @tomic/react useResource hook

Events

Store emits a few types of events that your app can listen to.

To listen to these events use the store.on method.

import { StoreEvents } from '@tomic/lib';

store.on(StoreEvents.Error, error => {
  notifyErrorReportingServer(error);
});

The following events are available

Event IDHandler typeDescription
StoreEvents.ResourceSaved(resource: Resource) => voidFired when any resource was saved
StoreEvents.ResourceRemoved(resource: Resource) => voidFired when any resource was deleted
StoreEvents.AgentChanged(agent: Agent) => voidFired when a new agent is set on the store
StoreEvents.Error(error: Error) => voidFired when store encounters an error