Kysoql

Getting started

Install Kysoql, authenticate to Salesforce, generate a schema, and execute a typed REST query.

This guide takes you from a new TypeScript project to a typed Salesforce query using Kysoql's native REST transport.

Kysoql is ESM-only and currently requires Node.js >=22.12.0.

1. Install Kysoql

pnpm add @kysoql/core @kysoql/rest @kysoql/auth
pnpm add -D @kysoql/codegen

2. Configure Salesforce authentication

For a server-to-server integration, JWT bearer is a good default when your application can protect a private key.

salesforce-auth.ts
import { SalesforceAuth } from "@kysoql/auth";

const auth = new SalesforceAuth({
  loginUrl: "https://login.salesforce.com",
  clientId: "external-client-app-id",
});

export const getSalesforceSession = () =>
  auth.jwtBearer({
    username: "integration@example.com",
    privateKey: { type: "file", path: "./salesforce-auth-key.pem" },
  });

If the PEM comes from a secret manager, use { type: "pem", value }. If your application already has an RS256 CryptoKey, use { type: "crypto-key", key }.

See JWT bearer authentication for the Salesforce external client app setup.

3. Generate your Salesforce schema

Create kysoql.config.ts and list the objects your application will query:

kysoql.config.ts
import { defineConfig } from "@kysoql/codegen";
import { getSalesforceSession } from "./salesforce-auth";

export default defineConfig({
  auth: getSalesforceSession,
  objects: ["Account", "Contact"],
  schemaName: "SalesforceSchema",
});

Run the generator:

pnpm exec kysoql generate

If the project has a src directory, the generated file goes to src/kysoql/salesforce.generated.ts by default. Otherwise it goes to kysoql/salesforce.generated.ts.

The generated schema reflects the objects, fields, relationships, and capabilities visible to the Salesforce user you authenticated as.

4. Create a REST-backed database

Use the same authentication function as an access-token provider. The REST client keeps the current access token in memory and asks for a replacement if Salesforce reports that the session is invalid.

db.ts
import { Kysoql } from "@kysoql/core";
import { createRestExecutor } from "@kysoql/rest";
import { getSalesforceSession } from "./salesforce-auth";
import {
  salesforceSchemaMetadata,
  type SalesforceSchema,
} from "./kysoql/salesforce.generated";

let session = await getSalesforceSession();
const instanceUrl = session.instanceUrl;

const executor = createRestExecutor({
  instanceUrl,
  apiVersion: "65.0",
  accessToken: async ({ refresh }) => {
    if (refresh) {
      const next = await getSalesforceSession();

      if (next.instanceUrl !== instanceUrl) {
        throw new Error("Salesforce instance changed. Recreate the REST client.");
      }

      session = next;
    }

    return session.accessToken;
  },
});

export const db = new Kysoql<SalesforceSchema>({
  executor,
  schemaMetadata: salesforceSchemaMetadata,
});

JWT bearer does not use refresh tokens. When the access token expires, the provider performs another JWT exchange.

5. Run a typed query

query.ts
import { db } from "./db";

const accounts = await db
  .selectFrom("Account")
  .select(["Id", "Name"])
  .where("Name", "like", "Acme%")
  .orderBy("Name")
  .limit(25)
  .execute();

for (const account of accounts) {
  console.log(account.Id, account.Name);
}

Kysoql compiles the builder to:

SELECT Id, Name FROM Account WHERE Name LIKE 'Acme%' ORDER BY Name LIMIT 25

The result type follows the generated Salesforce schema and the fields you selected. No matching rows returns an empty array.

Next steps

On this page