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
27 changes: 27 additions & 0 deletions .changeset/retire-js-expression-dialect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
'@objectstack/spec': patch
'@objectstack/formula': patch
---

fix(formula): retire the `js` expression dialect and fix the `hasDialect` false-positive (#3278)

The `js` **expression** dialect was declared in `ExpressionDialect` but never
shipped — it existed only as a registry stub with no engine and no author helper
(`cel`/`F`/`P` → CEL, `tmpl` → template, `cron` → cron; nothing ever emitted
`js`). Per ADR-0049 (enforce-or-remove) it is removed from the enum; the set is
now `{cel, cron, template}`.

Procedural JavaScript is unaffected: it remains the **L2** authoring surface —
the sandboxed, capability-gated `ScriptBody { language: 'js' }` in hook/action
bodies — which is a separate enum (`hook-body.zod.ts`), not an expression
dialect.

Also fixes a latent bug in `hasDialect`: it detected stubs via
`dialect.startsWith('stub:')`, but stubs were registered under their real name,
so the check was dead code and `hasDialect('js')` returned a false-positive
`true`. With the stub removed, `hasDialect` reports only registered real
engines, and the registry test now asserts the negative case (`hasDialect('js')
=== false`) so the gate can actually go red.

No runtime behavior changes for any valid persisted artifact — no producer ever
emitted `dialect: 'js'`. See the ADR-0058 addendum.
6 changes: 3 additions & 3 deletions content/docs/references/ai/model-registry.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ const result = ModelCapability.parse(data);
| :--- | :--- | :--- | :--- |
| **name** | `string` | ✅ | Registry name |
| **models** | `Record<string, { model: object; status?: Enum<'active' \| 'deprecated' \| 'experimental' \| 'disabled'>; priority?: integer; fallbackModels?: string[]; … }>` | ✅ | Model entries by ID |
| **promptTemplates** | `Record<string, { id: string; name: string; label: string; system?: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; … }>` | optional | Prompt templates by name |
| **promptTemplates** | `Record<string, { id: string; name: string; label: string; system?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; … }>` | optional | Prompt templates by name |
| **defaultModel** | `string` | optional | Default model ID |
| **enableAutoFallback** | `boolean` | optional | Auto-fallback on errors |

Expand Down Expand Up @@ -168,8 +168,8 @@ const result = ModelCapability.parse(data);
| **id** | `string` | ✅ | Unique template identifier |
| **name** | `string` | ✅ | Template name (snake_case) |
| **label** | `string` | ✅ | Display name |
| **system** | `string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }` | optional | System prompt — supports `{{var}`} interpolation |
| **user** | `string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }` | ✅ | User prompt template — supports `{{var}`} interpolation |
| **system** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }` | optional | System prompt — supports `{{var}`} interpolation |
| **user** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }` | ✅ | User prompt template — supports `{{var}`} interpolation |
| **assistant** | `string` | optional | Assistant message prefix |
| **variables** | `{ name: string; type?: Enum<'string' \| 'number' \| 'boolean' \| 'object' \| 'array'>; required?: boolean; defaultValue?: any; … }[]` | optional | Template variables |
| **modelId** | `string` | optional | Recommended model ID |
Expand Down
2 changes: 1 addition & 1 deletion content/docs/references/api/automation-api.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ const result = AutomationApiErrorCode.parse(data);
| **type** | `Enum<'autolaunched' \| 'record_change' \| 'schedule' \| 'screen' \| 'api'>` | ✅ | Flow type |
| **variables** | `{ name: string; type: string; isInput?: boolean; isOutput?: boolean }[]` | optional | Flow variables |
| **nodes** | `{ id: string; type: string; label: string; config?: Record<string, any>; … }[]` | ✅ | Flow nodes |
| **edges** | `{ id: string; source: string; target: string; condition?: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; … }[]` | ✅ | Flow connections |
| **edges** | `{ id: string; source: string; target: string; condition?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; … }[]` | ✅ | Flow connections |
| **active** | `boolean` | optional | Is active (Deprecated: use status) |
| **runAs** | `Enum<'system' \| 'user'>` | optional | Execution identity for the run: system = elevated (bypasses RLS), user = the triggering user (RLS-respecting). A schedule-triggered run has no trigger user, so under user it runs UNSCOPED (elevated) — declare system to make that explicit. |
| **errorHandling** | `{ strategy?: Enum<'fail' \| 'retry' \| 'continue'>; maxRetries?: integer; retryDelayMs?: integer; backoffMultiplier?: number; … }` | optional | Flow-level error handling configuration |
Expand Down
4 changes: 2 additions & 2 deletions content/docs/references/api/export.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -533,7 +533,7 @@ Type: `{ sourceField: string; targetField: string; targetLabel?: string; transfo
| **fields** | `string[]` | optional | Fields to include |
| **filter** | `Record<string, any>` | optional | Record filter criteria |
| **templateId** | `string` | optional | Export template ID for field mappings |
| **schedule** | `{ cronExpression: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; timezone?: string }` | ✅ | Schedule timing configuration |
| **schedule** | `{ cronExpression: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; timezone?: string }` | ✅ | Schedule timing configuration |
| **delivery** | `{ method: Enum<'email' \| 'storage' \| 'webhook'>; recipients?: string[]; storagePath?: string; webhookUrl?: string }` | ✅ | Export delivery configuration |


Expand Down Expand Up @@ -567,7 +567,7 @@ Type: `{ sourceField: string; targetField: string; targetLabel?: string; transfo
| **fields** | `string[]` | optional | Fields to include |
| **filter** | `Record<string, any>` | optional | Record filter criteria |
| **templateId** | `string` | optional | Export template ID for field mappings |
| **schedule** | `{ cronExpression: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; timezone?: string }` | ✅ | Schedule timing configuration |
| **schedule** | `{ cronExpression: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; timezone?: string }` | ✅ | Schedule timing configuration |
| **delivery** | `{ method: Enum<'email' \| 'storage' \| 'webhook'>; recipients?: string[]; storagePath?: string; webhookUrl?: string }` | ✅ | Export delivery configuration |
| **enabled** | `boolean` | optional | Whether the scheduled export is active |
| **lastRunAt** | `string` | optional | Last execution timestamp |
Expand Down
4 changes: 2 additions & 2 deletions content/docs/references/api/graphql.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ const result = FederationEntity.parse(data);
| **fields** | `{ required?: string[]; selectable?: string[] }` | optional | Field selection configuration |
| **authRequired** | `boolean` | optional | Require authentication |
| **permissions** | `string[]` | optional | Required permissions |
| **cache** | `{ enabled?: boolean; ttl?: integer; key?: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object } }` | optional | Query caching |
| **cache** | `{ enabled?: boolean; ttl?: integer; key?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object } }` | optional | Query caching |


---
Expand Down Expand Up @@ -408,7 +408,7 @@ const result = FederationEntity.parse(data);
| :--- | :--- | :--- | :--- |
| **path** | `string` | ✅ | Resolver path (Type.field) |
| **type** | `Enum<'datasource' \| 'computed' \| 'script' \| 'proxy'>` | ✅ | Resolver implementation type |
| **implementation** | `{ datasource?: string; query?: string; expression?: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; dependencies?: string[]; … }` | optional | Implementation configuration |
| **implementation** | `{ datasource?: string; query?: string; expression?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; dependencies?: string[]; … }` | optional | Implementation configuration |
| **cache** | `{ enabled?: boolean; ttl?: integer; keyArgs?: string[] }` | optional | Resolver caching |


Expand Down
6 changes: 3 additions & 3 deletions content/docs/references/automation/connector.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -76,16 +76,16 @@ const result = Connector.parse(data);
| **label** | `string` | optional | Sync display name |
| **description** | `string` | optional | Sync description |
| **source** | `{ object?: string; filters?: any; fields?: string[]; connectorInstanceId?: string; … }` | ✅ | Data source |
| **destination** | `{ object?: string; connectorInstanceId?: string; operation: Enum<'insert' \| 'update' \| 'upsert' \| 'delete' \| 'sync'>; mapping?: Record<string, string> \| { source: string; target: string; transform?: { type: 'constant'; value: any } \| { type: 'cast'; targetType: Enum<'string' \| 'number' \| 'boolean' \| 'date'> } \| { type: 'lookup'; table: string; keyField: string; valueField: string } \| { type: 'javascript'; expression: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object } } \| { type: 'map'; mappings: Record<string, any> }; defaultValue?: any }[]; … }` | ✅ | Data destination |
| **destination** | `{ object?: string; connectorInstanceId?: string; operation: Enum<'insert' \| 'update' \| 'upsert' \| 'delete' \| 'sync'>; mapping?: Record<string, string> \| { source: string; target: string; transform?: { type: 'constant'; value: any } \| { type: 'cast'; targetType: Enum<'string' \| 'number' \| 'boolean' \| 'date'> } \| { type: 'lookup'; table: string; keyField: string; valueField: string } \| { type: 'javascript'; expression: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object } } \| { type: 'map'; mappings: Record<string, any> }; defaultValue?: any }[]; … }` | ✅ | Data destination |
| **direction** | `Enum<'push' \| 'pull' \| 'bidirectional'>` | optional | Sync direction |
| **syncMode** | `Enum<'full' \| 'incremental' \| 'realtime'>` | optional | Sync mode |
| **conflictResolution** | `Enum<'source_wins' \| 'destination_wins' \| 'latest_wins' \| 'manual' \| 'merge'>` | optional | Conflict resolution |
| **schedule** | `string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }` | optional | Cron schedule |
| **schedule** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }` | optional | Cron schedule |
| **enabled** | `boolean` | optional | Sync enabled |
| **changeTrackingField** | `string` | optional | Field for change tracking |
| **batchSize** | `integer` | optional | Batch size for processing |
| **retry** | `{ maxAttempts?: integer; backoffMs?: integer }` | optional | Retry configuration |
| **validation** | `{ required?: string[]; unique?: string[]; custom?: { name: string; condition: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; message: string }[] }` | optional | Validation rules |
| **validation** | `{ required?: string[]; unique?: string[]; custom?: { name: string; condition: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; message: string }[] }` | optional | Validation rules |
| **errorHandling** | `{ onValidationError?: Enum<'skip' \| 'fail' \| 'log'>; onSyncError?: Enum<'skip' \| 'fail' \| 'retry'>; notifyOnError?: string[] }` | optional | Error handling |
| **optimization** | `{ parallelBatches?: boolean; cacheEnabled?: boolean; compressionEnabled?: boolean }` | optional | Performance optimization |
| **audit** | `{ logLevel?: Enum<'none' \| 'error' \| 'warn' \| 'info' \| 'debug'>; retainLogsForDays?: integer; trackChanges?: boolean }` | optional | Audit configuration |
Expand Down
12 changes: 6 additions & 6 deletions content/docs/references/automation/control-flow.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ const result = FlowRegion.parse(data);
| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **nodes** | `{ id: string; type: string; label: string; config?: Record<string, any>; … }[]` | ✅ | Region body nodes (single-entry/single-exit sub-graph) |
| **edges** | `{ id: string; source: string; target: string; condition?: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; … }[]` | optional | Region body edges |
| **edges** | `{ id: string; source: string; target: string; condition?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; … }[]` | optional | Region body edges |


---
Expand All @@ -111,7 +111,7 @@ const result = FlowRegion.parse(data);
| **iteratorVariable** | `string` | optional | Loop variable holding the current item |
| **indexVariable** | `string` | optional | Optional loop variable holding the current index |
| **maxIterations** | `integer` | optional | Hard cap on iterations (clamped to the engine ceiling) |
| **body** | `{ nodes: { id: string; type: string; label: string; config?: Record<string, any>; … }[]; edges?: { id: string; source: string; target: string; condition?: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; … }[] }` | optional | Loop body region (omit for legacy flat-graph loops) |
| **body** | `{ nodes: { id: string; type: string; label: string; config?: Record<string, any>; … }[]; edges?: { id: string; source: string; target: string; condition?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; … }[] }` | optional | Loop body region (omit for legacy flat-graph loops) |


---
Expand All @@ -124,7 +124,7 @@ const result = FlowRegion.parse(data);
| :--- | :--- | :--- | :--- |
| **name** | `string` | optional | Branch label |
| **nodes** | `{ id: string; type: string; label: string; config?: Record<string, any>; … }[]` | ✅ | Branch body nodes |
| **edges** | `{ id: string; source: string; target: string; condition?: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; … }[]` | optional | Branch body edges |
| **edges** | `{ id: string; source: string; target: string; condition?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; … }[]` | optional | Branch body edges |


---
Expand All @@ -135,7 +135,7 @@ const result = FlowRegion.parse(data);

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **branches** | `{ name?: string; nodes: { id: string; type: string; label: string; config?: Record<string, any>; … }[]; edges?: { id: string; source: string; target: string; condition?: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; … }[] }[]` | ✅ | Branch regions executed concurrently; implicit join at block end |
| **branches** | `{ name?: string; nodes: { id: string; type: string; label: string; config?: Record<string, any>; … }[]; edges?: { id: string; source: string; target: string; condition?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; … }[] }[]` | ✅ | Branch regions executed concurrently; implicit join at block end |


---
Expand All @@ -146,8 +146,8 @@ const result = FlowRegion.parse(data);

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **try** | `{ nodes: { id: string; type: string; label: string; config?: Record<string, any>; … }[]; edges?: { id: string; source: string; target: string; condition?: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; … }[] }` | ✅ | Protected region |
| **catch** | `{ nodes: { id: string; type: string; label: string; config?: Record<string, any>; … }[]; edges?: { id: string; source: string; target: string; condition?: string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; … }[] }` | optional | Handler region run when the try region fails |
| **try** | `{ nodes: { id: string; type: string; label: string; config?: Record<string, any>; … }[]; edges?: { id: string; source: string; target: string; condition?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; … }[] }` | ✅ | Protected region |
| **catch** | `{ nodes: { id: string; type: string; label: string; config?: Record<string, any>; … }[]; edges?: { id: string; source: string; target: string; condition?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; … }[] }` | optional | Handler region run when the try region fails |
| **errorVariable** | `string` | optional | Variable holding the caught error in the catch region |
| **retry** | `{ maxRetries?: integer; retryDelayMs?: integer; backoffMultiplier?: number; maxRetryDelayMs?: integer; … }` | optional | Optional retry policy for the try region |

Expand Down
2 changes: 1 addition & 1 deletion content/docs/references/automation/etl.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ const result = ETLDestination.parse(data);
| **destination** | `{ type: Enum<'database' \| 'api' \| 'file' \| 'stream' \| 'object' \| 'warehouse' \| 'storage' \| 'spreadsheet'>; connector?: string; config: Record<string, any>; writeMode?: Enum<'append' \| 'overwrite' \| 'upsert' \| 'merge'>; … }` | ✅ | Data destination |
| **transformations** | `{ name?: string; type: Enum<'map' \| 'filter' \| 'aggregate' \| 'join' \| 'script' \| 'lookup' \| 'split' \| 'merge' \| 'normalize' \| 'deduplicate'>; config: Record<string, any>; continueOnError?: boolean }[]` | optional | Transformation pipeline |
| **syncMode** | `Enum<'full' \| 'incremental' \| 'cdc'>` | optional | Sync mode |
| **schedule** | `string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }` | optional | Cron schedule expression |
| **schedule** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }` | optional | Cron schedule expression |
| **enabled** | `boolean` | optional | Pipeline enabled status |
| **retry** | `{ maxAttempts?: integer; backoffMs?: integer }` | optional | Retry configuration |
| **notifications** | `{ onSuccess?: string[]; onFailure?: string[] }` | optional | Notification settings |
Expand Down
2 changes: 1 addition & 1 deletion content/docs/references/automation/execution.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ const result = Checkpoint.parse(data);
| :--- | :--- | :--- | :--- |
| **id** | `string` | ✅ | Schedule instance ID |
| **flowName** | `string` | ✅ | Flow machine name |
| **cronExpression** | `string \| { dialect: Enum<'cel' \| 'js' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }` | ✅ | Cron expression — cron`0 9 * * MON-FRI` |
| **cronExpression** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }` | ✅ | Cron expression — cron`0 9 * * MON-FRI` |
| **timezone** | `string` | optional | IANA timezone for cron evaluation |
| **status** | `Enum<'active' \| 'paused' \| 'disabled' \| 'expired'>` | optional | Current schedule status |
| **nextRunAt** | `string` | optional | Next scheduled execution timestamp |
Expand Down
Loading