# Mental model (/docs/framework/guides/mental-model)



A few ideas make the rest of Kysoql much easier to reason about.

## Kysely inspires the API, not the query language [#kysely-inspires-the-api-not-the-query-language]

Kysoql borrows fluent, immutable TypeScript patterns from Kysely where they fit. It does not try to turn SOQL into SQL.

| Familiar idea                                                 | Kysoql behaviour                                                                       |
| ------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| `selectFrom`, `select`, `where`, `orderBy`, `limit`, `offset` | Similar fluent names where SOQL has a matching concept.                                |
| SQL joins                                                     | Use Salesforce parent paths, child subqueries, and supported semi-joins or anti-joins. |
| SQL placeholders                                              | Ordinary API values are escaped directly into compiled SOQL.                           |
| Mutations and transactions                                    | Outside the scope of a SOQL query builder.                                             |
| Kysely plugins                                                | Kysoql has its own `KysoqlPlugin` contract.                                            |
| Raw SQL                                                       | `soql.raw()` is Kysoql's explicit unsafe SOQL escape hatch.                            |

Apex binds are different from ordinary API values. They are available only in the compile-only Apex builders.

## Your schema describes the org to TypeScript [#your-schema-describes-the-org-to-typescript]

`new Kysoql<YourSchema>()` uses the schema type to constrain what you can build. The usual schema comes from `@kysoql/codegen` and reflects Salesforce metadata such as fields, relationships, nullability, and query capabilities.

The TypeScript type itself is compile-time metadata. For features that need runtime metadata, such as Big Object index validation, pass the generated `salesforceSchemaMetadata` value as shown in the codegen guide.

Do not use the broad helper types from `@kysoql/core` as a replacement for an org-specific generated schema. They cannot know your custom objects or fields.

## Builders are immutable [#builders-are-immutable]

Every query method returns a new builder:

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

const base = db.selectFrom("Account").select(["Id", "Name"]);
const acme = base.where("Name", "like", "Acme%");
const firstPage = acme.orderBy("Name").limit(25);

console.log(firstPage.compile().soql);
```

`base` still has no `WHERE`, `ORDER BY`, or `LIMIT`. That makes it safe to derive several queries from one starting point.

## Query modes narrow the API [#query-modes-narrow-the-api]

Kysoql changes the available methods as the query shape becomes more specific.

| Mode                  | How you enter it                                        | Result or purpose                             |
| --------------------- | ------------------------------------------------------- | --------------------------------------------- |
| Record query          | Select normal fields or supported SELECT functions      | Readonly array of selected records.           |
| Aggregate query       | Select an aggregate or group first                      | Readonly array of aggregate rows.             |
| Scalar count          | Select bare `fn.count()`                                | One `number`.                                 |
| Relationship subquery | Use `selectSubquery()`                                  | Nested relationship result in the parent row. |
| Semi-join             | Use an `in` or `not in` subquery callback               | A filter only.                                |
| Apex                  | Call `apex()` or `dynamicApex()` at the supported point | Compile-only SOQL.                            |

Methods disappear when they would no longer describe valid SOQL. A bare count, for example, does not expose the same pagination API as a normal record query.

## Result types follow selections [#result-types-follow-selections]

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

const query = db
  .selectFrom("Account")
  .select(["Id", "Name as name", "Owner.Name"]);
```

The result type contains only what you selected.

Generated nullability carries through to field and relationship types. A field added inside `$if` becomes optional because it might not be selected at runtime. Aliases create the aliased property name in the result.

The bundled executors validate the Salesforce response envelope, not every selected field value at runtime. Types are not an authorization or runtime validation layer. See [security and production use](/docs/core/reference/security).

## Compile or execute [#compile-or-execute]

`compile().soql` gives you the final SOQL string.

`execute()` needs a configured executor. The bundled executors follow root pagination for you. `.executeAll()` switches to Salesforce QueryAll mode rather than meaning "fetch all pages".

Use [native REST execution](/docs/rest/execution) for the normal transport and [compile for Apex](/docs/core/apex/overview) for Apex-only SOQL.

## About the examples [#about-the-examples]

Most guide snippets import `db` from the [getting started guide](/docs/framework/getting-started).

Generate every object and field an example uses before running it. Names such as `Office__c` and `Interests__c` are illustrative custom metadata and should be replaced with API names from your org.
