You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
| "No publication with event X found" | Typed channel event doesn't match any publication | Check `event:` option on the resource's `pub_sub` block |
368
405
| "Duplicate event names found in typed_channel" | Same event name across resources in one channel | Use unique event names per channel |
369
406
| "Payload type name conflict" | Same event name across different channels maps to different TS types | Rename events or ensure same `returns` type |
370
-
| Channel `unknown` payload type | Publication missing `returns`option | Add `returns: :some_type` to the publication |
407
+
| Channel `unknown` payload type | Publication missing `returns`type (no `transform :calc` or explicit `returns`) | Use `transform :some_calc` with an `:auto`-typed calculation (recommended), or add explicit `returns:` |
371
408
| "not `public?`" error on RPC action | Action has `public?false` | Set `public?true` on the action or remove it from `typescript_rpc` |
372
409
| "not `public?`" error on read_action | `read_action` has `public?false` | Set `public?true` on the read action |
373
410
| "not `public?`" error on relationship read action | Relationship destination's read action has `public?false` | Set `public?true` on the destination's read action |
Copy file name to clipboardExpand all lines: agent-docs/features/typed-channel.md
+69-5Lines changed: 69 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -10,10 +10,16 @@ SPDX-License-Identifier: MIT
10
10
11
11
The `AshTypescript.TypedChannel` DSL generates typed TypeScript event subscriptions from Ash PubSub publications. It reads the `returns` type from each declared publication and generates branded channel types, typed payload aliases, event maps, and subscription helper functions.
12
12
13
+
**Recommended approach**: Use `transform :some_calc` on publications to reference a resource calculation. When the calculation uses `:auto` typing, Ash automatically derives the `returns` type from the expression, so AshTypescript gets full type information without any manual `returns` declarations. This keeps the type and the transform logic in sync via a single source of truth (the calculation). You can also use explicit `returns: :type` with an anonymous function transform, but this requires manually keeping the type and transform in sync.
14
+
13
15
**Key distinction**: `AshTypescript.Rpc` with `generate_phx_channel_rpc_actions` is for request/response RPC over channels. `AshTypescript.TypedChannel` is for one-way push events (PubSub broadcasts) where the server pushes typed payloads to the client.
14
16
15
17
**Important**: `AshTypescript.TypedChannel` is a standalone Spark DSL — completely independent from `Ash.Resource` and `AshTypescript.Rpc`. The developer owns channel authorization via Phoenix's `join/3`.
16
18
19
+
## Requirements
20
+
21
+
Typed channels require **Ash >= 3.17.1**, which introduced `returns`, `public?`, and calculation `transform` support on PubSub publications. **Ash >= 3.21.1 is recommended**, as it added support for `:auto`-typed calculations as transforms, allowing Ash to automatically derive the `returns` type from the calculation expression.
@@ -51,10 +58,67 @@ The Orchestrator appends channel types to the end of `ash_types.ts` after all re
51
58
52
59
### Type Mapping
53
60
54
-
Channel payload types use `TypeMapper.map_channel_payload_type/2` instead of `map_type/3`. The difference: typed containers (maps/structs with `:fields`) generate plain object types without the `__type`/`__primitiveFields` metadata that the RPC field-selection system needs. Non-container types (primitives, lists, etc.) delegate to `map_type/3` with `:output` direction.
61
+
Channel payload types use `TypeMapper.map_channel_payload_type/2` instead of `map_type/3`. The codegen reads the publication's `returns` type — which Ash auto-populates when `transform :some_calc` references a calculation, or which can be set explicitly via `returns:`. The difference from RPC types: typed containers (maps/structs with `:fields`) generate plain object types without the `__type`/`__primitiveFields` metadata that the RPC field-selection system needs. Non-container types (primitives, lists, etc.) delegate to `map_type/3` with `:output` direction.
transform:fn notification -> %{id: notification.data.id, title: notification.data.title} end
118
+
```
119
+
120
+
### Channel definition
121
+
58
122
```elixir
59
123
defmoduleMyApp.OrgChanneldo
60
124
useAshTypescript.TypedChannel
@@ -101,7 +165,7 @@ The `VerifyTypedChannel` verifier runs at compile time:
101
165
| Event exists | Error | Each declared event must match a publication on the resource |
102
166
| Unique events | Error | Event names must be unique across all resources in a channel |
103
167
|`public?: true`| Warning | Publications should be marked `public?: true`|
104
-
|`returns` set | Warning | Publications without `returns` produce `unknown` TypeScript type |
168
+
|`returns` set | Warning | Publications without `returns`(no `transform :calc` or explicit `returns:`) produce `unknown` TypeScript type |
105
169
106
170
## Generated TypeScript
107
171
@@ -177,7 +241,7 @@ When `generate_all_channel_types/1` processes multiple channels, payload type al
177
241
178
242
Before deduplication, `validate_no_payload_type_conflicts!/2` checks that events sharing a payload type name also share the same TypeScript type. If two events across different channels produce the same type name (e.g., `ItemCreatedPayload`) but map to different TypeScript types (e.g., `{id: UUID}` vs `string`), codegen raises a `RuntimeError` with details about the conflicting events and channels.
179
243
180
-
This can happen when two resources declare publications with the same event name but different `returns` types, and those resources are used in separate channels. The verifier's unique-event check only applies within a single channel, so this cross-channel conflict is caught at codegen time instead.
244
+
This can happen when two resources declare publications with the same event name but different `returns` types (whether auto-derived from calculation transforms or explicitly declared), and those resources are used in separate channels. The verifier's unique-event check only applies within a single channel, so this cross-channel conflict is caught at codegen time instead.
| "No publication with event X found" | Event name doesn't match any publication on the resource | Check the `event:` option (or action name fallback) on the resource's `pub_sub` block |
259
323
| "Duplicate event names found" | Same event name used across multiple resources in one channel | Use unique event names per channel |
260
324
| "Payload type name conflict" | Same event name across different channels maps to different TypeScript types | Rename conflicting events or ensure they return the same type |
261
-
|`unknown` TypeScript payload type | Publication missing `returns`option | Add `returns: :some_type` to the publication|
325
+
|`unknown` TypeScript payload type | Publication missing `returns`type (no `transform :calc` or explicit `returns:`) | Use `transform :some_calc` with an `:auto`-typed calculation (recommended), or add explicit `returns:`|
262
326
| Channel types not in output |`typed_channels` not configured | Add modules to `typed_channels: [...]` in config |
263
327
| Channel functions not generated |`typed_channels_output_file` not configured | Set `typed_channels_output_file:` in config |
Typed channels require **Ash >= 3.17.1**, which introduced `returns`, `public?`, and calculation `transform` support on PubSub publications. **Ash >= 3.21.1 is recommended**, as it added support for `:auto`-typed calculations as transforms, allowing Ash to automatically derive the `returns` type from the calculation expression.
26
+
23
27
## Quick Start
24
28
25
-
### 1. Add PubSub publications with `returns` types
29
+
### 1. Add PubSub publications with calculation transforms
30
+
31
+
The recommended way to get typed payloads is to use `transform :some_calc` on
32
+
publications, pointing to a resource calculation with `:auto` typing. Ash
33
+
automatically derives the `returns` type from the calculation expression, so
34
+
AshTypescript gets the type information it needs without manual `returns`
A typed channel consists of two parts: a DSL module that declares which events get TypeScript types, and a Phoenix channel that handles runtime behavior. You can put them in the same module or keep them separate.
@@ -274,25 +303,25 @@ Wildcard topics require a `suffix` parameter that replaces the `*`. The factory
274
303
275
304
## Payload Type Resolution
276
305
277
-
The TypeScript payload type is derived from the publication's `returns`option:
306
+
The TypeScript payload type is derived from the publication's `returns`type. When using `transform :some_calc`, Ash auto-populates `returns` from the calculation's type. You can also set `returns` explicitly.
278
307
279
-
|`returns` Value | TypeScript Type |
280
-
|----------------|-----------------|
281
-
|`:string`|`string`|
282
-
|`:integer`|`number`|
283
-
|`:boolean`|`boolean`|
284
-
|`:uuid`|`UUID`|
285
-
|`:utc_datetime`|`UtcDateTime`|
286
-
|`:map` with `fields`|`{fieldName: type, ...}`|
287
-
| Not set |`unknown`|
308
+
|`returns` Value | TypeScript Type | How to Get It |
|`:map` with `fields`|`{fieldName: type, ...}`|`calculate :my_calc, :auto, expr(%{id: id, name: name})` or explicit `returns: :map` with `constraints`|
316
+
| Not set |`unknown`| Missing `transform :calc` and no explicit `returns`|
288
317
289
318
Map types with `:fields` constraints generate plain object types without the `__type`/`__primitiveFields` metadata used by the RPC field-selection system.
290
319
291
320
### Multi-Channel Payload Deduplication
292
321
293
322
When multiple channels are configured, payload type aliases are deduplicated by name. If two channels both subscribe to `article_published` from the same resource, only one `ArticlePublishedPayload` type is emitted in `ash_types.ts`.
294
323
295
-
If two different resources declare publications with the same event name but different `returns` types and those resources appear in separate channels, codegen will raise an error:
324
+
If two different resources declare publications with the same event name but different `returns` types (whether auto-derived or explicit) and those resources appear in separate channels, codegen will raise an error:
296
325
297
326
```
298
327
Payload type name conflict detected across typed channels.
@@ -355,7 +384,7 @@ The DSL verifier checks your configuration at compile time:
355
384
| Event exists | Error | Declared event doesn't match any publication on the resource |
356
385
| Unique events | Error | Same event name used across multiple resources in one channel |
357
386
|`public?: true`| Warning | Publication not marked as public |
358
-
|`returns` set | Warning | Publication missing `returns` (payload type becomes `unknown`) |
387
+
|`returns` set | Warning | Publication missing `returns`— no `transform :calc` or explicit `returns:`(payload type becomes `unknown`) |
0 commit comments