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
34 changes: 34 additions & 0 deletions .changeset/filter-operator-schema-projection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
'@objectstack/spec': patch
---

fix(spec): project a union branch-by-branch, so five filter operators reach a published reference page

`z.toJSONSchema()` refuses a whole schema the moment ONE node in it has no JSON
form, and `build-schemas.ts` applied that refusal per SCHEMA. `orderingComparandSchema`
is `z.union([z.number(), z.date(), z.string(), FieldReferenceSchema])`, so four
`data/filter.zod.ts` exports emitted nothing at all — and `$gt`, `$gte`, `$lt`,
`$lte` and `$between` reached no reference row. Not a blank Description cell: no
section. The ~2000 characters of `.describe()` on those slots — the #5685 comparand
contract, the #6571 endpoint contract, and the `{ "$gte": "2026-01-01" }` shape the
platform's own date-macro resolver produces — reached no reader.

The generator now makes a third attempt when both strict directions refuse: it
projects with Zod's `unrepresentable: 'any'`, marks every node that came back with
no structural keyword, and DROPS the marked ones that are direct members of an
`anyOf` / `oneOf`. That is not a narrowing. These artifacts describe JSON
documents, a JSON document cannot carry a `Date` INSTANCE, so the set of JSON
documents that union accepts is unchanged by the drop.

⛔ A marked node anywhere else — an object property, a record value, an array item
— refuses the projection and the export is skipped with the message Zod threw, so
this cannot change WHY anything is skipped. Five exports leave
`unemitted-schemas.baseline.json` (23 → 18): the four filter exports, plus
`data/Hook`, whose only unprojectable member was the deprecated inline-function
handler branch — that puts 22 `data/Hook:` authorable keys under the key ratchet
for the first time.

Published artifacts gain `json-schema/data/{ComparisonOperator,FieldOperators,
NormalizedFilter,RangeOperator,Hook}.json`, each carrying an
`x-unprojectable-branches` record naming exactly which branch the projection
dropped and where.
197 changes: 194 additions & 3 deletions content/docs/references/data/filter.mdx

Large diffs are not rendered by default.

66 changes: 63 additions & 3 deletions content/docs/references/data/hook.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,73 @@ description: Hook protocol schemas
## TypeScript Usage

```typescript
import { HookContextSchema, HookEvent } from '@objectstack/spec/data';
import type { HookContext } from '@objectstack/spec/data';
import { HookSchema, HookContextSchema, HookEvent } from '@objectstack/spec/data';
import type { Hook, HookContext } from '@objectstack/spec/data';

// Validate data
const result = HookContextSchema.parse(data);
const result = HookSchema.parse(data);
```

---

