-
Notifications
You must be signed in to change notification settings - Fork 0
ποΈ Data Management
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 |
Creates a datastore accessor that provides unified access to datasets.
ccm.store( [config] ) β Promise<Datastore>| Parameter | Type | Description |
|---|---|---|
config |
Object (optional) |
Datastore configuration that determines how and where datasets are stored |
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.
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");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 });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 });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.
<!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 }
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.
- 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.
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.
All datastore accessors expose the same asynchronous API for interacting with datasets.
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.
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.
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.
Counts datasets matching a query.
store.count( query ) β Promise<number>Example:
const total = await store.count({ done: false });Removes all datasets from the datastore.
store.clear() β Promise<void>Example:
await store.clear();ccmjs supports three interchangeable storage layers.
The datastore implementation used by ccm.store() depends on the provided configuration.
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
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
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
Reads one or more datasets from a datastore.
ccm.get( [config], [key_or_query] ) β Promise<Dataset | Dataset[]>| Parameter | Type | Description |
|---|---|---|
config |
Object |
Datastore configuration. Same options as in ccm.store(). |
key_or_query |
string | Object |
Dataset key or query object |
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.
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 }
);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.
-
ccm.get()always creates a temporary datastore accessor internally. - For repeated operations on the same datastore,
ccm.store()should be used instead.
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
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.
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>