@ably/ai-transport 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -19
- package/dist/ably-ai-transport.js +1790 -1091
- package/dist/ably-ai-transport.js.map +1 -1
- package/dist/ably-ai-transport.umd.cjs +1 -1
- package/dist/ably-ai-transport.umd.cjs.map +1 -1
- package/dist/constants.d.ts +2 -2
- package/dist/core/agent.d.ts +20 -5
- package/dist/core/channel-options.d.ts +57 -0
- package/dist/core/codec/codec-event.d.ts +9 -0
- package/dist/core/codec/decoder.d.ts +4 -1
- package/dist/core/codec/define-codec.d.ts +100 -0
- package/dist/core/codec/encoder.d.ts +2 -7
- package/dist/core/codec/field-bag.d.ts +85 -0
- package/dist/core/codec/fields.d.ts +141 -0
- package/dist/core/codec/index.d.ts +8 -1
- package/dist/core/codec/input-descriptor-decoder.d.ts +19 -0
- package/dist/core/codec/input-descriptor-encoder.d.ts +22 -0
- package/dist/core/codec/input-descriptors.d.ts +281 -0
- package/dist/core/codec/output-descriptor-decoder.d.ts +29 -0
- package/dist/core/codec/output-descriptor-encoder.d.ts +31 -0
- package/dist/core/codec/output-descriptors.d.ts +237 -0
- package/dist/core/codec/types.d.ts +95 -36
- package/dist/core/codec/well-known-inputs.d.ts +52 -0
- package/dist/core/transport/agent-view.d.ts +296 -0
- package/dist/core/transport/decode-fold.d.ts +40 -32
- package/dist/core/transport/headers.d.ts +30 -1
- package/dist/core/transport/index.d.ts +1 -1
- package/dist/core/transport/invocation.d.ts +1 -1
- package/dist/core/transport/load-history-pages.d.ts +71 -0
- package/dist/core/transport/load-history.d.ts +21 -16
- package/dist/core/transport/run-manager.d.ts +9 -11
- package/dist/core/transport/session-support.d.ts +55 -0
- package/dist/core/transport/tree.d.ts +165 -15
- package/dist/core/transport/types/agent.d.ts +120 -98
- package/dist/core/transport/types/client.d.ts +45 -12
- package/dist/core/transport/types/tree.d.ts +52 -10
- package/dist/core/transport/types/view.d.ts +55 -28
- package/dist/core/transport/view.d.ts +176 -58
- package/dist/core/transport/wire-log.d.ts +102 -0
- package/dist/errors.d.ts +10 -4
- package/dist/index.d.ts +6 -5
- package/dist/react/ably-ai-transport-react.js +784 -415
- package/dist/react/ably-ai-transport-react.js.map +1 -1
- package/dist/react/ably-ai-transport-react.umd.cjs +1 -1
- package/dist/react/ably-ai-transport-react.umd.cjs.map +1 -1
- package/dist/react/contexts/client-session-context.d.ts +2 -1
- package/dist/react/contexts/client-session-provider.d.ts +3 -0
- package/dist/react/index.d.ts +2 -1
- package/dist/react/internal/skipped-session.d.ts +8 -0
- package/dist/react/use-view.d.ts +3 -3
- package/dist/utils.d.ts +22 -54
- package/dist/vercel/ably-ai-transport-vercel.js +2297 -2026
- package/dist/vercel/ably-ai-transport-vercel.js.map +1 -1
- package/dist/vercel/ably-ai-transport-vercel.umd.cjs +1 -1
- package/dist/vercel/ably-ai-transport-vercel.umd.cjs.map +1 -1
- package/dist/vercel/codec/decode-lifecycle.d.ts +9 -0
- package/dist/vercel/codec/events.d.ts +1 -2
- package/dist/vercel/codec/fields.d.ts +44 -0
- package/dist/vercel/codec/fold-content.d.ts +16 -0
- package/dist/vercel/codec/fold-data.d.ts +16 -0
- package/dist/vercel/codec/fold-input.d.ts +67 -0
- package/dist/vercel/codec/fold-lifecycle.d.ts +16 -0
- package/dist/vercel/codec/fold-text.d.ts +16 -0
- package/dist/vercel/codec/fold-tool-input.d.ts +17 -0
- package/dist/vercel/codec/fold-tool-output.d.ts +16 -0
- package/dist/vercel/codec/index.d.ts +5 -30
- package/dist/vercel/codec/inputs.d.ts +11 -0
- package/dist/vercel/codec/outputs.d.ts +11 -0
- package/dist/vercel/codec/reducer-state.d.ts +121 -0
- package/dist/vercel/codec/reducer.d.ts +20 -102
- package/dist/vercel/codec/tool-transitions.d.ts +0 -6
- package/dist/vercel/codec/wire-data.d.ts +34 -0
- package/dist/vercel/index.d.ts +1 -0
- package/dist/vercel/react/ably-ai-transport-vercel-react.js +2013 -9500
- package/dist/vercel/react/ably-ai-transport-vercel-react.js.map +1 -1
- package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs +1 -70
- package/dist/vercel/react/ably-ai-transport-vercel-react.umd.cjs.map +1 -1
- package/dist/vercel/react/contexts/chat-transport-context.d.ts +2 -1
- package/dist/vercel/run-end-reason.d.ts +66 -11
- package/dist/vercel/tool-part.d.ts +21 -0
- package/dist/vercel/transport/chat-transport.d.ts +0 -2
- package/dist/vercel/transport/index.d.ts +1 -1
- package/dist/vercel/transport/run-output-stream.d.ts +6 -8
- package/dist/version.d.ts +1 -1
- package/package.json +2 -2
- package/src/constants.ts +2 -2
- package/src/core/agent.ts +43 -19
- package/src/core/channel-options.ts +89 -0
- package/src/core/codec/codec-event.ts +27 -0
- package/src/core/codec/decoder.ts +145 -21
- package/src/core/codec/define-codec.ts +432 -0
- package/src/core/codec/encoder.ts +13 -54
- package/src/core/codec/field-bag.ts +142 -0
- package/src/core/codec/fields.ts +193 -0
- package/src/core/codec/index.ts +43 -0
- package/src/core/codec/input-descriptor-decoder.ts +97 -0
- package/src/core/codec/input-descriptor-encoder.ts +150 -0
- package/src/core/codec/input-descriptors.ts +373 -0
- package/src/core/codec/output-descriptor-decoder.ts +139 -0
- package/src/core/codec/output-descriptor-encoder.ts +101 -0
- package/src/core/codec/output-descriptors.ts +307 -0
- package/src/core/codec/types.ts +99 -36
- package/src/core/codec/well-known-inputs.ts +96 -0
- package/src/core/transport/agent-session.ts +330 -589
- package/src/core/transport/agent-view.ts +738 -0
- package/src/core/transport/client-session.ts +74 -69
- package/src/core/transport/decode-fold.ts +57 -47
- package/src/core/transport/headers.ts +57 -4
- package/src/core/transport/index.ts +2 -1
- package/src/core/transport/invocation.ts +1 -1
- package/src/core/transport/load-history-pages.ts +220 -0
- package/src/core/transport/load-history.ts +63 -61
- package/src/core/transport/pipe-stream.ts +10 -1
- package/src/core/transport/run-manager.ts +25 -31
- package/src/core/transport/session-support.ts +96 -0
- package/src/core/transport/tree.ts +414 -47
- package/src/core/transport/types/agent.ts +129 -102
- package/src/core/transport/types/client.ts +49 -13
- package/src/core/transport/types/tree.ts +61 -12
- package/src/core/transport/types/view.ts +57 -28
- package/src/core/transport/view.ts +520 -172
- package/src/core/transport/wire-log.ts +189 -0
- package/src/errors.ts +10 -3
- package/src/index.ts +44 -11
- package/src/react/contexts/client-session-context.ts +1 -1
- package/src/react/contexts/client-session-provider.tsx +38 -2
- package/src/react/index.ts +2 -1
- package/src/react/internal/skipped-session.ts +62 -0
- package/src/react/use-client-session.ts +7 -30
- package/src/react/use-view.ts +3 -3
- package/src/utils.ts +31 -97
- package/src/vercel/codec/decode-lifecycle.ts +70 -0
- package/src/vercel/codec/events.ts +1 -3
- package/src/vercel/codec/fields.ts +58 -0
- package/src/vercel/codec/fold-content.ts +54 -0
- package/src/vercel/codec/fold-data.ts +46 -0
- package/src/vercel/codec/fold-input.ts +255 -0
- package/src/vercel/codec/fold-lifecycle.ts +85 -0
- package/src/vercel/codec/fold-text.ts +55 -0
- package/src/vercel/codec/fold-tool-input.ts +86 -0
- package/src/vercel/codec/fold-tool-output.ts +79 -0
- package/src/vercel/codec/index.ts +23 -63
- package/src/vercel/codec/inputs.ts +116 -0
- package/src/vercel/codec/outputs.ts +207 -0
- package/src/vercel/codec/reducer-state.ts +169 -0
- package/src/vercel/codec/reducer.ts +52 -838
- package/src/vercel/codec/tool-transitions.ts +1 -12
- package/src/vercel/codec/wire-data.ts +64 -0
- package/src/vercel/index.ts +1 -0
- package/src/vercel/react/contexts/chat-transport-context.ts +1 -1
- package/src/vercel/react/use-chat-transport.ts +8 -28
- package/src/vercel/react/use-message-sync.ts +5 -10
- package/src/vercel/run-end-reason.ts +95 -16
- package/src/vercel/tool-part.ts +25 -0
- package/src/vercel/transport/chat-transport.ts +10 -22
- package/src/vercel/transport/index.ts +1 -1
- package/src/vercel/transport/run-output-stream.ts +7 -8
- package/src/version.ts +1 -1
- package/dist/core/transport/branch-chain.d.ts +0 -43
- package/dist/core/transport/load-conversation.d.ts +0 -128
- package/dist/vercel/codec/decoder.d.ts +0 -9
- package/dist/vercel/codec/encoder.d.ts +0 -11
- package/src/core/transport/branch-chain.ts +0 -58
- package/src/core/transport/load-conversation.ts +0 -355
- package/src/vercel/codec/decoder.ts +0 -696
- package/src/vercel/codec/encoder.ts +0 -548
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declarative input descriptors — the single source of truth for a codec's
|
|
3
|
+
* `ai-input` wire mapping, the input-side sibling of {@link import('./output-descriptors.js')}.
|
|
4
|
+
*
|
|
5
|
+
* Inputs come in two cardinalities: a single domain input ↔ one wire message
|
|
6
|
+
* (the `event` construct), and a single domain message ↔ many atomic wire events
|
|
7
|
+
* (the `batch` construct — the input sibling of the output `stream`). Both are
|
|
8
|
+
* declared once per codec and consumed by the generic input encode/decode
|
|
9
|
+
* drivers, so adding an input is one descriptor entry rather than a pair of
|
|
10
|
+
* hand-synchronised switch arms.
|
|
11
|
+
*
|
|
12
|
+
* Authoring is cast-free: the {@link inputBuilder} factory hands the codec a
|
|
13
|
+
* `{ event, batch }` pair curried on the codec's input union, so every `data` /
|
|
14
|
+
* `fields` / `parts` callback receives the exact narrowed member. The descriptors
|
|
15
|
+
* are then erased to a heterogeneous {@link InputDescriptor} via a single
|
|
16
|
+
* documented cast at each constructor boundary — never in author code.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type * as Ably from 'ably';
|
|
20
|
+
|
|
21
|
+
import { wildcardMatcher } from './field-bag.js';
|
|
22
|
+
import type { DataCodec, FieldFor, HeaderField } from './fields.js';
|
|
23
|
+
import type { MessagePayload, WriteOptions } from './types.js';
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Type helpers
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
/** Resolve the input union member a `kind` literal selects. */
|
|
30
|
+
export type ResolveInput<U extends { kind: string }, K extends U['kind']> = Extract<U, { kind: K }>;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The payload an input `event`'s `fields` / `data` operate on. Inputs nest their
|
|
34
|
+
* domain data under `payload` (the `{ kind, codecMessageId, payload }` envelope of
|
|
35
|
+
* the well-known input variants), so a single event's spec is authored against the
|
|
36
|
+
* payload, and the driver wraps/unwraps the envelope. A member with no `payload`
|
|
37
|
+
* (a `wireOnly` signal) resolves to `never` — such an event declares no `fields` /
|
|
38
|
+
* `data`, so the payload type is never used.
|
|
39
|
+
* @template C - The narrowed input member.
|
|
40
|
+
*/
|
|
41
|
+
export type PayloadOf<C> = C extends { payload: infer P } ? P : never;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Resolve the part union member a `partType` literal selects, mirroring the
|
|
45
|
+
* output {@link import('./output-descriptors.js').ResolveType} curry one level down.
|
|
46
|
+
* An exact match wins; a wildcard literal (`data-*`) resolves to the template
|
|
47
|
+
* member (`data-${string}`).
|
|
48
|
+
* @template P - The part union.
|
|
49
|
+
* @template T - The selected `partType` literal (or a `*-*` wildcard).
|
|
50
|
+
*/
|
|
51
|
+
export type ResolvePart<P extends { type: string }, T extends string> =
|
|
52
|
+
Extract<P, { type: T }> extends never
|
|
53
|
+
? T extends `${infer Pre}-*`
|
|
54
|
+
? Extract<P, { type: `${Pre}-${string}` }>
|
|
55
|
+
: never
|
|
56
|
+
: Extract<P, { type: T }>;
|
|
57
|
+
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// Author-facing specs (narrowed)
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// Input driver core surface + contexts
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The encoder-core view the input encode driver receives: discrete publishes
|
|
68
|
+
* only — inputs never stream. The concrete {@link EncoderCore} satisfies this
|
|
69
|
+
* structurally.
|
|
70
|
+
*/
|
|
71
|
+
export interface InputEncoderCore {
|
|
72
|
+
/** Publish a single discrete message. */
|
|
73
|
+
publishDiscrete(payload: MessagePayload, opts?: WriteOptions): Promise<Ably.PublishResult>;
|
|
74
|
+
/** Publish multiple discrete messages atomically (the batch fan-out). */
|
|
75
|
+
publishDiscreteBatch(payloads: MessagePayload[], opts?: WriteOptions): Promise<Ably.PublishResult>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Per-write context passed to the input encode driver. */
|
|
79
|
+
export interface InputEncodeContext {
|
|
80
|
+
/** Per-write overrides (the wire codec-message-id is stamped here by the client session). */
|
|
81
|
+
opts: WriteOptions | undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Per-message context the input decode driver receives for one inbound `ai-input` message. */
|
|
85
|
+
export interface InputDecodeContext {
|
|
86
|
+
/** The codec `kind` header value (the input descriptor's dispatch key). */
|
|
87
|
+
codecKind: string;
|
|
88
|
+
/** The inbound message data. */
|
|
89
|
+
data: unknown;
|
|
90
|
+
/** The inbound codec-tier headers. */
|
|
91
|
+
codecHeaders: Record<string, string>;
|
|
92
|
+
/** The inbound transport-tier headers (role, codec-message-id, discrete marker). */
|
|
93
|
+
transportHeaders: Record<string, string>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The spec the input `event` construct accepts for member `C`. A member with
|
|
98
|
+
* no `payload` has nothing for `fields` / `data` to lens onto, so it may only
|
|
99
|
+
* be declared `wireOnly` or escape-hatched; the driver also rejects a
|
|
100
|
+
* payload-less encode at runtime.
|
|
101
|
+
* @template C - The narrowed input member.
|
|
102
|
+
*/
|
|
103
|
+
export type InputEventSpecFor<C> = [PayloadOf<C>] extends [never]
|
|
104
|
+
? Pick<InputEventSpec<C>, 'wireOnly'>
|
|
105
|
+
: InputEventSpec<C>;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* A single-event input descriptor spec, narrowed to input member `C`. `fields`
|
|
109
|
+
* and `data` operate on the member's {@link PayloadOf payload}; the driver wraps
|
|
110
|
+
* the `{ kind, codecMessageId, payload }` envelope on decode and unwraps it on
|
|
111
|
+
* encode. A `wireOnly` event carries no payload (kind only).
|
|
112
|
+
* @template C - The narrowed input member.
|
|
113
|
+
*/
|
|
114
|
+
export interface InputEventSpec<C> {
|
|
115
|
+
/**
|
|
116
|
+
* Declared header fields over the member's payload, written on encode and
|
|
117
|
+
* read on decode. Each field's key names both the wire header and the
|
|
118
|
+
* payload property it carries (see {@link FieldFor}). Omit for none.
|
|
119
|
+
*/
|
|
120
|
+
fields?: readonly FieldFor<PayloadOf<C>>[];
|
|
121
|
+
/** Wire `data` codec over the payload. Omit when the input carries no data (`data: ''`). */
|
|
122
|
+
data?: DataCodec<PayloadOf<C>>;
|
|
123
|
+
/** Wire-only signal: encode stamps only the `kind` header (empty data, no fields); decode yields `[]`. */
|
|
124
|
+
wireOnly?: boolean;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* A per-part wire mapping inside a {@link BatchSpec}, narrowed to part member `Q`.
|
|
129
|
+
* `fields` and `data` operate on the selected part; the batch driver fans the
|
|
130
|
+
* domain message out into one wire event per part and reassembles them in the
|
|
131
|
+
* reducer (merge by codec-message-id).
|
|
132
|
+
* @template Q - The narrowed part member.
|
|
133
|
+
*/
|
|
134
|
+
export interface PartSpec<Q> {
|
|
135
|
+
/**
|
|
136
|
+
* Declared header fields for this part, written on encode and read on
|
|
137
|
+
* decode. Each field's key names both the wire header and the part property
|
|
138
|
+
* it carries (see {@link FieldFor}). Omit for none.
|
|
139
|
+
*/
|
|
140
|
+
fields?: readonly FieldFor<Q>[];
|
|
141
|
+
/** Wire `data` codec over the part. Omit when the part carries no data. */
|
|
142
|
+
data?: DataCodec<Q>;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* The curried part sub-builder a {@link BatchSpec.parts} function receives.
|
|
147
|
+
* Mirrors the {@link inputBuilder} `event` curry one level down — and the
|
|
148
|
+
* output builder's wildcard idiom: `p(partType, spec)` narrows `spec` to the
|
|
149
|
+
* part member the literal selects, and a `-*` literal (e.g. `data-*`) declares
|
|
150
|
+
* a wildcard family whose dispatch predicate is derived from the literal's
|
|
151
|
+
* prefix, narrowing `spec` to the template member. Both forms are cast-free in
|
|
152
|
+
* author code.
|
|
153
|
+
* @template P - The part union.
|
|
154
|
+
*/
|
|
155
|
+
export type PartBuilder<P extends { type: string }> = <T extends P['type'] | `${string}-*`>(
|
|
156
|
+
partType: T,
|
|
157
|
+
spec: PartSpec<ResolvePart<P, T>>,
|
|
158
|
+
) => PartDescriptor;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Per-message wire headers a {@link BatchSpec.messageHeaders} stamps on every
|
|
162
|
+
* part of one batch. These carry metadata that belongs to the whole message
|
|
163
|
+
* rather than an individual part (e.g. the message id as a codec header, the
|
|
164
|
+
* sender role as a transport header), so the decode side can reconstruct the
|
|
165
|
+
* shared message envelope from any single part. Both tiers are optional.
|
|
166
|
+
*/
|
|
167
|
+
export interface BatchMessageHeaders {
|
|
168
|
+
/** Codec-tier headers stamped on every part (e.g. a per-message id). */
|
|
169
|
+
codecHeaders?: Record<string, string>;
|
|
170
|
+
/** Transport-tier headers stamped on every part (e.g. the sender role). */
|
|
171
|
+
transportHeaders?: Record<string, string>;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* The context a {@link BatchSpec.assemble} receives alongside one decoded part:
|
|
176
|
+
* the inbound header tiers of the wire event the part was decoded from. A batch
|
|
177
|
+
* fans out into N independent wire events, so each part arrives carrying the
|
|
178
|
+
* shared per-message headers ({@link BatchMessageHeaders}); `assemble` reads them
|
|
179
|
+
* to rebuild the message envelope (id, role, …) around its one part.
|
|
180
|
+
*/
|
|
181
|
+
export interface BatchAssembleContext {
|
|
182
|
+
/** The inbound codec-tier headers (carries the per-message codec headers). */
|
|
183
|
+
codecHeaders: Record<string, string>;
|
|
184
|
+
/** The inbound transport-tier headers (carries the per-message transport headers). */
|
|
185
|
+
transportHeaders: Record<string, string>;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* A multi-part input descriptor spec: one domain message decomposed into many
|
|
190
|
+
* atomic wire events sharing the input member's `kind` and codec-message-id, each
|
|
191
|
+
* carrying a `partType` sub-discriminator. The part union `P` is inferred from
|
|
192
|
+
* `explode`'s return type and threaded into `parts`'s curried `p` and `assemble`,
|
|
193
|
+
* so all three are cast-free in author code.
|
|
194
|
+
* @template C - The narrowed input member (the message-bearing input).
|
|
195
|
+
* @template P - The part union the message explodes into.
|
|
196
|
+
*/
|
|
197
|
+
export interface BatchSpec<C, P extends { type: string }> {
|
|
198
|
+
/** ENCODE: decompose the domain message into its parts. */
|
|
199
|
+
explode: (input: C) => readonly P[];
|
|
200
|
+
/** The `partType` sub-discriminator read off each part on encode. */
|
|
201
|
+
partTypeOf: (part: P) => string;
|
|
202
|
+
/** Declarative per-part wire mapping (a sub-table built via the curried `p`). */
|
|
203
|
+
parts: (p: PartBuilder<P>) => readonly PartDescriptor[];
|
|
204
|
+
/**
|
|
205
|
+
* ENCODE: per-message headers stamped on every part (the driver merges them
|
|
206
|
+
* onto each part's headers, including the ≥1-event fallback). Use for metadata
|
|
207
|
+
* shared by the whole message — e.g. a message-id codec header and a role
|
|
208
|
+
* transport header. Omit when parts carry no shared per-message metadata.
|
|
209
|
+
*/
|
|
210
|
+
messageHeaders?: (input: C) => BatchMessageHeaders;
|
|
211
|
+
/**
|
|
212
|
+
* DECODE: shape one decoded wire part into a one-part input (the reducer merges
|
|
213
|
+
* parts by codec-message-id). `ctx` exposes the inbound header tiers so the
|
|
214
|
+
* shared per-message metadata stamped by `messageHeaders` can be read back.
|
|
215
|
+
* The driver stamps only `kind`; the per-message identity rides the
|
|
216
|
+
* transport header and is recovered through `ctx` when needed.
|
|
217
|
+
*/
|
|
218
|
+
assemble: (part: P, ctx: BatchAssembleContext) => Omit<C, 'kind'>;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
// Erased descriptors (heterogeneous array elements)
|
|
223
|
+
// ---------------------------------------------------------------------------
|
|
224
|
+
|
|
225
|
+
/** A single-event input descriptor erased to the codec's input union `U`. */
|
|
226
|
+
export interface InputEventDescriptor {
|
|
227
|
+
/** Discriminator. */
|
|
228
|
+
construct: 'event';
|
|
229
|
+
/** The wire `kind` this input dispatches on. */
|
|
230
|
+
kind: string;
|
|
231
|
+
/** Declared header fields (read/written against the member's payload). */
|
|
232
|
+
fields: readonly HeaderField<unknown>[];
|
|
233
|
+
/** Wire `data` codec, if any. */
|
|
234
|
+
data?: DataCodec<unknown>;
|
|
235
|
+
/** Wire-only signal flag. */
|
|
236
|
+
wireOnly: boolean;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** An erased per-part wire mapping within a {@link BatchDescriptor}. */
|
|
240
|
+
export interface PartDescriptor {
|
|
241
|
+
/** The exact `partType` this part encodes as (the wildcard sentinel for a family). */
|
|
242
|
+
partType: string;
|
|
243
|
+
/** Decode-dispatch predicate for a wildcard part; absent for an exact part. */
|
|
244
|
+
match?: (partType: string) => boolean;
|
|
245
|
+
/** Declared header fields for this part. */
|
|
246
|
+
fields: readonly HeaderField<unknown>[];
|
|
247
|
+
/** Wire `data` codec over the part, if any. */
|
|
248
|
+
data?: DataCodec<unknown>;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** A multi-part (batch) input descriptor erased to the codec's input union `U`. */
|
|
252
|
+
export interface BatchDescriptor<U> {
|
|
253
|
+
/** Discriminator. */
|
|
254
|
+
construct: 'batch';
|
|
255
|
+
/** The wire `kind` every part of this batch shares. */
|
|
256
|
+
kind: string;
|
|
257
|
+
/** Decompose the domain input into its parts. */
|
|
258
|
+
explode: (input: U) => readonly unknown[];
|
|
259
|
+
/** Read the `partType` sub-discriminator off a part. */
|
|
260
|
+
partTypeOf: (part: unknown) => string;
|
|
261
|
+
/** The per-part wire mappings. */
|
|
262
|
+
parts: readonly PartDescriptor[];
|
|
263
|
+
/** Build the per-message headers stamped on every part, if any. */
|
|
264
|
+
messageHeaders?: (input: U) => BatchMessageHeaders;
|
|
265
|
+
/** Shape one decoded part into a one-part input (sans the driver-stamped `kind`). */
|
|
266
|
+
assemble: (part: unknown, ctx: BatchAssembleContext) => Omit<U, 'kind'>;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** An erased input descriptor — a single event or a multi-part batch. */
|
|
270
|
+
export type InputDescriptor<U> = InputEventDescriptor | BatchDescriptor<U>;
|
|
271
|
+
|
|
272
|
+
// ---------------------------------------------------------------------------
|
|
273
|
+
// Builder factory
|
|
274
|
+
// ---------------------------------------------------------------------------
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* The direction-scoped input builder `defineCodec` injects into the `input`
|
|
278
|
+
* config function — `event` and `batch`, both curried on the codec's input union
|
|
279
|
+
* so author entries narrow cast-free.
|
|
280
|
+
* @template U - The codec's input union.
|
|
281
|
+
*/
|
|
282
|
+
export interface InputBuilder<U extends { kind: string }> {
|
|
283
|
+
/**
|
|
284
|
+
* Declare a single-event input. Narrows `spec` to the member `kind` selects;
|
|
285
|
+
* `fields` / `data` operate on that member's payload.
|
|
286
|
+
* @param kind - The input member's `kind` literal (the wire dispatch key).
|
|
287
|
+
* @param spec - The narrowed input spec. Omit for a bare-`kind` input.
|
|
288
|
+
* @returns An erased {@link InputDescriptor}.
|
|
289
|
+
*/
|
|
290
|
+
event: <K extends U['kind']>(kind: K, spec?: InputEventSpecFor<ResolveInput<U, K>>) => InputDescriptor<U>;
|
|
291
|
+
/**
|
|
292
|
+
* Declare a multi-part (batch) input. Narrows the spec to the message-bearing
|
|
293
|
+
* member `kind` selects; `explode`'s return type fixes the part union `P`, which
|
|
294
|
+
* threads into `parts`'s curried `p` and `assemble` cast-free.
|
|
295
|
+
* @param kind - The input member's `kind` literal (the shared wire dispatch key).
|
|
296
|
+
* @param spec - The narrowed batch spec.
|
|
297
|
+
* @returns An erased {@link InputDescriptor}.
|
|
298
|
+
*/
|
|
299
|
+
batch: <K extends U['kind'], P extends { type: string }>(
|
|
300
|
+
kind: K,
|
|
301
|
+
spec: BatchSpec<ResolveInput<U, K>, P>,
|
|
302
|
+
) => InputDescriptor<U>;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Build the curried `{ event, batch }` input builder for a codec's input union.
|
|
307
|
+
* `defineCodec` calls this once and hands the result to the `input` config
|
|
308
|
+
* function; mirrors the output side's {@link import('./output-descriptors.js').outputBuilder}.
|
|
309
|
+
* @template U - The codec's input union.
|
|
310
|
+
* @returns The direction-scoped {@link InputBuilder}.
|
|
311
|
+
*/
|
|
312
|
+
export const inputBuilder = <U extends { kind: string }>(): InputBuilder<U> => {
|
|
313
|
+
// The internal part sub-builder reads only the structural `fields`/`data` off the
|
|
314
|
+
// spec; the narrowed part type is an author-facing concern, erased here.
|
|
315
|
+
interface ErasedPartSpec {
|
|
316
|
+
fields?: readonly HeaderField<unknown>[];
|
|
317
|
+
data?: DataCodec<unknown>;
|
|
318
|
+
}
|
|
319
|
+
const part = (partType: string, spec: ErasedPartSpec): PartDescriptor => {
|
|
320
|
+
// A `-*` literal declares a wildcard family; the dispatch predicate is
|
|
321
|
+
// derived from the literal so the two can never disagree (see wildcardMatcher).
|
|
322
|
+
const match = wildcardMatcher(partType);
|
|
323
|
+
return {
|
|
324
|
+
partType,
|
|
325
|
+
...(match ? { match } : {}),
|
|
326
|
+
fields: spec.fields ?? [],
|
|
327
|
+
data: spec.data,
|
|
328
|
+
};
|
|
329
|
+
};
|
|
330
|
+
// CAST: the part sub-builder is exposed to authors narrowed (PartBuilder<P>) so
|
|
331
|
+
// each `p(partType, spec)` narrows its spec to the selected part. Internally it
|
|
332
|
+
// reads only the structural `fields`/`data`, so the narrowed specs erase to the
|
|
333
|
+
// structural `ErasedPartSpec` at this boundary; a descriptor's part callbacks
|
|
334
|
+
// only ever run against the part their literal/predicate matched.
|
|
335
|
+
const p = part as unknown as PartBuilder<{ type: string }>;
|
|
336
|
+
|
|
337
|
+
return {
|
|
338
|
+
event: (kind, spec) => {
|
|
339
|
+
// CAST: the author-facing spec is conditional (a payload-less member may
|
|
340
|
+
// only declare wireOnly / escape hatches); both branches erase to one
|
|
341
|
+
// structural bag here, and the impl only reads optional properties off it.
|
|
342
|
+
const bag = spec as InputEventSpec<{ kind: string; payload: unknown }> | undefined;
|
|
343
|
+
// CAST: `spec` is narrowed to the member `kind` selects; the descriptor erases
|
|
344
|
+
// that to the codec's union `U` so heterogeneous descriptors share one array
|
|
345
|
+
// type. The drivers only ever invoke a descriptor's callbacks with the matching
|
|
346
|
+
// member, so the erasure is sound.
|
|
347
|
+
return {
|
|
348
|
+
construct: 'event',
|
|
349
|
+
kind,
|
|
350
|
+
fields: bag?.fields ?? [],
|
|
351
|
+
data: bag?.data,
|
|
352
|
+
wireOnly: bag?.wireOnly ?? false,
|
|
353
|
+
} as unknown as InputDescriptor<U>;
|
|
354
|
+
},
|
|
355
|
+
batch: (kind, spec) => {
|
|
356
|
+
// CAST: `p` is the single structural sub-builder; the author's `parts`
|
|
357
|
+
// function is typed to the narrowed `PartBuilder<P>`, so we hand it `p`
|
|
358
|
+
// through the same erasure the part specs already cross.
|
|
359
|
+
const parts = (spec.parts as (b: PartBuilder<{ type: string }>) => readonly PartDescriptor[])(p);
|
|
360
|
+
// CAST: see `event` — the narrowed batch spec (with its part-union-typed
|
|
361
|
+
// `explode`/`partTypeOf`/`assemble`) erases to `BatchDescriptor<U>`.
|
|
362
|
+
return {
|
|
363
|
+
construct: 'batch',
|
|
364
|
+
kind,
|
|
365
|
+
explode: spec.explode,
|
|
366
|
+
partTypeOf: spec.partTypeOf,
|
|
367
|
+
parts,
|
|
368
|
+
messageHeaders: spec.messageHeaders,
|
|
369
|
+
assemble: spec.assemble,
|
|
370
|
+
} as unknown as InputDescriptor<U>;
|
|
371
|
+
},
|
|
372
|
+
};
|
|
373
|
+
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic output decode driver over a descriptor set.
|
|
3
|
+
*
|
|
4
|
+
* Rebuilds events from the wire: discrete messages dispatch on the codec `kind`
|
|
5
|
+
* header, streamed families rebuild start/delta/end chunks from the stream
|
|
6
|
+
* tracker. Escape-hatch `decode`/`decodeEnd`/`decodeDiscrete` functions take
|
|
7
|
+
* over where a pure field rebuild can't express the mapping.
|
|
8
|
+
*
|
|
9
|
+
* This driver is pure chunk reconstruction — it carries no decode-time side
|
|
10
|
+
* effects (e.g. a codec's stream-join lifecycle repair). Those belong in the
|
|
11
|
+
* codec's hook layer wrapping this driver.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { stripUndefined } from '../../utils.js';
|
|
15
|
+
import { KIND_HEADER, partitionOutputEvents, readFields } from './field-bag.js';
|
|
16
|
+
import type {
|
|
17
|
+
OutputDecodeContext,
|
|
18
|
+
OutputDescriptor,
|
|
19
|
+
OutputEventDescriptor,
|
|
20
|
+
OutputStreamDescriptor,
|
|
21
|
+
} from './output-descriptors.js';
|
|
22
|
+
import type { StreamTrackerState } from './types.js';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The reconstructed chunk's domain discriminator field — the codec model's own
|
|
26
|
+
* `type` discriminator, per `CodecOutputEvent.type`. Distinct
|
|
27
|
+
* from {@link KIND_HEADER}: this is the rebuilt object's property, never the
|
|
28
|
+
* wire dispatch key.
|
|
29
|
+
*/
|
|
30
|
+
const TYPE_FIELD = 'type';
|
|
31
|
+
|
|
32
|
+
/** Decodes wire messages of union `U` from a descriptor set. */
|
|
33
|
+
export interface OutputDescriptorDecoder<U> {
|
|
34
|
+
/** Rebuild the chunk(s) emitted when a stream starts. */
|
|
35
|
+
buildStart(tracker: StreamTrackerState): U[];
|
|
36
|
+
/** Rebuild the chunk(s) for a stream delta. */
|
|
37
|
+
buildDelta(tracker: StreamTrackerState, delta: string): U[];
|
|
38
|
+
/** Rebuild the chunk(s) emitted when a stream completes. */
|
|
39
|
+
buildEnd(tracker: StreamTrackerState, closingCodecHeaders: Record<string, string>): U[];
|
|
40
|
+
/**
|
|
41
|
+
* Decode a discrete message by its codec `kind`.
|
|
42
|
+
* @param codecKind - The codec `kind` header value (the dispatch key).
|
|
43
|
+
* @param codecHeaders - The inbound codec-tier headers.
|
|
44
|
+
* @param transportHeaders - The inbound transport-tier headers.
|
|
45
|
+
* @param data - The inbound message data.
|
|
46
|
+
* @returns The decoded events (empty if no descriptor matches).
|
|
47
|
+
*/
|
|
48
|
+
decodeDiscrete(
|
|
49
|
+
codecKind: string,
|
|
50
|
+
codecHeaders: Record<string, string>,
|
|
51
|
+
transportHeaders: Record<string, string>,
|
|
52
|
+
data: unknown,
|
|
53
|
+
): U[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Build an output decode driver for a descriptor set.
|
|
58
|
+
* @template U - The codec's event union.
|
|
59
|
+
* @param descriptors - The descriptor set (events + streamed families).
|
|
60
|
+
* @returns An {@link OutputDescriptorDecoder} that reconstructs events from the wire.
|
|
61
|
+
*/
|
|
62
|
+
export const createOutputDescriptorDecoder = <U extends { type: string }>(
|
|
63
|
+
descriptors: readonly OutputDescriptor<U>[],
|
|
64
|
+
): OutputDescriptorDecoder<U> => {
|
|
65
|
+
const { discreteByType, wildcards } = partitionOutputEvents(descriptors);
|
|
66
|
+
const streamByKind = new Map<string, OutputStreamDescriptor<U>>();
|
|
67
|
+
|
|
68
|
+
for (const descriptor of descriptors) {
|
|
69
|
+
if (descriptor.construct === 'stream') {
|
|
70
|
+
streamByKind.set(descriptor.kind, descriptor);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// CAST: the rebuild seam — `bag` is assembled from the descriptor's declared
|
|
75
|
+
// fields and data codec, so it conforms to the matched member by construction.
|
|
76
|
+
// `typeValue` is the descriptor identity, written to the chunk's domain `type`
|
|
77
|
+
// field (not the wire `kind` header).
|
|
78
|
+
const rebuild = (typeValue: string, bag: Record<string, unknown>): U => {
|
|
79
|
+
bag[TYPE_FIELD] = typeValue;
|
|
80
|
+
return stripUndefined(bag) as unknown as U;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const decodeEvent = (descriptor: OutputEventDescriptor<U>, codecKind: string, ctx: OutputDecodeContext): U[] => {
|
|
84
|
+
const bag = readFields(descriptor.fields, ctx.codecHeaders);
|
|
85
|
+
if (descriptor.data) Object.assign(bag, descriptor.data.decode(ctx.data));
|
|
86
|
+
return [rebuild(codecKind, bag)];
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// Resolve the stream family from the tracker's `kind` header. An unrecognized
|
|
90
|
+
// family yields no descriptor, and the build* hooks return no events —
|
|
91
|
+
// unreachable in practice, since a tracker only exists because the encoder
|
|
92
|
+
// started a stream stamping a known family id.
|
|
93
|
+
const familyOf = (tracker: StreamTrackerState): OutputStreamDescriptor<U> | undefined =>
|
|
94
|
+
streamByKind.get(tracker.codecHeaders[KIND_HEADER] ?? '');
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
buildStart: (tracker) => {
|
|
98
|
+
const desc = familyOf(tracker);
|
|
99
|
+
if (!desc) return [];
|
|
100
|
+
const bag = readFields(desc.fields, tracker.codecHeaders);
|
|
101
|
+
bag[desc.idField] = tracker.streamId;
|
|
102
|
+
return [rebuild(desc.start, bag)];
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
buildDelta: (tracker, delta) => {
|
|
106
|
+
const desc = familyOf(tracker);
|
|
107
|
+
if (!desc) return [];
|
|
108
|
+
const bag: Record<string, unknown> = { [desc.idField]: tracker.streamId, [desc.deltaField]: delta };
|
|
109
|
+
return [rebuild(desc.delta, bag)];
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
buildEnd: (tracker, closingCodecHeaders) => {
|
|
113
|
+
const desc = familyOf(tracker);
|
|
114
|
+
if (!desc) return [];
|
|
115
|
+
if (desc.decodeEnd) {
|
|
116
|
+
return desc.decodeEnd({
|
|
117
|
+
streamId: tracker.streamId,
|
|
118
|
+
accumulated: tracker.accumulated,
|
|
119
|
+
codecHeaders: tracker.codecHeaders,
|
|
120
|
+
closingCodecHeaders,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
const bag = readFields(desc.fields, closingCodecHeaders);
|
|
124
|
+
bag[desc.idField] = tracker.streamId;
|
|
125
|
+
return [rebuild(desc.end, bag)];
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
decodeDiscrete: (codecKind, codecHeaders, transportHeaders, data) => {
|
|
129
|
+
const ctx: OutputDecodeContext = { codecKind, codecHeaders, transportHeaders, data };
|
|
130
|
+
const evt = discreteByType.get(codecKind);
|
|
131
|
+
if (evt) return decodeEvent(evt, codecKind, ctx);
|
|
132
|
+
const streamDesc = streamByKind.get(codecKind);
|
|
133
|
+
if (streamDesc?.decodeDiscrete) return streamDesc.decodeDiscrete(ctx);
|
|
134
|
+
const wildcard = wildcards.find((w) => w.match?.(codecKind));
|
|
135
|
+
if (wildcard) return decodeEvent(wildcard, codecKind, ctx);
|
|
136
|
+
return [];
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic output encode driver over a descriptor set.
|
|
3
|
+
*
|
|
4
|
+
* Builds a chunk→descriptor registry once, then routes each event: discrete
|
|
5
|
+
* descriptors publish a single message, streamed families drive
|
|
6
|
+
* start/append/close, and escape-hatch `encode` functions take over entirely.
|
|
7
|
+
* Headers are always built through the descriptor's declared fields (the `h`
|
|
8
|
+
* builder), so the imperative paths can't drift from the declarative ones.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import * as Ably from 'ably';
|
|
12
|
+
|
|
13
|
+
import { ErrorCode } from '../../errors.js';
|
|
14
|
+
import type { EncoderCore } from './encoder.js';
|
|
15
|
+
import { partitionOutputEvents, prop, writeFields } from './field-bag.js';
|
|
16
|
+
import type { HeaderBuilder, OutputDescriptor, OutputStreamDescriptor } from './output-descriptors.js';
|
|
17
|
+
import type { WriteOptions } from './types.js';
|
|
18
|
+
|
|
19
|
+
/** Per-write output encode context threaded from the encoder. */
|
|
20
|
+
export interface OutputEncodeContext {
|
|
21
|
+
/** The encoder's configured fallback message id, if any. */
|
|
22
|
+
messageId: string | undefined;
|
|
23
|
+
/** Per-write overrides. */
|
|
24
|
+
opts: WriteOptions | undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Encodes events of union `U` to channel operations via a descriptor set. */
|
|
28
|
+
export interface OutputDescriptorEncoder<U> {
|
|
29
|
+
/**
|
|
30
|
+
* Encode one event through its descriptor.
|
|
31
|
+
* @param chunk - The event to encode.
|
|
32
|
+
* @param core - The encoder core to publish/stream through.
|
|
33
|
+
* @param ctx - Per-write context (fallback message id, write options).
|
|
34
|
+
* @returns A promise resolving when the publish/stream operation completes.
|
|
35
|
+
*/
|
|
36
|
+
encode(chunk: U, core: EncoderCore, ctx: OutputEncodeContext): Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Build an output encode driver for a descriptor set bound to a wire message name.
|
|
41
|
+
* @template U - The codec's event union.
|
|
42
|
+
* @param descriptors - The descriptor set (events + streamed families).
|
|
43
|
+
* @param wireName - The Ably message name for this direction (`ai-output` / `ai-input`).
|
|
44
|
+
* @returns An {@link OutputDescriptorEncoder} routing each event through its descriptor.
|
|
45
|
+
*/
|
|
46
|
+
export const createOutputDescriptorEncoder = <U extends { type: string }>(
|
|
47
|
+
descriptors: readonly OutputDescriptor<U>[],
|
|
48
|
+
wireName: string,
|
|
49
|
+
): OutputDescriptorEncoder<U> => {
|
|
50
|
+
const { discreteByType, wildcards } = partitionOutputEvents(descriptors);
|
|
51
|
+
const streamByPhase = new Map<string, { descriptor: OutputStreamDescriptor<U>; phase: 'start' | 'delta' | 'end' }>();
|
|
52
|
+
|
|
53
|
+
for (const descriptor of descriptors) {
|
|
54
|
+
if (descriptor.construct === 'stream') {
|
|
55
|
+
streamByPhase.set(descriptor.start, { descriptor, phase: 'start' });
|
|
56
|
+
streamByPhase.set(descriptor.delta, { descriptor, phase: 'delta' });
|
|
57
|
+
streamByPhase.set(descriptor.end, { descriptor, phase: 'end' });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
encode: async (chunk, core, ctx) => {
|
|
63
|
+
const { type } = chunk;
|
|
64
|
+
|
|
65
|
+
const streamEntry = streamByPhase.get(type);
|
|
66
|
+
if (streamEntry) {
|
|
67
|
+
const { descriptor, phase } = streamEntry;
|
|
68
|
+
const h: HeaderBuilder<U> = (c, keys) => writeFields(descriptor.fields, descriptor.kind, c, keys);
|
|
69
|
+
// CAST: idField/deltaField are string-valued chunk keys by construction.
|
|
70
|
+
const streamId = prop(chunk, descriptor.idField) as string;
|
|
71
|
+
if (phase === 'start') {
|
|
72
|
+
await core.startStream(streamId, { name: wireName, data: '', codecHeaders: h(chunk) }, ctx.opts);
|
|
73
|
+
} else if (phase === 'delta') {
|
|
74
|
+
core.appendStream(streamId, prop(chunk, descriptor.deltaField) as string);
|
|
75
|
+
} else if (descriptor.onEnd) {
|
|
76
|
+
await descriptor.onEnd(chunk, core, { h, name: wireName, messageId: ctx.messageId, opts: ctx.opts });
|
|
77
|
+
} else {
|
|
78
|
+
await core.closeStream(streamId, { name: wireName, data: '', codecHeaders: h(chunk) });
|
|
79
|
+
}
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const descriptor = discreteByType.get(type) ?? wildcards.find((w) => w.match?.(type));
|
|
84
|
+
if (!descriptor) {
|
|
85
|
+
throw new Ably.ErrorInfo(`unable to publish; unsupported event type '${type}'`, ErrorCode.InvalidArgument, 400);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const h: HeaderBuilder<U> = (c, keys) => writeFields(descriptor.fields, c.type, c, keys);
|
|
89
|
+
if (descriptor.encode) {
|
|
90
|
+
await descriptor.encode(chunk, core, { h, name: wireName, messageId: ctx.messageId, opts: ctx.opts });
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const data = descriptor.data ? descriptor.data.encode(chunk) : '';
|
|
95
|
+
await core.publishDiscrete(
|
|
96
|
+
{ name: wireName, data, codecHeaders: h(chunk), ephemeral: descriptor.ephemeral?.(chunk) },
|
|
97
|
+
ctx.opts,
|
|
98
|
+
);
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
};
|