# Rule: Choose the Right DS Pattern Before Writing a Single Line

The `external` attribute is the single most impactful decision in any DataSource. Getting it wrong causes a
**runtime crash** that is invisible at build time and only surfaces when a user hits the affected screen.

---

## The `external` Attribute

| `external` | What the DS contains                             | Framework behavior                                      |
| ---------- | ------------------------------------------------ | ------------------------------------------------------- |
| `false`    | `<QueryCondition>`, `<Attributes>`, `<Entities>` | Framework auto-generates SQL from your declarations     |
| `true`     | `<Database><Load>` JavaScript block              | Framework calls your Load script; you return the result |

**Rule:** These two halves are mutually exclusive. Never put `<QueryCondition>` in an `external="true"` DS, and never put `<Database><Load>` in an `external="false"` DS.

---

## Declarative DS (`external="false"`)

Use for:

-   Single-record BO lookups (DsBo\* files are almost always declarative)
-   List queries with straightforward WHERE/ORDER BY clauses
-   Any query that filters by a known set of columns

Core structure:

```xml
<DataSource name="DsBo<Entity>" backendSystem="sf" businessObjectClass="Bo<Entity>"
            external="false" editableEntity="<Entity>" schemaVersion="2.0">
  <Attributes>
    <Attribute name="pKey" table="<Entity>" column="Id" />
    <!-- more <Attribute> / <DateTimeAttribute> / <DerivedAttribute> rows -->
  </Attributes>
  <Entities>
    <Entity name="<Entity>" alias="" idAttribute="Id" />
    <!-- optional joined entities -->
  </Entities>
  <QueryCondition><![CDATA[
    <Entity>.Id = #pKey#
  ]]></QueryCondition>
  <OrderCriteria>
    <OrderCriterion entity="<Entity>" attribute="Id" direction="ASC" />
  </OrderCriteria>
  <Parameters>
    <Parameter name="pKey" type="TEXT" />
  </Parameters>
</DataSource>
```

Real-file anchor: `src/Visit/DS/DsBoVisit_sf.datasource.xml`
(single Visit by pKey, joined to User via `Visit.VisitorId = User.Id`)

---

## Scripted DS (`external="true"` with `<Database><Load>`)

Use for:

-   Conditional WHERE clauses built at runtime
-   UNION queries
-   Dynamic joins or multi-branch SQL
-   Any query needing JavaScript logic before the SQL string is assembled

Core structure:

```xml
<DataSource name="DsLo<Name>" backendSystem="sf" businessObjectClass="Lo<Name>"
            external="true" editableEntity="<Entity>" schemaVersion="2.0">
  <Attributes>
    <!-- same <Attribute> / <DerivedAttribute> declarations -->
  </Attributes>
  <Entities>
    <Entity name="<Entity>" alias="" idAttribute="Id" />
  </Entities>
  <Database platform="SQLite">
    <Load><![CDATA[
      var param = Utils.convertForDBParam(jsonQuery.myParam, "DomPKey");
      var sql = "SELECT <Entity>.Id AS pKey FROM <Entity> ";
      sql    += "WHERE <Entity>.SomeField = #param# ";
      var sqlParams = {param};
      return Utils.replaceMacrosParam(sql, sqlParams);
    ]]></Load>
  </Database>
</DataSource>
```

Real-file anchor: `src/Visit/DS/DsLoVisit_sf.datasource.xml`
(scripted; builds dynamic date-range + status conditions at runtime)

---

## Context-Menu / Empty DS (`external="true"` with trivial Load)

Use for:

-   Any LO that gets its rows populated by `.bl.js` code, not a database query
-   Right-click / context menus
-   BL-assembled option lists

The Load block must return `undefined`. The framework will treat the LO as empty on load; BL code then pushes items in.

```xml
<DataSource name="DsLo<MenuName>ContextMenu" backendSystem="sf"
            businessObjectClass="Lo<MenuName>ContextMenu"
            external="true" editableEntity="" schemaVersion="2.0">
  <Database>
    <Load><![CDATA[
      // Intended to be empty - This list object gets its items by business logic!
      return undefined;
    ]]></Load>
    <Update><![CDATA[ return undefined; ]]></Update>
    <Insert><![CDATA[ return undefined; ]]></Insert>
    <Delete><![CDATA[ return undefined; ]]></Delete>
  </Database>
</DataSource>
```

Real-file anchor: `src/Visit/DS/DsLoVisitOverviewContextMenu_sf.datasource.xml`

---

## Symptom → Cause → Fix

| Symptom                                                | Cause                                                              | Fix                                                      |
| ------------------------------------------------------ | ------------------------------------------------------------------ | -------------------------------------------------------- |
| `Cannot read properties of undefined (reading 'Load')` | Declarative DS has `external="true"`                               | Change to `external="false"`                             |
| `SQLITE_ERROR: no such column`                         | Column missing from `app.db3`                                      | Run `verify-sqlite-schema`; remove or replace the column |
| Screen loads but list is always empty                  | Scripted DS returns `undefined` instead of `{sql, params}`         | Check the Load block's return statement                  |
| Saves succeed but DB row unchanged                     | Declarative DS missing `editableEntity` or has `editableEntity=""` | Set `editableEntity="<EntityName>"`                      |
| Build passes but data loads in wrong order             | `OrderCriterion` missing `direction` attribute                     | Add `direction="ASC"` or `direction="DESC"`              |
