Skip to content

πŸ—„οΈ Data Management

Andre Kless edited this page Mar 27, 2026 · 19 revisions

Data management in ccmjs is based on datastore accessors that abstract the underlying storage layer and provide a unified interface for reading, writing, and querying datasets.

The ccmjs framework provides two closely related API functions:

Function Returns Typical usage
ccm.store(config) Datastore accessor Continuous interaction with a datastore (read/write multiple datasets)
ccm.get(config, key_or_query) Dataset or Dataset[] One-time data retrieval

βš™οΈ ccm.store()

Creates a datastore accessor that provides unified access to datasets.

Syntax

ccm.store( [config] ) β†’ Promise<Datastore>

Parameters

Parameter Type Description
config Object (optional) Datastore configuration that determines how and where datasets are stored

Configuration Options

Depending on the configuration, the framework automatically selects the appropriate datastore implementation:

Condition Store Type
no name provided InMemoryStore (volatile memory)
name but no url OfflineStore (persistent browser storage via IndexedDB)
name and url RemoteStore (server-based datastore)

All datastore implementations share the same API and can be used interchangeably.

InMemoryStore Options

Used when no name is provided.

Option Type Description
datasets Object | Dataset[] Initial datasets. May be provided as { key: dataset } map or array of datasets.

Example:

const store = await ccm.store({
  datasets: [
    { key: "a", value: 1 },
    { key: "b", value: 2 }
  ]
});

const data = await store.get("a");

OfflineStore Options

Used when name is provided but no url.

Option Type Description
name string Name of the object store inside IndexedDB

Example:

const store = await ccm.store({
  name: "tasks"
});

await store.set({
  key: "task1",
  title: "Buy milk",
  done: false
});

const tasks = await store.get({ done: false });

RemoteStore Options

Used when both name and url are provided.

Option Type Description
name string Name of the collection inside remote database
url string Server endpoint used for datastore communication
db string Optional database identifier if the server supports multiple databases
observe Object Query that defines which datasets should be observed
onchange function Callback triggered when observed datasets change
user Object User component instance used for authentication

Example:

const store = await ccm.store({
  name: "tasks",
  url: "https://example.com/api",
  db: "redis",
  observe: { done: false },
  onchange: dataset => {
    console.log("Dataset changed:", dataset);
  }
});

const tasks = await store.get({ done: false });

Return Value

Returns a Promise that resolves to a datastore accessor.

The returned accessor provides the following methods:

store.get( key_or_query )
store.set( dataset )
store.del( key )
store.count( query )
store.clear()

All methods return Promises.

Example

<!DOCTYPE html>
<html>
  <head>
    <script src="https://ccmjs.github.io/framework/ccm.js"></script>
    <script>
      (async () => {

        const store = await ccm.store({
          datasets: [
            { key: "a", value: 1 },
            { key: "b", value: 2 }
          ]
        });

        const data = await store.get("a");
        console.log(data);

      })();
    </script>
  </head>
</html>

Console output:

{ key: "a", value: 1 }

Description

ccm.store() is the primary entry point for data access in ccmjs. It creates a datastore accessor that encapsulates the underlying storage mechanism and exposes a uniform API for interacting with datasets. Depending on the configuration, ccmjs automatically selects one of the following datastore implementations:

  • InMemoryStore – volatile storage in a JavaScript object
  • OfflineStore – persistent storage in the browser via IndexedDB
  • RemoteStore – persistent storage on a remote server

All implementations expose the same datastore API.

Notes

  • If no configuration is provided, an empty InMemoryStore is created.
  • Datastore accessors are lightweight and can be created multiple times.
  • Components interact only with the datastore API and remain independent of the storage implementation.

πŸ“„ Dataset Structure

A dataset is a plain JavaScript object containing a unique key. The key must be unique within a datastore.

Example:

{
  "key": "task1",
  "title": "Buy milk",
  "done": false
}

The key uniquely identifies a dataset within a datastore. Apart from the key, datasets may contain arbitrary properties. ccmjs does not enforce a predefined schema.

πŸ”§ Datastore API

All datastore accessors expose the same asynchronous API for interacting with datasets.

get()

Reads one or multiple datasets.

store.get( [key_or_query] ) β†’ Promise<Dataset | Dataset[]>

If a key is provided, the corresponding dataset is returned. If no dataset exists for the given key, the result is null.

const dataset = await store.get("task1");

If a query object is provided, all matching datasets are returned as an array.

const tasks = await store.get({ done: false });

Queries used with get() follow a simple object-based matching model.

Example:

{ done: false, priority: "high" }

This query returns all datasets whose properties match the provided values.

All datastore implementations in ccmjs guarantee support for a portable subset of query functionality based on simple equality matching with plain JavaScript objects and works consistently across all datastore implementations.

Some datastore implementations may support more advanced query capabilities. The exact capabilities depend on the underlying datastore implementation. For example, a remote datastore backed by MongoDB may allow the full MongoDB query language. Advanced queries may also support additional parameters such as field projections and datastore-specific query options.

Example (MongoDB-style query):

