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
68 changes: 68 additions & 0 deletions .changeset/retire-three-orphan-operator-vocabularies.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
---
"@objectstack/spec": major
---

refactor(spec)!: retire three orphan operator vocabularies (objectui#2945 Track A)

An audit of every comparison/aggregation vocabulary the spec ships
(objectstack-ai/objectui#2901) found the operator vocabularies had multiplied
past what any code consults. Three had **no importer at all** — not in this
repo, not in objectui, not in cloud — and each contradicted the vocabulary that
is actually enforced. Removed rather than reconciled: a second name for one
concept is how they drifted apart in the first place.

**`AggregationFunctionEnum`** (`shared/enums.zod.ts`). Its own doc comment
claimed it was *"used across query, data-engine, analytics, field"*. It was used
by nothing. `AggregationFunction` (`data/query.zod.ts`) is the vocabulary the
query engine, `service-analytics`' dataset compiler and the native-SQL strategy
all gate on — and the two disagreed: this one carried
`percentile`/`median`/`stddev`/`variance`, that one carries
`array_agg`/`string_agg`. It also exported a *type* named `AggregationFunction`
while `data/query.zod.ts` exports a *value* of that name, so the two occupied
the same identifier in different declaration spaces with different members.

**`FilterOperator`** + `EventFilterCondition` + `EventFilterSchema`
(`api/websocket.zod.ts`), reached from `EventSubscriptionSchema.filters`. No
runtime ever evaluated an event filter — `matchesSubscription` matches on object
name and event type only (`contracts/realtime-service.ts`) — and the
subscription shape the transports actually carry is the separate, deliberately
unvalidated `filters: z.unknown()` on `SubscriptionEventSchema`
(`api/realtime.zod.ts`). So this was a *second* modelling of event filtering
that described a capability no code provided: a subscriber who set `filters`
received every event regardless.

The `filters` **key stays**, now typed `z.unknown()` with the same
NOT-YET-ENFORCED marker as its `api/realtime.zod.ts` counterpart. Retiring an
object key requires a tombstone plus a conversion (ADR-0104), which is the right
rule and the wrong trade here — there is no author to migrate for a shape nothing
validated, and Track A is meant to carry no migration. The two subscription
surfaces now describe event filtering identically, and neither implies an
enforcement that does not exist. Whichever grows real filtering should lower onto
`AST_OPERATOR_MAP` rather than reintroduce a vocabulary of its own.

**`ODataFilterOperatorSchema`** (`api/odata.zod.ts`). Nothing parses an OData
`$filter` against it — `$filter` is carried as an opaque string on
`ODataQuerySchema` and as the `odata` adapter template in
`query-adapter.zod.ts` — and an enum mixing operators with `(`/`)` could not
validate an expression anyway, since it describes tokens, not a grammar. A real
implementation needs a parser, and that parser should lower onto
`AST_OPERATOR_MAP` like every other entry point.

**Breaking, in the narrowest sense.** All three were reachable as public
exports (`@objectstack/spec/shared` and `@objectstack/spec/api`), so this is a
`major`. No consumer exists to break: verified by grep across framework
`packages/` + `apps/`, objectui, and cloud. Nothing is *narrowed* — no accepted
value stops being accepted, so no already-stored metadata or in-flight payload
changes meaning. That is what made this the one track of objectui#2945 that was
safe to start; narrowing `VALID_AST_OPERATORS` or retiring a
`VIEW_FILTER_OPERATORS` alias is not, and remains blocked on #3948.

The generated artefacts move with the deletions, as the ratchets require:
`json-schema.manifest.json` drops the five unpublished schemas,
`authorable-surface.json` the seven keys of the two deleted objects,
`api-surface.json` the eight exports, and the three reference-doc pages are
regenerated.

Verified: full `@objectstack/spec` suite **6917 tests across 266 files**, plus
`tsc --noEmit`, `check:docs`, `check:api-surface`, `check:authorable-surface` and
`check:skill-docs`, all clean.
25 changes: 2 additions & 23 deletions content/docs/references/api/odata.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -112,8 +112,8 @@ count: true
## TypeScript Usage

```typescript
import { ODataConfig, ODataError, ODataFilterFunction, ODataFilterOperator, ODataMetadata, ODataQuery, ODataResponse } from '@objectstack/spec/api';
import type { ODataConfig, ODataError, ODataFilterFunction, ODataFilterOperator, ODataMetadata, ODataQuery, ODataResponse } from '@objectstack/spec/api';
import { ODataConfig, ODataError, ODataFilterFunction, ODataMetadata, ODataQuery, ODataResponse } from '@objectstack/spec/api';
import type { ODataConfig, ODataError, ODataFilterFunction, ODataMetadata, ODataQuery, ODataResponse } from '@objectstack/spec/api';

// Validate data
const result = ODataConfig.parse(data);
Expand DownExpand Up@@ -179,27 +179,6 @@ const result = ODataConfig.parse(data);
* `all`


---

## ODataFilterOperator

### Allowed Values

* `eq`
* `ne`
* `lt`
* `le`
* `gt`
* `ge`
* `and`
* `or`
* `not`
* `(`
* `)`
* `in`
* `has`


---

## ODataMetadata
Expand Down
58 changes: 5 additions & 53 deletions content/docs/references/api/websocket.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,8 +30,8 @@ runtime today.
## TypeScript Usage

```typescript
import { AckMessage, CursorMessage, CursorPosition, DocumentState, EditMessage, EditOperation, EditOperationType, ErrorMessage, EventFilter, EventFilterCondition, EventMessage, EventPattern, EventSubscription, FilterOperator, PingMessage, PongMessage, PresenceMessage, PresenceState, PresenceUpdate, SimpleCursorPosition, SimplePresenceState, SubscribeMessage, UnsubscribeMessage, UnsubscribeRequest, WebSocketConfig, WebSocketEvent, WebSocketMessage, WebSocketMessageType, WebSocketPresenceStatus, WebSocketServerConfig } from '@objectstack/spec/api';
import type { AckMessage, CursorMessage, CursorPosition, DocumentState, EditMessage, EditOperation, EditOperationType, ErrorMessage, EventFilter, EventFilterCondition, EventMessage, EventPattern, EventSubscription, FilterOperator, PingMessage, PongMessage, PresenceMessage, PresenceState, PresenceUpdate, SimpleCursorPosition, SimplePresenceState, SubscribeMessage, UnsubscribeMessage, UnsubscribeRequest, WebSocketConfig, WebSocketEvent, WebSocketMessage, WebSocketMessageType, WebSocketPresenceStatus, WebSocketServerConfig } from '@objectstack/spec/api';
import { AckMessage, CursorMessage, CursorPosition, DocumentState, EditMessage, EditOperation, EditOperationType, ErrorMessage, EventMessage, EventPattern, EventSubscription, PingMessage, PongMessage, PresenceMessage, PresenceState, PresenceUpdate, SimpleCursorPosition, SimplePresenceState, SubscribeMessage, UnsubscribeMessage, UnsubscribeRequest, WebSocketConfig, WebSocketEvent, WebSocketMessage, WebSocketMessageType, WebSocketPresenceStatus, WebSocketServerConfig } from '@objectstack/spec/api';
import type { AckMessage, CursorMessage, CursorPosition, DocumentState, EditMessage, EditOperation, EditOperationType, ErrorMessage, EventMessage, EventPattern, EventSubscription, PingMessage, PongMessage, PresenceMessage, PresenceState, PresenceUpdate, SimpleCursorPosition, SimplePresenceState, SubscribeMessage, UnsubscribeMessage, UnsubscribeRequest, WebSocketConfig, WebSocketEvent, WebSocketMessage, WebSocketMessageType, WebSocketPresenceStatus, WebSocketServerConfig } from '@objectstack/spec/api';

// Validate data
const result = AckMessage.parse(data);
Expand DownExpand Up@@ -163,33 +163,6 @@ const result = AckMessage.parse(data);
| **details** | `any` | optional | Additional error details |


---

## EventFilter

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **conditions** | `{ field: string; operator: Enum<'eq' \| 'ne' \| 'gt' \| 'gte' \| 'lt' \| 'lte' \| 'in' \| 'nin' \| 'contains' \| 'startsWith' \| 'endsWith' \| 'exists' \| 'regex'>; value?: any }[]` | optional | Array of filter conditions |
| **and** | `[EventFilter](#eventfilter)[]` | optional | AND logical combination of filters |
| **or** | `[EventFilter](#eventfilter)[]` | optional | OR logical combination of filters |
| **not** | `[EventFilter](#eventfilter)` | optional | NOT logical negation of filter |


---

## EventFilterCondition

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **field** | `string` | ✅ | Field path to filter on (supports dot notation, e.g., "user.email") |
| **operator** | `Enum<'eq' \| 'ne' \| 'gt' \| 'gte' \| 'lt' \| 'lte' \| 'in' \| 'nin' \| 'contains' \| 'startsWith' \| 'endsWith' \| 'exists' \| 'regex'>` | ✅ | Comparison operator |
| **value** | `any` | optional | Value to compare against (not needed for "exists" operator) |


---

## EventMessage
Expand DownExpand Up@@ -222,31 +195,10 @@ const result = AckMessage.parse(data);
| **subscriptionId** | `string` | ✅ | Unique subscription identifier |
| **events** | `string[]` | ✅ | Event patterns to subscribe to (supports wildcards, e.g., "record.*", "user.created") |
| **objects** | `string[]` | optional | Object names to filter events by (e.g., ["account", "contact"]) |
| **filters** | `{ conditions?: { field: string; operator: Enum<'eq' \| 'ne' \| 'gt' \| 'gte' \| 'lt' \| 'lte' \| 'in' \| 'nin' \| 'contains' \| 'startsWith' \| 'endsWith' \| 'exists' \| 'regex'>; value?: any }[]; and?: object[]; or?: object[]; not?: object }` | optional | Advanced filter conditions for event payloads |
| **filters** | `any` | optional | Filter conditions for event payloads (not yet enforced — the runtime filters by object name andevent type only) |
| **channels** | `string[]` | optional | Channel names for scoped subscriptions |


---

## FilterOperator

### Allowed Values

* `eq`
* `ne`
* `gt`
* `gte`
* `lt`
* `lte`
* `in`
* `nin`
* `contains`
* `startsWith`
* `endsWith`
* `exists`
* `regex`


---

## PingMessage
Expand DownExpand Up@@ -361,7 +313,7 @@ const result = AckMessage.parse(data);
| **messageId** | `string` | ✅ | Unique message identifier |
| **type** | `'subscribe'` | ✅ | |
| **timestamp** | `string` | ✅ | ISO 8601 datetime when message was sent |
| **subscription** | `{ subscriptionId: string; events: string[]; objects?: string[]; filters?: { conditions?: { field: string; operator: Enum<'eq' \| 'ne' \| 'gt' \| 'gte' \| 'lt' \| 'lte' \| 'in' \| 'nin' \| 'contains' \| 'startsWith' \| 'endsWith' \| 'exists' \| 'regex'>; value?: any }[]; and?: object[]; or?: object[]; not?: object }; … }` | ✅ | Subscription configuration |
| **subscription** | `{ subscriptionId: string; events: string[]; objects?: string[]; filters?: any; … }` | ✅ | Subscription configuration |


---
Expand DownExpand Up@@ -440,7 +392,7 @@ This schema accepts one of the following structures:
| **messageId** | `string` | ✅ | Unique message identifier |
| **type** | `'subscribe'` | ✅ | |
| **timestamp** | `string` | ✅ | ISO 8601 datetime when message was sent |
| **subscription** | `{ subscriptionId: string; events: string[]; objects?: string[]; filters?: { conditions?: { field: string; operator: Enum<'eq' \| 'ne' \| 'gt' \| 'gte' \| 'lt' \| 'lte' \| 'in' \| 'nin' \| 'contains' \| 'startsWith' \| 'endsWith' \| 'exists' \| 'regex'>; value?: any }[]; and?: object[]; or?: object[]; not?: object }; … }` | ✅ | Subscription configuration |
| **subscription** | `{ subscriptionId: string; events: string[]; objects?: string[]; filters?: any; … }` | ✅ | Subscription configuration |

---

Expand Down
28 changes: 4 additions & 24 deletions content/docs/references/shared/enums.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,7 +5,7 @@ description: Enums protocol schemas

{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}

Aggregation functions used across query, data-engine, analytics, field
Sort direction used across query, data-engine, analytics

<Callout type="info">
**Source:** `packages/spec/src/shared/enums.zod.ts`
Expand All@@ -14,33 +14,13 @@ Aggregation functions used across query, data-engine, analytics, field
## TypeScript Usage

```typescript
import { AggregationFunctionEnum, CacheStrategyEnum, IsolationLevelEnum, MutationEventEnum, SortDirectionEnum, SortItem } from '@objectstack/spec/shared';
import type { AggregationFunctionEnum, CacheStrategyEnum, IsolationLevelEnum, MutationEventEnum, SortDirectionEnum, SortItem } from '@objectstack/spec/shared';
import { CacheStrategyEnum, IsolationLevelEnum, MutationEventEnum, SortDirectionEnum, SortItem } from '@objectstack/spec/shared';
import type { CacheStrategyEnum, IsolationLevelEnum, MutationEventEnum, SortDirectionEnum, SortItem } from '@objectstack/spec/shared';

// Validate data
const result = AggregationFunctionEnum.parse(data);
const result = CacheStrategyEnum.parse(data);
```

---

## AggregationFunctionEnum

Standard aggregation functions

### Allowed Values

* `count`
* `sum`
* `avg`
* `min`
* `max`
* `count_distinct`
* `percentile`
* `median`
* `stddev`
* `variance`


---

## CacheStrategyEnum
Expand Down
8 changes: 0 additions & 8 deletions packages/spec/api-surface.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -2494,9 +2494,6 @@
"ErrorMessageSchema (const)",
"ErrorResponse (type)",
"ErrorResponseSchema (const)",
"EventFilter (type)",
"EventFilterCondition (type)",
"EventFilterSchema (const)",
"EventMessage (type)",
"EventMessageSchema (const)",
"EventPattern (type)",
Expand DownExpand Up@@ -2525,7 +2522,6 @@
"FileTypeValidationSchema (const)",
"FileUploadResponse (type)",
"FileUploadResponseSchema (const)",
"FilterOperator (type)",
"FindDataRequest (type)",
"FindDataRequestSchema (const)",
"FindDataResponse (type)",
Expand DownExpand Up@@ -2793,8 +2789,6 @@
"ODataErrorSchema (const)",
"ODataFilterFunction (type)",
"ODataFilterFunctionSchema (const)",
"ODataFilterOperator (type)",
"ODataFilterOperatorSchema (const)",
"ODataMetadata (type)",
"ODataMetadataSchema (const)",
"ODataQuery (type)",
Expand DownExpand Up@@ -4392,8 +4386,6 @@
"positionForm (const)"
],
"./shared": [
"AggregationFunction (type)",
"AggregationFunctionEnum (const)",
"AppName (type)",
"AppNameSchema (const)",
"ApplyProtectionContext (interface)",
Expand Down
7 changes: 0 additions & 7 deletions packages/spec/authorable-surface.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -999,13 +999,6 @@
"api/ErrorResponse:error",
"api/ErrorResponse:meta",
"api/ErrorResponse:success",
"api/EventFilter:and",
"api/EventFilter:conditions",
"api/EventFilter:not",
"api/EventFilter:or",
"api/EventFilterCondition:field",
"api/EventFilterCondition:operator",
"api/EventFilterCondition:value",
"api/EventMessage:eventName",
"api/EventMessage:messageId",
"api/EventMessage:object",
Expand Down
5 changes: 0 additions & 5 deletions packages/spec/json-schema.manifest.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -208,8 +208,6 @@
"api/ErrorHandlingConfig",
"api/ErrorMessage",
"api/ErrorResponse",
"api/EventFilter",
"api/EventFilterCondition",
"api/EventMessage",
"api/EventPattern",
"api/EventSubscription",
Expand All@@ -225,7 +223,6 @@
"api/FileDownloadUrlResponse",
"api/FileTypeValidation",
"api/FileUploadResponse",
"api/FilterOperator",
"api/FindDataRequest",
"api/FindDataResponse",
"api/FlowSummary",
Expand DownExpand Up@@ -361,7 +358,6 @@
"api/ODataConfig",
"api/ODataError",
"api/ODataFilterFunction",
"api/ODataFilterOperator",
"api/ODataMetadata",
"api/ODataQuery",
"api/ODataQueryAdapter",
Expand DownExpand Up@@ -1199,7 +1195,6 @@
"security/SharingRule",
"security/SharingRuleType",
"security/TenancyPosture",
"shared/AggregationFunctionEnum",
"shared/AppName",
"shared/BaseMetadataRecord",
"shared/CacheStrategyEnum",
Expand Down
33 changes: 0 additions & 33 deletions packages/spec/src/api/odata.test.ts
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
import { describe, it, expect } from 'vitest';
import {
ODataQuerySchema,
ODataFilterOperatorSchema,
ODataFilterFunctionSchema,
ODataResponseSchema,
ODataErrorSchema,
Expand DownExpand Up@@ -269,38 +268,6 @@ describe('ODataQuerySchema', () => {
});
});

describe('ODataFilterOperatorSchema', () => {
it('should accept comparison operators', () => {
const operators = ['eq', 'ne', 'lt', 'le', 'gt', 'ge'];

operators.forEach(op => {
expect(() => ODataFilterOperatorSchema.parse(op)).not.toThrow();
});
});

it('should accept logical operators', () => {
const operators = ['and', 'or', 'not'];

operators.forEach(op => {
expect(() => ODataFilterOperatorSchema.parse(op)).not.toThrow();
});
});

it('should accept grouping operators', () => {
expect(() => ODataFilterOperatorSchema.parse('(')).not.toThrow();
expect(() => ODataFilterOperatorSchema.parse(')')).not.toThrow();
});

it('should accept other operators', () => {
expect(() => ODataFilterOperatorSchema.parse('in')).not.toThrow();
expect(() => ODataFilterOperatorSchema.parse('has')).not.toThrow();
});

it('should reject invalid operators', () => {
expect(() => ODataFilterOperatorSchema.parse('invalid')).toThrow();
});
});

describe('ODataFilterFunctionSchema', () => {
it('should accept string functions', () => {
const functions = [
Expand Down
Loading
Loading