# PB 0.39.1 JS Migration Quirks

Collected during SPQ-v2 Phase 4 implementation (2026-07-07).

## API Rule Validation Ordering Bug

**Symptom:** `listRule` / `viewRule` / `createRule` that reference custom fields fail with `invalid left operand "foo" - unknown field "foo"` even when the field IS in the schema.

**Root cause:** PB 0.39 validates API rules against its internal schema snapshot at rule-set time. If you create a collection AND set rules in the same `migrate()` call, the rules validator is running against the PRE-COMMIT snapshot (which doesn't have the new fields yet).

**What does NOT work (all three tried, all failed):**
1. Saving with empty rules, then setting rules on the same object + `app.save()` again — FAILS
2. Saving with empty rules, then `app.findCollectionByNameOrId()`, setting rules on fetched object + `app.save()` — FAILS
3. Separate migration file (M27 — after M25/M26 committed) — STILL FAILS

**The rules validator always runs against the pre-commit snapshot.** The only workaround for single-shop deployments: use empty rules (`''`) and rely on frontend filtering + `createRule: null` for write protection on server-side hook-only collections.

**When you need scoped rules:** Use the PocketBase admin UI after deployment, not JS migrations.

## Missing Field Constructors

| Wanted | Use Instead |
|--------|-------------|
| `DateTimeField` | `TextField` — store ISO strings |
| `DateField` | `TextField` — store ISO strings |

Available constructors (confirmed working in PB 0.39.1):
- `TextField`
- `EmailField`
- `SelectField`
- `JSONField`
- `BoolField`
- `NumberField` (likely, not confirmed)

## JS Runtime Support

PB 0.39.1 uses Goja (Go-based ES runtime). Confirmed working:
- ✅ `const`, `let`, `var`
- ✅ Template literals `` `hello ${world}` ``
- ✅ Arrow functions `(x) => x + 1`
- ✅ `Object.keys()`, `Array.forEach()`, `JSON.stringify()`
- ✅ `new Collection({ name, type, schema: [...] })`
- ✅ `new Record(collection, data)`

## Hook Event Names

| Correct API (PB 0.39) | Names that DON'T exist |
|------------------------|------------------------|
| `app.onRecordCreateRequest((e) => { ... })` | — |
| `app.onRecordUpdateRequest((e) => { ... })` | `onRecordBeforeUpdateRequest`, `onRecordAfterUpdateRequest` |
| `app.onRecordDeleteRequest((e) => { ... })` | — |

Only the three "Request" hooks exist — there are no "Before"/"After" variants.

## Record Access in Hooks

- `e.record` — the record being saved (in update hooks, this is the NEW data)
- `e.oldRecord` — available in update hooks, the current DB state before the update
- `e.dao` — data access object for querying other collections
- `e.httpContext` — access to the HTTP request (query params, headers)

## Creating Records Inside Hooks

```js
var collection = app.findCollectionByNameOrId('myCollection');
var record = new Record(collection, {
  field1: 'value1',
  field2: 'value2',
});
e.dao.saveRecord(record);
```

## Public Share-Token Access Pattern

To detect if a request is using the public share-token path (vs. authenticated API):
```js
var shareToken = e.httpContext?.request?.query?.get('shareToken') || '';
if (shareToken) {
  // This is a public (unauthenticated) update via share link
  // Enforce expiry, restrict fields, etc.
}
```

## Mailer API (for sending email in hooks)

```js
var mailer = app.getMailer();
await mailer.send({
  to: [{ address: 'user@example.com', name: 'Name' }],
  subject: 'Subject',
  html: '<p>HTML body</p>',
});
```

Requires SMTP env vars set on the PB container (PB will crash on startup without valid `PB_SMTP_*` vars once mailer is used).
