Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .changeset/import-dryrun-hook-boundary-docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
"@objectstack/spec": patch
---

docs(spec): `ImportRequest.dryRun` states the boundary an import preview has — no automations run (#6537)

An import dry run routes through the engine's own write-path validation
(`DataProtocol.validateData`, #6037 / #4633 ruling D) and applies no
`runAutomations` gate, because gating it would leave the common dry run with no
validation at all — the false all-clear that work set out to close. The engine's
validate-only path deliberately runs **no hooks**: a preview that fired
user-authored side effects (mail, outbound calls, writes to other objects) would
be the retired `BatchOptions.validateOnly` defect (#4052) in a new spelling.

The consequence an author can meet is narrow and, until now, written down only in
the engine's and the import runner's source comments: on an object whose
`beforeInsert` hook derives a **required business field**, a dry run with
`runAutomations: true` can report `required` for a row the real import would have
created. Audit and ownership stamps (`created_by`, `owner_id`,
`organization_id`, …) are `system`/`readonly` and skipped by validation anyway, so
they cannot produce this.

`ImportRequest.dryRun`'s description now says so, which puts it on the reference
page an author reads before sending the request — the same schema backs
`CreateImportJobRequest`, so the synchronous route and the async import job both
carry it. **Zero behaviour change**: one description string, and the
`content/docs/references/api/export.mdx` cells regenerated from it by
`gen:schema && gen:docs` (no hand edits).
4 changes: 2 additions & 2 deletions content/docs/references/api/export.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ const result = CreateExportJobRequestSchema.parse(data);
| **xlsxBase64** | `string` | optional | Base64-encoded .xlsx workbook bytes (when format = xlsx); parsed server-side |
| **sheet** | `string \| integer` | optional | Worksheet name or 1-based index to read (xlsx; defaults to the first sheet) |
| **mapping** | `Record<string, string> \| { sourceField: string; targetField: string; targetLabel?: string; transform: Enum<'none' \| 'uppercase' \| 'lowercase' \| 'trim' \| 'date_format' \| 'lookup'>; … }[]` | optional | Source column → target field mapping |
| **dryRun** | `boolean` | ✅ | Validate + coerce every row without persisting |
| **dryRun** | `boolean` | ✅ | Validate + coerce every row without persisting. The verdict is the engine's own write-path validation, with one boundary an author should know: a preview runs NO automations. Hooks never fire in a dry run (#6037) — a preview that executed user-authored side effects (mail, outbound calls, writes to other objects) would be the retired `validateOnly` defect in a new spelling. So a dry run with `runAutomations: true` can report `required` for a field a `beforeInsert` hook would populate during the real import; for hook-derived fields the real write is authoritative. |
| **writeMode** | `Enum<'insert' \| 'update' \| 'upsert'>` | ✅ | insert / update / upsert semantics |
| **matchFields** | `string[]` | optional | Fields that identify an existing record (required for update/upsert) |
| **runAutomations** | `boolean` | ✅ | Fire triggers/hooks for each imported row (off by default for bulk) |
Expand Down Expand Up @@ -365,7 +365,7 @@ Type: `{ sourceField: string; targetField: string; targetLabel?: string; transfo
| **xlsxBase64** | `string` | optional | Base64-encoded .xlsx workbook bytes (when format = xlsx); parsed server-side |
| **sheet** | `string \| integer` | optional | Worksheet name or 1-based index to read (xlsx; defaults to the first sheet) |
| **mapping** | `Record<string, string> \| { sourceField: string; targetField: string; targetLabel?: string; transform: Enum<'none' \| 'uppercase' \| 'lowercase' \| 'trim' \| 'date_format' \| 'lookup'>; … }[]` | optional | Source column → target field mapping |
| **dryRun** | `boolean` | ✅ | Validate + coerce every row without persisting |
| **dryRun** | `boolean` | ✅ | Validate + coerce every row without persisting. The verdict is the engine's own write-path validation, with one boundary an author should know: a preview runs NO automations. Hooks never fire in a dry run (#6037) — a preview that executed user-authored side effects (mail, outbound calls, writes to other objects) would be the retired `validateOnly` defect in a new spelling. So a dry run with `runAutomations: true` can report `required` for a field a `beforeInsert` hook would populate during the real import; for hook-derived fields the real write is authoritative. |
| **writeMode** | `Enum<'insert' \| 'update' \| 'upsert'>` | ✅ | insert / update / upsert semantics |
| **matchFields** | `string[]` | optional | Fields that identify an existing record (required for update/upsert) |
| **runAutomations** | `boolean` | ✅ | Fire triggers/hooks for each imported row (off by default for bulk) |
Expand Down
9 changes: 8 additions & 1 deletion packages/spec/src/api/export.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,14 @@ export const ImportRequestSchema = lazySchema(() => z.object({
mapping: ImportMappingSchema.optional()
.describe('Source column → target field mapping'),
dryRun: z.boolean().default(false)
.describe('Validate + coerce every row without persisting'),
.describe(
'Validate + coerce every row without persisting. The verdict is the engine\'s own write-path ' +
'validation, with one boundary an author should know: a preview runs NO automations. Hooks never ' +
'fire in a dry run (#6037) — a preview that executed user-authored side effects (mail, outbound ' +
'calls, writes to other objects) would be the retired `validateOnly` defect in a new spelling. So a ' +
'dry run with `runAutomations: true` can report `required` for a field a `beforeInsert` hook would ' +
'populate during the real import; for hook-derived fields the real write is authoritative.',
),
writeMode: ImportWriteMode.default('insert')
.describe('insert / update / upsert semantics'),
matchFields: z.array(z.string()).optional()
Expand Down
Loading