96 lines
3.6 KiB
Markdown
96 lines
3.6 KiB
Markdown
# 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).
|