Kysoql
Reference

Custom executors and testing

Connect Kysoql to another transport or test queries without a live Salesforce org.

Most applications can use @kysoql/rest or @kysoql/jsforce. Build a custom executor when you already have a transport layer or need tighter control over execution.

Executor contract

Only executeQuery is required:

import type {
  AbortableQueryOptions,
  CompiledQuery,
} from "@kysoql/core";

interface ExampleExecutor {
  executeQuery<O>(
    query: CompiledQuery<O>,
    options?: AbortableQueryOptions,
  ): Promise<readonly O[]>;

  executeAllQuery?<O>(
    query: CompiledQuery<O>,
    options?: AbortableQueryOptions,
  ): Promise<readonly O[]>;

  executeCountQuery?(
    query: CompiledQuery<number>,
    options?: AbortableQueryOptions,
  ): Promise<number>;

  executeAllCountQuery?(
    query: CompiledQuery<number>,
    options?: AbortableQueryOptions,
  ): Promise<number>;
}

Expose an optional method only when the transport genuinely supports it. Kysoql will not silently turn QueryAll into a normal query or reinterpret record arrays as scalar counts.

A custom executor owns authentication, network calls, paging, response validation, and transport errors. The generic output type is erased at runtime, so casting arbitrary JSON to O[] is not validation.

Wrap an existing executor

Wrappers are useful for tracing, metrics, or policy checks. Preserve optional capabilities instead of replacing missing methods with fake fallbacks:

import type { QueryExecutor } from "@kysoql/core";

export function timedExecutor(
  inner: QueryExecutor,
  observe: (method: string, milliseconds: number) => void,
): QueryExecutor {
  const wrapped: QueryExecutor = {
    async executeQuery(query, options) {
      const started = performance.now();
      try {
        return await inner.executeQuery(query, options);
      } finally {
        observe("executeQuery", performance.now() - started);
      }
    },
  };

  if (inner.executeCountQuery) {
    wrapped.executeCountQuery = inner.executeCountQuery.bind(inner);
  }

  if (inner.executeAllQuery) {
    wrapped.executeAllQuery = inner.executeAllQuery.bind(inner);
  }

  if (inner.executeAllCountQuery) {
    wrapped.executeAllCountQuery = inner.executeAllCountQuery.bind(inner);
  }

  return wrapped;
}

Avoid logging raw tokens or query text when SOQL literals may contain sensitive values.

Test without Salesforce

For most builder tests, compile the query and assert the SOQL. For adapter tests, inject a fake fetch or a small executor that returns the exact response your test needs.

import assert from "node:assert/strict";
import { Kysoql } from "@kysoql/core";
import { createRestExecutor } from "@kysoql/rest";
import type { SalesforceSchema } from "./kysoql/salesforce.generated";

const fetch: typeof globalThis.fetch = async () =>
  Response.json({
    done: true,
    totalSize: 1,
    records: [{ Id: "001000000000001AAA", Name: "Example account" }],
  });

const db = new Kysoql<SalesforceSchema>({
  executor: createRestExecutor({
    instanceUrl: "https://example.my.salesforce.com",
    accessToken: "test-only-token",
    fetch,
  }),
});

const rows = await db.selectFrom("Account").select(["Id", "Name"]).limit(1).execute();
assert.equal(rows[0]?.Name, "Example account");

Add transport-focused cases for pagination, malformed responses, count results, QueryAll, abort signals, and error propagation when those behaviours matter to your executor.

Custom compilers

DefaultQueryCompiler is the normal compiler. Replacing it is an advanced extension point for callers that deliberately own their query serialization. A custom compiler is responsible for preserving the escaping and structural validation guarantees your application depends on.

On this page