## Hook

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **name** | `string` | ✅ | Hook unique name (snake_case) |
| **label** | `string` | optional | Description of what this hook does |
| **object** | `string \| string[]` | ✅ | Target object(s) |
| **events** | `Enum<'beforeFind' \| 'afterFind' \| 'beforeInsert' \| 'afterInsert' \| 'beforeUpdate' \| 'afterUpdate' \| 'beforeDelete' \| 'afterDelete'>[]` | ✅ | Lifecycle events |
| **handler** | `string` | optional | Handler function name (string, post-build) or inline function (pre-build) — DEPRECATED, prefer `body` |
| **body** | `{ language: 'expression'; source: string } \| { language: 'js'; source: string; capabilities?: Enum<'api.read' \| 'api.write' \| 'api.transaction' \| 'crypto.uuid' \| 'log'>[]; timeoutMs?: integer; … }` | optional | Hook body — expression (L1) or sandboxed JS (L2) |
| **priority** | `number` | optional (default: `100`) | Execution priority |
| **async** | `boolean` | optional (default: `false`) | Run specifically as fire-and-forget |
| **condition** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }` | optional | Predicate (CEL); hook runs only when TRUE. e.g. P`record.status == "closed" && record.amount > 1000` |
| **description** | `string` | optional | Human-readable description of what this hook does |
| **retryPolicy** | `{ maxRetries?: number; backoffMs?: number }` | optional | Retry policy for failed hook executions |
| **timeoutMs** | `number` | optional | Maximum execution time in milliseconds before the hook is aborted |
| **timeout** | `never` | optional | [REMOVED] `hook.timeout` was removed in @objectstack/spec 17 — its unit (milliseconds) lived only in the description, beside a body-level `timeoutMs` and a `retryPolicy.backoffMs` that spell theirs, so the same number read as two conventions on one surface. Rename the key to `timeoutMs`; the value (milliseconds) is unchanged. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. |
| **onError** | `Enum<'abort' \| 'log'>` | optional (default: `"abort"`) | Error handling strategy |
| **runAs** | `Enum<'system' \| 'user' \| 'inherit'>` | optional (default: `"inherit"`) | Execution identity for the hook's ctx.api data operations: system = elevated (bypasses RLS), user = the triggering user (RLS-respecting), inherit = the context of the write that fired the hook (the pre-runAs behaviour; the default). A hook with no trigger user has no identity to scope to, so under user its ctx.api data operations are REFUSED — declare system to make the elevation explicit. This covers any hook fired by a write that carried no user (an isSystem plugin/service write; a system-elevated flow node). Scope: ctx.api only — condition evaluation, the readonly strip on ctx.input, ctx.session and async are unchanged. |
| **_lock** | `Enum<'none' \| 'no-overlay' \| 'no-delete' \| 'full'>` | optional | Item-level lock — controls overlay & delete (ADR-0010). |
| **_lockReason** | `string` | optional | Human-readable reason shown when a write is refused by _lock. |
| **_lockSource** | `Enum<'artifact' \| 'package' \| 'env-forced'>` | optional | Layer that set _lock (artifact \| package \| env-forced). |
| **_provenance** | `Enum<'package' \| 'org' \| 'env-forced'>` | optional | Origin of the item (package \| org \| env-forced). |
| **_packageId** | `string` | optional | Owning package machine id. |
| **_packageVersion** | `string` | optional | Owning package version. |
| **_lockDocsUrl** | `string` | optional | Optional documentation link surfaced next to _lockReason. |

### Nested Shape: `Hook.body[language='expression']`

L1 expression body — pure formula, no IO

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **language** | `'expression'` | ✅ | |
| **source** | `string` | ✅ | Formula expression source |

### Nested Shape: `Hook.body[language='js']`

L2 sandboxed JS body — runs inside an isolated VM with declared capabilities

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **language** | `'js'` | ✅ | |
| **source** | `string` | ✅ | Function body source |
| **capabilities** | `Enum<'api.read' \| 'api.write' \| 'api.transaction' \| 'crypto.uuid' \| 'log'>[]` | optional (default: `[]`) | Granted capability tokens |
| **timeoutMs** | `integer` | optional | Per-invocation timeout (ms) |
| **memoryMb** | `integer` | optional | Per-invocation memory cap (MB) |

### Nested Shape: `Hook.retryPolicy`

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **maxRetries** | `number` | optional (default: `3`) | Maximum retry attempts on failure |
| **backoffMs** | `number` | optional (default: `1000`) | Backoff delay between retries in milliseconds |


---

## HookContext
Expand Down
12 changes: 6 additions & 6 deletions content/docs/references/index.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Protocol Reference
description: Every schema published by @objectstack/spec — 1577 schemas across 14 protocol modules
description: Every schema published by @objectstack/spec — 1582 schemas across 14 protocol modules
---

{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
Expand All @@ -23,7 +23,7 @@ counts are sums of the rows they head. Regenerate with
| [API Protocol](/docs/references/api) | 31 | 437 | REST contracts, endpoints, routing, realtime, batch, discovery. |
| [Automation Protocol](/docs/references/automation) | 13 | 73 | Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. |
| [Cloud Protocol](/docs/references/cloud) | 11 | 94 | Environments, packages and versions, marketplace, developer portal, tenancy. |
| [Data Protocol](/docs/references/data) | 29 | 168 | Objects, fields, queries, filters, datasources and drivers — the ObjectQL layer. |
| [Data Protocol](/docs/references/data) | 29 | 173 | Objects, fields, queries, filters, datasources and drivers — the ObjectQL layer. |
| [Identity Protocol](/docs/references/identity) | 5 | 27 | Users and accounts, organizations, positions, SCIM provisioning. |
| [Integration Protocol](/docs/references/integration) | 1 | 24 | The single connector protocol (ADR-0097) — catalog descriptors and provider-bound instances. |
| [Kernel Protocol](/docs/references/kernel) | 30 | 162 | Plugin lifecycle and manifests, capabilities and security, metadata loading, service registry. |
Expand All @@ -33,7 +33,7 @@ counts are sums of the rows they head. Regenerate with
| [Studio Protocol](/docs/references/studio) | 3 | 35 | Studio designer metadata — the authoring surfaces for the protocols above. |
| [System Protocol](/docs/references/system) | 33 | 272 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. |
| [UI Protocol](/docs/references/ui) | 16 | 153 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. |
| **Total** | **198** | **1577** | 14 protocol modules |
| **Total** | **198** | **1582** | 14 protocol modules |

---

Expand Down Expand Up @@ -149,7 +149,7 @@ Environments, packages and versions, marketplace, developer portal, tenancy.

## Data Protocol

**Source:** `packages/spec/src/data/` · **Import:** `@objectstack/spec/data` · **29 pages, 168 schemas**
**Source:** `packages/spec/src/data/` · **Import:** `@objectstack/spec/data` · **29 pages, 173 schemas**

Objects, fields, queries, filters, datasources and drivers — the ObjectQL layer.

Expand All @@ -175,8 +175,8 @@ Objects, fields, queries, filters, datasources and drivers — the ObjectQL laye
| [`feed.zod.ts`](/docs/references/data/feed) | `FeedFilterMode`, `FeedItemType` |
| [`field.zod.ts`](/docs/references/data/field) | `CurrencyConfig`, `CurrencyValue`, `Field`, `FieldMaskingKeep`, `FieldMaskingRule`, `FieldType`, `InlineGridColumn`, `LocationCoordinates`, `SelectOption`, `UniqueScope` |
| [`field-value.zod.ts`](/docs/references/data/field-value) | `Address`, `AddressValue`, `CalendarDateValue`, `ClockTimeValue`, `FileLikeValue`, `FileReferenceIdValue`, `FileValue`, `InstantValue`, `LocationValue`, `ReferenceIdValue` |
| [`filter.zod.ts`](/docs/references/data/filter) | `EqualityOperator`, `FieldReference`, `FilterArray`, `FilterCondition`, `QueryFilter`, `SetOperator`, `SpecialOperator`, `StringOperator` |
| [`hook.zod.ts`](/docs/references/data/hook) | `HookContext`, `HookEvent` |
| [`filter.zod.ts`](/docs/references/data/filter) | `ComparisonOperator`, `EqualityOperator`, `FieldOperators`, `FieldReference`, `FilterArray`, `FilterCondition`, `NormalizedFilter`, `QueryFilter`, `RangeOperator`, `SetOperator`, `SpecialOperator`, `StringOperator` |
| [`hook.zod.ts`](/docs/references/data/hook) | `Hook`, `HookContext`, `HookEvent` |
| [`hook-body.zod.ts`](/docs/references/data/hook-body) | `ExpressionBody`, `HookBody`, `HookBodyCapability`, `ScriptBody` |
| [`mapping.zod.ts`](/docs/references/data/mapping) | `ImportFieldMapping`, `Mapping`, `TransformType` |
| [`object.zod.ts`](/docs/references/data/object) | `ApiMethod`, `ApiOperation`, `Index`, `Lifecycle`, `LifecycleClass`, `Object`, `ObjectAccessConfig`, `ObjectCapabilities`, `ObjectExtension`, `ObjectExternalBinding`, `ObjectFieldGroup`, `ObjectOwnershipEnum`, `ObjectRequiredPermissions`, `PerOperationRequiredPermissions`, `RowCrudActionOverride`, `TenancyConfig` |
Expand Down
4 changes: 4 additions & 0 deletions packages/spec/authorable-defaults/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@
"data/FullTextSearch:fuzzy = false",
"data/FullTextSearch:highlight = false",
"data/FullTextSearch:operator = \"or\"",
"data/Hook:async = false",
"data/Hook:onError = \"abort\"",
"data/Hook:priority = 100",
"data/Hook:runAs = \"inherit\"",
"data/ImportFieldMapping:transform = \"none\"",
"data/Index:unique = false",
"data/JSONValidation:active = true",
Expand Down
48 changes: 48 additions & 0 deletions packages/spec/authorable-surface/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@
"data/AutoPersistenceConfig:path",
"data/AutoPersistenceConfig:type",
"data/BaseEngineOptions:context",
"data/ComparisonOperator:$gt",
"data/ComparisonOperator:$gte",
"data/ComparisonOperator:$lt",
"data/ComparisonOperator:$lte",
"data/ConditionalValidation:_lock",
"data/ConditionalValidation:_lockDocsUrl",
"data/ConditionalValidation:_lockReason",
Expand Down Expand Up @@ -415,6 +419,24 @@
"data/Field:widget",
"data/FieldMaskingKeep:keepHead",
"data/FieldMaskingKeep:keepTail",
"data/FieldOperators:$between",
"data/FieldOperators:$contains",
"data/FieldOperators:$endsWith",
"data/FieldOperators:$eq",
"data/FieldOperators:$exists",
"data/FieldOperators:$gt",
"data/FieldOperators:$gte",
"data/FieldOperators:$icontains",
"data/FieldOperators:$ilike",
"data/FieldOperators:$in",
"data/FieldOperators:$like",
"data/FieldOperators:$lt",
"data/FieldOperators:$lte",
"data/FieldOperators:$ne",
"data/FieldOperators:$nin",
"data/FieldOperators:$notContains",
"data/FieldOperators:$null",
"data/FieldOperators:$startsWith",
"data/FieldReference:$field",
"data/FieldReference:addDays",
"data/FilePersistenceConfig:autoSaveInterval [RETIRED]",
Expand Down Expand Up @@ -455,6 +477,28 @@
"data/FullTextSearch:minScore",
"data/FullTextSearch:operator",
"data/FullTextSearch:query",
"data/Hook:_lock",
"data/Hook:_lockDocsUrl",
"data/Hook:_lockReason",
"data/Hook:_lockSource",
"data/Hook:_packageId",
"data/Hook:_packageVersion",
"data/Hook:_provenance",
"data/Hook:async",
"data/Hook:body",
"data/Hook:condition",
"data/Hook:description",
"data/Hook:events",
"data/Hook:handler",
"data/Hook:label",
"data/Hook:name",
"data/Hook:object",
"data/Hook:onError",
"data/Hook:priority",
"data/Hook:retryPolicy",
"data/Hook:runAs",
"data/Hook:timeout [RETIRED]",
"data/Hook:timeoutMs",
"data/HookContext:api",
"data/HookContext:dispatch",
"data/HookContext:event",
Expand Down Expand Up @@ -618,6 +662,9 @@
"data/NoSQLTransactionOptions:readConcern",
"data/NoSQLTransactionOptions:readPreference",
"data/NoSQLTransactionOptions:writeConcern",
"data/NormalizedFilter:$and",
"data/NormalizedFilter:$not",
"data/NormalizedFilter:$or",
"data/Object:_lock",
"data/Object:_lockDocsUrl",
"data/Object:_lockReason",
Expand Down Expand Up @@ -736,6 +783,7 @@
"data/Query:where",
"data/Query:windowFunctions [RETIRED]",
"data/QueryFilter:where",
"data/RangeOperator:$between",
"data/ReferenceResolution:field",
"data/ReferenceResolution:fieldType",
"data/ReferenceResolution:multiple",
Expand Down
8 changes: 8 additions & 0 deletions packages/spec/declaration-map/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"CalendarDateValueSchema": "data/CalendarDateValue",
"ClockTimeValue": "data/ClockTimeValue",
"ClockTimeValueSchema": "data/ClockTimeValue",
"ComparisonOperatorSchema": "data/ComparisonOperator",
"ConditionalValidation": "data/ConditionalValidation",
"ConditionalValidationSchema": "data/ConditionalValidation",
"ConsistencyLevel": "data/ConsistencyLevel",
Expand Down Expand Up @@ -145,6 +146,8 @@
"FieldMaskingRuleSchema": "data/FieldMaskingRule",
"FieldNode": "data/FieldNode",
"FieldNodeSchema": "data/FieldNode",
"FieldOperators": "data/FieldOperators",
"FieldOperatorsSchema": "data/FieldOperators",
"FieldReference": "data/FieldReference",
"FieldReferenceSchema": "data/FieldReference",
"FieldSchema": "data/Field",
Expand All @@ -167,12 +170,14 @@
"FullTextSearchSchema": "data/FullTextSearch",
"GroupByNode": "data/GroupByNode",
"GroupByNodeSchema": "data/GroupByNode",
"Hook": "data/Hook",
"HookBody": "data/HookBody",
"HookBodyCapability": "data/HookBodyCapability",
"HookBodySchema": "data/HookBody",
"HookContext": "data/HookContext",
"HookContextSchema": "data/HookContext",
"HookEvent": "data/HookEvent",
"HookSchema": "data/Hook",
"ImportFieldMapping": "data/ImportFieldMapping",
"ImportFieldMappingSchema": "data/ImportFieldMapping",
"IndexSchema": "data/Index",
Expand Down Expand Up @@ -216,6 +221,8 @@
"NoSQLQueryOptionsSchema": "data/NoSQLQueryOptions",
"NoSQLTransactionOptions": "data/NoSQLTransactionOptions",
"NoSQLTransactionOptionsSchema": "data/NoSQLTransactionOptions",
"NormalizedFilter": "data/NormalizedFilter",
"NormalizedFilterSchema": "data/NormalizedFilter",
"ObjectAccessConfig": "data/ObjectAccessConfig",
"ObjectAccessConfigSchema": "data/ObjectAccessConfig",
"ObjectCapabilities": "data/ObjectCapabilities",
Expand Down Expand Up @@ -245,6 +252,7 @@
"QueryFilter": "data/QueryFilter",
"QueryFilterSchema": "data/QueryFilter",
"QuerySchema": "data/Query",
"RangeOperatorSchema": "data/RangeOperator",
"ReferenceIdValue": "data/ReferenceIdValue",
"ReferenceIdValueSchema": "data/ReferenceIdValue",
"ReferenceResolution": "data/ReferenceResolution",
Expand Down
2 changes: 2 additions & 0 deletions packages/spec/docs-import-surface.baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"api/MetadataQueryRequest — no type export",
"automation/FlowVariable — no type export",
"automation/StateMachine — no type export",
"data/ComparisonOperator — no type export",
"data/DataEngineAggregateRequest — no type export",
"data/DataEngineCountRequest — no type export",
"data/DataEngineDeleteRequest — no type export",
Expand All @@ -24,6 +25,7 @@
"data/Object — no type export",
"data/ObjectOwnershipEnum — no type export",
"data/Query — no type export",
"data/RangeOperator — no type export",
"data/SeedMode — no type export",
"data/SetOperator — no type export",
"data/SpecialOperator — no type export",
Expand Down
5 changes: 5 additions & 0 deletions packages/spec/json-schema.manifest/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"data/BaseEngineOptions",
"data/CalendarDateValue",
"data/ClockTimeValue",
"data/ComparisonOperator",
"data/ConditionalValidation",
"data/ConsistencyLevel",
"data/ContextToken",
Expand Down Expand Up @@ -81,6 +82,7 @@
"data/FieldMaskingKeep",
"data/FieldMaskingRule",
"data/FieldNode",
"data/FieldOperators",
"data/FieldReference",
"data/FieldType",
"data/FileLikeValue",
Expand All @@ -92,6 +94,7 @@
"data/FormatValidation",
"data/FullTextSearch",
"data/GroupByNode",
"data/Hook",
"data/HookBody",
"data/HookBodyCapability",
"data/HookContext",
Expand All @@ -118,6 +121,7 @@
"data/NoSQLOperationType",
"data/NoSQLQueryOptions",
"data/NoSQLTransactionOptions",
"data/NormalizedFilter",
"data/Object",
"data/ObjectAccessConfig",
"data/ObjectCapabilities",
Expand All @@ -134,6 +138,7 @@
"data/PostgresConfig",
"data/Query",
"data/QueryFilter",
"data/RangeOperator",
"data/ReferenceIdValue",
"data/ReferenceResolution",
"data/ReferenceResolutionError",
Expand Down
Loading
Loading