Kysoql
Compile for Apex

Compile for Apex

Generate SOQL for static or dynamic Apex without mixing it with API execution.

Apex builders produce SOQL for code that runs inside Salesforce Apex. They do not execute Apex, deploy classes, or resolve Apex variables from TypeScript.

Enter Apex after shaping the query

import { apexBind } from "@kysoql/core";
import { db } from "./db";

const compiled = db
  .selectFrom("Account")
  .select(["Id", "Name"])
  .apex()
  .where("Id", "=", apexBind<string>("accountId"))
  .withUserMode()
  .compile();

console.log(compiled.soql);
// SELECT Id, Name FROM Account WHERE Id = :accountId WITH USER_MODE

Define accountId in the Apex caller. The TypeScript generic only checks that the bind is compatible with the field. It does not create or validate the Apex variable.

Finish ordinary selections, sorting, grouping, and HAVING before calling .apex() or .dynamicApex(). The Apex builder deliberately exposes a narrower set of methods after the switch.

Choose the access mode intentionally

withUserMode() emits WITH USER_MODE and withSystemMode() emits WITH SYSTEM_MODE. Calling one replaces the other.

Kysoql does not decide which mode is appropriate for your security model. The mode also shares Salesforce's filtering WITH slot with data categories, RecordVisibilityContext, and UserId.

Lock records

import { apexBind } from "@kysoql/core";
import { db } from "./db";

const locked = db
  .selectFrom("Account")
  .select(["Id", "Name"])
  .apex()
  .where("Id", "=", apexBind<string>("accountId"))
  .forUpdate()
  .compile();

forUpdate() is available on Apex record queries. It cannot be combined with ORDER BY or ALL ROWS. Salesforce controls the actual transaction and lock behaviour.

Include deleted records

import { db } from "./db";

const compiled = db
  .selectFrom("Account")
  .select(["Id", "Name"])
  .apex()
  .allRows()
  .compile();

allRows() is available on Apex record, aggregate, and count builders. For REST queries, use executeAll() instead.

Aggregate and count queries

Build the aggregate structure before entering Apex:

import { apexBind } from "@kysoql/core";
import { db } from "./db";

const report = db
  .selectFrom("Opportunity")
  .select(({ fn }) => fn.sum("Amount").as("totalAmount"))
  .groupBy("StageName")
  .select("StageName")
  .apex()
  .where("IsClosed", "=", apexBind<boolean>("closed"))
  .limit(apexBind<number>("rowLimit"))
  .compile();

The specialised Apex aggregate builders accept supported filters and binds but do not reopen the full aggregate-building surface after the context switch.

Static and dynamic Apex

Use .apex() for the static compile context. Use .dynamicApex() when you need the dynamic-only features Kysoql models, such as bound Database.QueryOptions.

Neither method is a JavaScript execution adapter. Your Apex code still owns bind values, access level, and the API used to execute the resulting SOQL.

Continue with Apex bindings or the Core API reference.

On this page