const tasks = await ccm.get(
  { name: "tasks", url: "https://example.com/api" },
  { age: { $gt: 18 } },  // advanced query
  { title: 1, done: 1 },  // projection
  { limit: 10, sort: { title: 1 } }  // options
);

However, such advanced queries are not guaranteed to be portable across different datastore implementations.

To ensure that components remain datastore-independent and reusable, it is recommended to rely only on the portable query subset unless a specific datastore implementation is intentionally required.

set()

Creates or updates a dataset.

store.set( dataset ) β†’ Promise<Dataset>

Example:

await store.set({
  key: "task1",
  title: "Buy milk"
});

If no key is provided, a key is generated automatically. If a dataset with the same key already exists, it is updated. The operation resolves to the created or updated dataset.

del()

Deletes a dataset.

store.del( key ) β†’ Promise<Dataset>

Example:

await store.del("task1");

The operation resolves to the deleted dataset, or null if no dataset existed for the given key.

count()

Counts datasets matching a query.

store.count( query ) β†’ Promise<number>

Example:

const total = await store.count({ done: false });

clear()

Removes all datasets from the datastore.

store.clear() β†’ Promise<void>

Example:

await store.clear();

🧱 Storage Layers

ccmjs supports three interchangeable storage layers. The datastore implementation used by ccm.store() depends on the provided configuration.

InMemoryStore

Datasets are stored in a JavaScript object within the current runtime environment.

Characteristics:

  • volatile storage
  • fastest access
  • data is lost when the page reloads

Typical use cases:

  • temporary app state
  • development and testing
  • default datasets in configurations

OfflineStore

Datasets are stored persistently in the browser using IndexedDB.

Characteristics:

  • persistent across page reloads
  • entirely client-side
  • no server required

Typical use cases:

  • offline-capable apps
  • local user data
  • client-side caching

RemoteStore

Datasets are stored on a remote server.

Characteristics:

  • persistent server-side storage
  • shared across multiple clients
  • optional authentication
  • optional realtime updates via WebSocket

Typical use cases:

  • multi-user apps
  • shared app data
  • collaborative tools

βš™οΈ ccm.get()

Reads one or more datasets from a datastore.

Syntax

ccm.get( [config], [key_or_query] ) β†’ Promise<Dataset | Dataset[]>

Parameters

Parameter Type Description
config Object Datastore configuration. Same options as in ccm.store().
key_or_query string | Object Dataset key or query object

Return Value

Returns a Promise that resolves to a dataset or an array of datasets depending on whether a key or query was provided. If a dataset does not exist, the result is null.

Example

const dataset = await ccm.get(
  { name: "tasks", url: "https://example.com/api" },
  "task1"
);

Example result:

{ key: "task1", title: "Buy milk" }

Query example:

const tasks = await ccm.get(
  { name: "tasks", url: "https://example.com/api" },
  { done: false }
);

Description

ccm.get() is a convenience function for retrieving datasets. Internally, it performs the following steps:

ccm.store(config)
      ↓
store.get(key_or_query)

This function is especially useful when data should be read only once and no further datastore interaction is required. It is also commonly used inside component configurations to declare data dependencies.

Notes

  • ccm.get() always creates a temporary datastore accessor internally.
  • For repeated operations on the same datastore, ccm.store() should be used instead.

πŸ”— Data Dependencies

ccmjs allows components to declare data-related dependencies directly in their configuration. These dependencies are resolved automatically during instance creation.

Two types of data dependencies exist:

  • Datastore dependencies – Provide a datastore accessor
  • Dataset dependencies – Load datasets directly

Datastore Dependencies

A component can declare a dependency on a datastore accessor using ccm.store().

Example:

config: {
  store: [ "ccm.store", { name: "tasks", url: "https://example.com/api" } ]
}

During instance creation, ccmjs resolves this dependency and injects a datastore accessor into the configuration. These dependencies are resolved before the instance’s init() phase. The component can then interact with the datastore programmatically:

const tasks = await this.store.get({ done: false });
await this.store.set({ key: "task1", done: true });

This is useful when a component needs continuous access to a datastore, for example to perform multiple read or write operations.

Dataset Dependencies

If only datasets are required, they can be loaded directly using ccm.get().

Example:

config: {
  tasks: [ "ccm.get", { name: "tasks", url: "https://example.com/api" }, { done: false } ]
}

During instance creation, ccmjs automatically resolves this dependency and injects the retrieved datasets into the component configuration. This allows components to declare required data declaratively, without manual loading logic.

Datasets can also provide the configuration for a component instance. In this example, the configuration for a quiz component is stored in a datastore and loaded declaratively using ccm.get():

ccm.start(
  "https://ccmjs.github.io/quiz/ccm.quiz.mjs",
  ["ccm.get", { "name": "quiz_configs", "url": "https://example.com/api" }, "math_quiz"],
  document.body
);

The retrieved dataset becomes the configuration for the component instance. The dataset structure therefore directly defines the configuration of the app instance.

The same configuration dependency can also be declared directly in HTML:

<ccm-app
  component="https://ccmjs.github.io/quiz/ccm.quiz.mjs"
  config='["ccm.get", { "name": "quiz_configs", "url": "/api" }, "math_quiz"]'>
</ccm-app>