Overview

Creating your own plugin in very easy, simply implement the IDbPlugin found in @routier/core and profit. There

Requirements

A plugin must implement a small interface that Routier uses during reads/writes:

  • initialize/destroy lifecycle
  • add/update/remove batched entity operations
  • query execution with parameterized filters, ordering, skip/take
  • change tracking integration (apply computed/tracked fields after save)
  • identity/index awareness (keys, distinct, composite indexes)

Minimal skeleton

import { BulkPersistResult } from "@routier/core/collections";
import type { IDbPlugin, DbPluginBulkPersistEvent, DbPluginQueryEvent, DbPluginEvent, ITranslatedValue } from "@routier/core/plugins";
import { PluginEventCallbackPartialResult, PluginEventCallbackResult, PluginEventResult } from "@routier/core/results";
import { JsonTranslator } from "@routier/core/plugins/translators";

export class MyPlugin implements IDbPlugin {

    // Use the constructor to take in any plugin options
    constructor() {

    }

    query<TShape>(event: DbPluginQueryEvent<any, TShape>, done: PluginEventCallbackResult<ITranslatedValue<TShape>>): void {
        // Execute the event.operation against your backend and invoke the callback
        // Use a translator to wrap results in ITranslatedValue (allows iteration for grouped queries
        // and determines if change tracking should be enabled)
        const translator = new JsonTranslator(event.operation);
        const results: unknown[] = []; // Your query results here

        // translate() automatically wraps results in ITranslatedValue
        const translatedValue = translator.translate(results);
        done(PluginEventResult.success(event.id, translatedValue));
    }

    bulkPersist(event: DbPluginBulkPersistEvent, done: PluginEventCallbackPartialResult<BulkPersistResult>): void {
        // Persist adds/updates/removes from event.operation to your backend

        // The operation is a key value pair of schema Id + changes
        for (const [schemaId, changes] of event.operation) {
            const {
                adds,
                updates,
                hasItems,
                removes,
                tags,
                total
            } = changes;
        }

        done(PluginEventResult.error(event.id, new Error("Not implemented")));
    }

    destroy(event: DbPluginEvent, done: PluginEventCallbackResult<never>): void {
        // Cleanup resources/connections
        done(PluginEventResult.success(event.id));
    }
}

Key behaviors

  • Respect schema metadata passed by collections (keys, indexes, nullable/optional/defaults).
  • Compute fields marked as computed() after persistence; persist when tracked().
  • For identity() columns, return generated values so entities can be updated in memory before saveChangesAsync() resolves.
  • For distinct()/index() ensure unique or indexed storage if supported by the backend.

Separation for single-collection datastores

If your datastore persists all entities into one physical table/collection (e.g. PouchDB), add a tracked computed property to each schema that records its collection name. This guarantees clear separation between entity types and prevents cross‑collection collisions when fields share names (like name). See the tracked + computed example in the schema modifiers reference: Tracked computed. With SQL/SQLite backends, this is not an issue since data is already isolated per table.

Testing your plugin

  • Start with the Memory plugin behavior as a reference.
  • Use the CRUD how‑to pages to validate operations and saved changes.
  • Wire into an example app and run live queries to ensure incremental updates behave as expected.

Examples to study

Next steps

  • Expose configuration via your plugin constructor (database name, connection string, options).
  • Document installation and usage under Integrations → Plugins.