Writing a driver
Four steps. The compiler enforces the last one, because DRIVERS is a closed
Record<DriverEnum, DriverDefinition> — add an enum member without registering it and the build
fails.
1. Add the enum member
Section titled “1. Add the enum member”export enum DriverEnum { // … MY_BACKEND = 'my-backend',}2. Write the request strategy
Section titled “2. Write the request strategy”Extend AbstractRequestStrategy and override one hook. buildUri() handles resource assertion and
joining; parts() returns the query-string segments.
import { AbstractRequestStrategy } from '@qubeejs/core';
export class MyBackendRequestStrategy extends AbstractRequestStrategy { public readonly capabilities: StrategyCapabilities = { embedded: false, fields: false, filters: true, includes: false, operatorFilters: false, search: false, select: true, sort: true, };
protected parts(state: QueryBuilderState, options: QueryBuilderOptions): string[] { const out: string[] = [];
Object.entries(state.filters).forEach(([field, values]) => { out.push(`${field}=${values.join(',')}`); });
out.push(`${options.page}=${state.page}`, `${options.limit}=${state.limit}`);
return out; }}Declare capabilities honestly. It is what makes the builder throw a useful error instead of
emitting something the server ignores.
Leave state.params alone. QueryBuilder.generateUri() appends those after buildUri() returns,
for every strategy, and throws ParamCollisionError if one names a key your strategy emitted.
3. Write the response strategy
Section titled “3. Write the response strategy”If the envelope is flat, extend AbstractFlatResponseStrategy and you are done — the base reads
every field by name from ResponseOptions:
export class MyBackendResponseStrategy extends AbstractFlatResponseStrategy {}If it is nested, extend AbstractDotPathResponseStrategy, which resolves dotted paths like
meta.pagination.total. If it is neither, implement IResponseStrategy directly.
4. Register it
Section titled “4. Register it”export const MY_BACKEND_DRIVER: DriverDefinition = { id: 'my-backend', createRequestStrategy: () => new MyBackendRequestStrategy(), createResponseStrategy: () => new MyBackendResponseStrategy(), createResponseOptions: (config) => new ResponseOptions(config),};Then one line in DRIVERS, and a named export from src/index.ts.
Keeping a driver in your own code
Section titled “Keeping a driver in your own code”The four steps above add a driver to this repository. A driver can equally live in your own
application: export a DriverDefinition and pass it to createQubee(). Give it an id of its own
so capability errors name it, rather than the built-in driver it started from:
import { createQubee, JsonApiRequestStrategy, JsonApiResponseOptions, JsonApiResponseStrategy, type DriverDefinition,} from '@qubeejs/core';
class StudioApiRequestStrategy extends JsonApiRequestStrategy { // override what this backend does differently}
export const STUDIO_API_DRIVER: DriverDefinition = { id: 'studio-api', createRequestStrategy: () => new StudioApiRequestStrategy(), createResponseStrategy: () => new JsonApiResponseStrategy(), createResponseOptions: (config) => new JsonApiResponseOptions(config),};
const { builder } = createQubee({ driver: STUDIO_API_DRIVER });
builder.setSearch('jazz');// UnsupportedSearchError: The 'studio-api' driver does not support full-text search.id is a DriverId: any string, while editors still suggest the twenty built-in ids. Only those
resolve through DRIVERS, so a driver of your own is always passed as its definition.
Serialising nested structures
Section titled “Serialising nested structures”Use the bundled stringify() rather than URLSearchParams, which percent-encodes brackets with no
opt-out:
import { stringify } from '@qubeejs/core';
stringify({ filters: { status: { $eq: 'published' } } });// filters[status][$eq]=publishedstringify() does not encode, and does not need to: AbstractRequestStrategy.buildUri() hands
parts() a state whose filter values, operator values and search term are already
percent-encoded. Emit them as they arrive. Encoding them again produces %2520 where a space
should be, and anything your strategy adds around a value — operators, brackets, a joining , —
must stay literal.
