@zerodds/wire 0.0.0-stage → 1.0.0-rc.7

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.
@@ -0,0 +1,52 @@
1
+ /**
2
+ * What can go wrong on the wire.
3
+ *
4
+ * The variants of `crates/wire/src/error.rs`, as errors. Every one
5
+ * names what was expected and where: a reader that returned a zero for
6
+ * a truncated stream would hand the caller a value that was never
7
+ * sent, and a writer that clamped an over-wide integer would put a
8
+ * different number on the wire than the caller asked for.
9
+ */
10
+ /** Base of every wire failure. */
11
+ export declare class WireError extends Error {
12
+ constructor(message: string);
13
+ }
14
+ /** The stream ended before the value did. */
15
+ export declare class Truncated extends WireError {
16
+ readonly at: number;
17
+ readonly needed: number;
18
+ readonly available: number;
19
+ constructor(at: number, needed: number, available: number);
20
+ }
21
+ /**
22
+ * A length field claims more than the stream holds.
23
+ *
24
+ * Separate from {@link Truncated}: a length that cannot be true is a
25
+ * malformed stream, not a short one, and allocating for it is how a
26
+ * reader turns a bad message into an out-of-memory.
27
+ */
28
+ export declare class ImpossibleLength extends WireError {
29
+ readonly at: number;
30
+ readonly claimed: number;
31
+ readonly available: number;
32
+ constructor(at: number, claimed: number, available: number);
33
+ }
34
+ /** A string held octets that are not UTF-8. */
35
+ export declare class NotUtf8 extends WireError {
36
+ readonly at: number;
37
+ constructor(at: number);
38
+ }
39
+ /**
40
+ * A value outside what its type admits.
41
+ *
42
+ * Thrown by the writer as well as the reader, which is where
43
+ * TypeScript is like Python and unlike Rust: an integer there is `i32`
44
+ * or `u64` and the compiler has already refused the value, while a
45
+ * `number` here holds anything a binary64 holds and a `bigint` is
46
+ * unbounded, so the check has to happen at the point of the write.
47
+ */
48
+ export declare class OutOfRange extends WireError {
49
+ readonly at: number;
50
+ readonly expected: string;
51
+ constructor(at: number, expected: string);
52
+ }
package/dist/error.js ADDED
@@ -0,0 +1,78 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright 2026 ZeroDDS Contributors
3
+ /**
4
+ * What can go wrong on the wire.
5
+ *
6
+ * The variants of `crates/wire/src/error.rs`, as errors. Every one
7
+ * names what was expected and where: a reader that returned a zero for
8
+ * a truncated stream would hand the caller a value that was never
9
+ * sent, and a writer that clamped an over-wide integer would put a
10
+ * different number on the wire than the caller asked for.
11
+ */
12
+ /** Base of every wire failure. */
13
+ export class WireError extends Error {
14
+ constructor(message) {
15
+ super(message);
16
+ // `Error` is not a normal base in older targets and the prototype
17
+ // is otherwise lost, which makes `instanceof` answer false for a
18
+ // subclass. Set explicitly rather than left to the target.
19
+ this.name = new.target.name;
20
+ Object.setPrototypeOf(this, new.target.prototype);
21
+ }
22
+ }
23
+ /** The stream ended before the value did. */
24
+ export class Truncated extends WireError {
25
+ at;
26
+ needed;
27
+ available;
28
+ constructor(at, needed, available) {
29
+ super(`at ${at}: ${needed} bytes needed, ${available} available`);
30
+ this.at = at;
31
+ this.needed = needed;
32
+ this.available = available;
33
+ }
34
+ }
35
+ /**
36
+ * A length field claims more than the stream holds.
37
+ *
38
+ * Separate from {@link Truncated}: a length that cannot be true is a
39
+ * malformed stream, not a short one, and allocating for it is how a
40
+ * reader turns a bad message into an out-of-memory.
41
+ */
42
+ export class ImpossibleLength extends WireError {
43
+ at;
44
+ claimed;
45
+ available;
46
+ constructor(at, claimed, available) {
47
+ super(`at ${at}: a length of ${claimed} with ${available} bytes left`);
48
+ this.at = at;
49
+ this.claimed = claimed;
50
+ this.available = available;
51
+ }
52
+ }
53
+ /** A string held octets that are not UTF-8. */
54
+ export class NotUtf8 extends WireError {
55
+ at;
56
+ constructor(at) {
57
+ super(`at ${at}: the string is not UTF-8`);
58
+ this.at = at;
59
+ }
60
+ }
61
+ /**
62
+ * A value outside what its type admits.
63
+ *
64
+ * Thrown by the writer as well as the reader, which is where
65
+ * TypeScript is like Python and unlike Rust: an integer there is `i32`
66
+ * or `u64` and the compiler has already refused the value, while a
67
+ * `number` here holds anything a binary64 holds and a `bigint` is
68
+ * unbounded, so the check has to happen at the point of the write.
69
+ */
70
+ export class OutOfRange extends WireError {
71
+ at;
72
+ expected;
73
+ constructor(at, expected) {
74
+ super(`at ${at}: expected ${expected}`);
75
+ this.at = at;
76
+ this.expected = expected;
77
+ }
78
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The sixteen octets a decimal string is, least-significant first.
3
+ *
4
+ * Least-significant first because that is the order a little-endian
5
+ * stream carries them and the order `Float128` is documented in;
6
+ * `Writer.writeF128` reverses the whole sixteen for a big-endian stream,
7
+ * which is CORBA Figure 9.2's rule and not two halves swapped in place.
8
+ *
9
+ * Accepts what the corpus writes: an optional sign, digits with an
10
+ * optional point, an optional `e±nn`, and the three names a decimal
11
+ * cannot spell — `inf`, `-inf`, `nan`. A negative zero keeps its sign,
12
+ * which is the one thing a value's magnitude cannot carry and is a
13
+ * different sixteen octets.
14
+ */
15
+ export declare function float128FromDecimalString(text: string): Uint8Array;
@@ -0,0 +1,181 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright 2026 ZeroDDS Contributors
3
+ /**
4
+ * IEEE 754 binary128 — the sixteen octets CDR gives `long double`.
5
+ *
6
+ * The TypeScript counterpart of `crates/float128`,
7
+ * `runtime/c/src/float128.c`, `runtime/cpp`'s and
8
+ * `runtime/java/.../Float128.java`: a sixth implementation of the same
9
+ * clauses and not a binding to any of them. CORBA 3.3 part 2 §9.3.1.3
10
+ * with Figure 9.2 gives the layout — one sign bit, a fifteen-bit
11
+ * exponent biased by 16383, a 112-bit stored fraction — and DDS-XTypes
12
+ * 1.3 Table 31 names it: quadruple precision, sixteen octets, aligned to
13
+ * eight.
14
+ *
15
+ * ## Why the decimal is parsed here and not by the generator
16
+ *
17
+ * A `number` is a binary64 and drops sixty bits of a binary128's
18
+ * significand, so a generated file that carried the value as a `number`
19
+ * would have lost it before any octet was written. The decimal string
20
+ * from the corpus reaches this file intact and is converted **exactly**:
21
+ * `BigInt` is arbitrary precision, so `M * 10^D` is a rational this
22
+ * computes with rather than approximates, and the single rounding is the
23
+ * one IEEE 754 §4.3.1 requires — to nearest, ties to even.
24
+ *
25
+ * A converter in the generator would be the Rust column answering for
26
+ * this one: the same argument `runtime/cpp/tests/corpus_bytes.cpp`
27
+ * makes, and the reason five columns each parse the decimal themselves.
28
+ */
29
+ /** Bits in the stored fraction. IEEE 754-2008 clause 3.6. */
30
+ const FRACTION_BITS = 112n;
31
+ /** What is added to the unbiased exponent to get the stored one. */
32
+ const EXPONENT_BIAS = 16383;
33
+ /** The stored exponent of an infinity or a NaN. */
34
+ const EXPONENT_MAX = 0x7fff;
35
+ /** The unbiased exponent of the smallest normal value. */
36
+ const MIN_NORMAL_EXPONENT = -16382;
37
+ /** `2^112`, the implied leading one in its stored position. */
38
+ const IMPLIED_ONE = 1n << FRACTION_BITS;
39
+ /**
40
+ * The sixteen octets a decimal string is, least-significant first.
41
+ *
42
+ * Least-significant first because that is the order a little-endian
43
+ * stream carries them and the order `Float128` is documented in;
44
+ * `Writer.writeF128` reverses the whole sixteen for a big-endian stream,
45
+ * which is CORBA Figure 9.2's rule and not two halves swapped in place.
46
+ *
47
+ * Accepts what the corpus writes: an optional sign, digits with an
48
+ * optional point, an optional `e±nn`, and the three names a decimal
49
+ * cannot spell — `inf`, `-inf`, `nan`. A negative zero keeps its sign,
50
+ * which is the one thing a value's magnitude cannot carry and is a
51
+ * different sixteen octets.
52
+ */
53
+ export function float128FromDecimalString(text) {
54
+ const trimmed = text.trim();
55
+ const lowered = trimmed.toLowerCase();
56
+ if (lowered === "inf" || lowered === "+inf" || lowered === "infinity") {
57
+ return octetsOf(false, EXPONENT_MAX, 0n);
58
+ }
59
+ if (lowered === "-inf" || lowered === "-infinity") {
60
+ return octetsOf(true, EXPONENT_MAX, 0n);
61
+ }
62
+ if (lowered === "nan" || lowered === "-nan") {
63
+ // A quiet NaN with no payload: the leading fraction bit set, which
64
+ // is IEEE 754 §6.2.1's quiet bit.
65
+ return octetsOf(lowered.startsWith("-"), EXPONENT_MAX, 1n << (FRACTION_BITS - 1n));
66
+ }
67
+ const parsed = decimalParts(trimmed);
68
+ if (parsed === undefined) {
69
+ throw new RangeError(`a decimal, not \`${text}\``);
70
+ }
71
+ const { negative, digits, exponent } = parsed;
72
+ if (digits === 0n) {
73
+ return octetsOf(negative, 0, 0n);
74
+ }
75
+ // The value as an exact fraction. Nothing is rounded until the
76
+ // significand is formed, which is what makes the result correctly
77
+ // rounded rather than rounded twice.
78
+ let numerator = digits;
79
+ let denominator = 1n;
80
+ if (exponent >= 0) {
81
+ numerator *= 10n ** BigInt(exponent);
82
+ }
83
+ else {
84
+ denominator = 10n ** BigInt(-exponent);
85
+ }
86
+ // The unbiased exponent: the largest `e` with `2^e <= value`.
87
+ let unbiased = bitLength(numerator) - bitLength(denominator);
88
+ while (!atLeastPowerOfTwo(numerator, denominator, unbiased)) {
89
+ unbiased -= 1;
90
+ }
91
+ while (atLeastPowerOfTwo(numerator, denominator, unbiased + 1)) {
92
+ unbiased += 1;
93
+ }
94
+ // Below the smallest normal the exponent is fixed and the significand
95
+ // is what loses bits, which is what a subnormal is. Clamping here
96
+ // rather than branching keeps one rounding for both cases: at the
97
+ // boundary the rounded significand comes out as exactly `2^112`,
98
+ // which is the smallest normal and is read back as one below.
99
+ let scale = Math.max(unbiased, MIN_NORMAL_EXPONENT);
100
+ let significand = roundedShiftedQuotient(numerator, denominator, 112 - scale);
101
+ if (significand >= IMPLIED_ONE << 1n) {
102
+ // Rounding carried past the top bit — `0x1.ffff…f` became `2`. One
103
+ // more exponent and one more rounding, which cannot carry again.
104
+ scale += 1;
105
+ significand = roundedShiftedQuotient(numerator, denominator, 112 - scale);
106
+ }
107
+ if (significand < IMPLIED_ONE) {
108
+ return octetsOf(negative, 0, significand);
109
+ }
110
+ const biased = scale + EXPONENT_BIAS;
111
+ if (biased >= EXPONENT_MAX) {
112
+ // Past the largest finite value. An infinity and not a saturated
113
+ // significand: IEEE 754 §7.4 says an overflow rounds to infinity
114
+ // under round-to-nearest, and the largest finite value would be a
115
+ // number the caller did not write.
116
+ return octetsOf(negative, EXPONENT_MAX, 0n);
117
+ }
118
+ return octetsOf(negative, biased, significand - IMPLIED_ONE);
119
+ }
120
+ /** The three parts of a decimal, or `undefined` where there are not three. */
121
+ function decimalParts(text) {
122
+ const match = /^([+-]?)(\d*)(?:\.(\d*))?(?:[eE]([+-]?\d+))?$/.exec(text);
123
+ if (match === null) {
124
+ return undefined;
125
+ }
126
+ const whole = match[2] ?? "";
127
+ const fraction = match[3] ?? "";
128
+ if (whole === "" && fraction === "") {
129
+ return undefined;
130
+ }
131
+ const exponent = Number(match[4] ?? "0") - fraction.length;
132
+ return {
133
+ negative: match[1] === "-",
134
+ digits: BigInt(`${whole}${fraction}` === "" ? "0" : `${whole}${fraction}`),
135
+ exponent,
136
+ };
137
+ }
138
+ /** Whether `numerator / denominator >= 2^power`. */
139
+ function atLeastPowerOfTwo(numerator, denominator, power) {
140
+ if (power >= 0) {
141
+ return numerator >= denominator << BigInt(power);
142
+ }
143
+ return numerator << BigInt(-power) >= denominator;
144
+ }
145
+ /**
146
+ * `round(numerator * 2^shift / denominator)`, to nearest with ties to
147
+ * even.
148
+ *
149
+ * The one rounding in the whole conversion. Doing it on the exact
150
+ * rational is what makes it correct: rounding the decimal to a `number`
151
+ * first and then to a binary128 rounds twice, and two roundings differ
152
+ * from one in the last place often enough that the corpus's
153
+ * `needs_all_of_them` is exactly the case that catches it.
154
+ */
155
+ function roundedShiftedQuotient(numerator, denominator, shift) {
156
+ const top = shift >= 0 ? numerator << BigInt(shift) : numerator;
157
+ const bottom = shift >= 0 ? denominator : denominator << BigInt(-shift);
158
+ const quotient = top / bottom;
159
+ const remainder = top % bottom;
160
+ const twice = remainder * 2n;
161
+ if (twice > bottom || (twice === bottom && (quotient & 1n) === 1n)) {
162
+ return quotient + 1n;
163
+ }
164
+ return quotient;
165
+ }
166
+ /** How many bits a positive `BigInt` occupies. */
167
+ function bitLength(value) {
168
+ return value === 0n ? 0 : value.toString(2).length;
169
+ }
170
+ /** The sixteen octets of one sign, biased exponent and stored fraction. */
171
+ function octetsOf(negative, biasedExponent, fraction) {
172
+ let bits = fraction | (BigInt(biasedExponent) << FRACTION_BITS);
173
+ if (negative) {
174
+ bits |= 1n << 127n;
175
+ }
176
+ const octets = new Uint8Array(16);
177
+ for (let index = 0; index < 16; index += 1) {
178
+ octets[index] = Number((bits >> BigInt(index * 8)) & 0xffn);
179
+ }
180
+ return octets;
181
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The TypeScript wire runtime, and the descriptor runtime beside it.
3
+ *
4
+ * Two halves under one import path. The first is XCDR itself — the
5
+ * reader, the writer, the policies the dialects differ on — written
6
+ * from DDS-XTypes 1.3 §7.4. The second is the DDS-TS 1.0 chapter 8
7
+ * descriptor runtime: the side-table values the generated code
8
+ * registers, the reflection functions that read them, and the Annex C
9
+ * WASM bindings.
10
+ *
11
+ * The spec names the descriptor package `@zerodds/types` and says the
12
+ * import path is implementation-defined while the exported names are
13
+ * not (§8, Runtime Package Name). Every name chapter 8 defines is
14
+ * exported here, unprefixed and unrenamed; the path is this one.
15
+ */
16
+ export * from "./endianness.js";
17
+ export * from "./error.js";
18
+ export * from "./float128.js";
19
+ export * from "./policy.js";
20
+ export * from "./reader.js";
21
+ export * from "./writer.js";
22
+ /** The descriptor runtime — DDS-TS 1.0 chapter 8 and Annex C. */
23
+ export * from "./branded.js";
24
+ export * from "./dds_any.js";
25
+ export * from "./equal.js";
26
+ export * from "./registry.js";
27
+ /** The operations runtime — DDS-TS 1.0 chapter 11. */
28
+ export * from "./operations.js";
29
+ export * from "./test_backend.js";
30
+ export * from "./types.js";
31
+ export * from "./wasm.js";
package/dist/index.js ADDED
@@ -0,0 +1,33 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright 2026 ZeroDDS Contributors
3
+ /**
4
+ * The TypeScript wire runtime, and the descriptor runtime beside it.
5
+ *
6
+ * Two halves under one import path. The first is XCDR itself — the
7
+ * reader, the writer, the policies the dialects differ on — written
8
+ * from DDS-XTypes 1.3 §7.4. The second is the DDS-TS 1.0 chapter 8
9
+ * descriptor runtime: the side-table values the generated code
10
+ * registers, the reflection functions that read them, and the Annex C
11
+ * WASM bindings.
12
+ *
13
+ * The spec names the descriptor package `@zerodds/types` and says the
14
+ * import path is implementation-defined while the exported names are
15
+ * not (§8, Runtime Package Name). Every name chapter 8 defines is
16
+ * exported here, unprefixed and unrenamed; the path is this one.
17
+ */
18
+ export * from "./endianness.js";
19
+ export * from "./error.js";
20
+ export * from "./float128.js";
21
+ export * from "./policy.js";
22
+ export * from "./reader.js";
23
+ export * from "./writer.js";
24
+ /** The descriptor runtime — DDS-TS 1.0 chapter 8 and Annex C. */
25
+ export * from "./branded.js";
26
+ export * from "./dds_any.js";
27
+ export * from "./equal.js";
28
+ export * from "./registry.js";
29
+ /** The operations runtime — DDS-TS 1.0 chapter 11. */
30
+ export * from "./operations.js";
31
+ export * from "./test_backend.js";
32
+ export * from "./types.js";
33
+ export * from "./wasm.js";
@@ -0,0 +1,180 @@
1
+ /**
2
+ * The Operations-Profile descriptor types — DDS-TS 1.0 §11.5.
3
+ *
4
+ * Chapter 8's descriptors describe *data*: a struct, a union, an
5
+ * enumeration. These describe *calls*: what an IDL `interface` declares,
6
+ * in the same side-table form and referring to the same
7
+ * {@link DdsTypeRef} graph, so one walk reads both.
8
+ *
9
+ * The generator emits one `ServiceDescriptor` constant per IDL
10
+ * interface, named `<IfaceName>Service`, beside the `<IfaceName>Client`
11
+ * and `<IfaceName>Handler` interfaces (§11.1). No class is emitted:
12
+ * "Concrete client construction is the responsibility of the runtime …
13
+ * which produces objects satisfying the client interface from the
14
+ * Service Descriptor."
15
+ *
16
+ * ## Why the shapes are not classes here either
17
+ *
18
+ * Every member is `readonly` and every collection is a
19
+ * `ReadonlyArray`. A descriptor is a value the generator writes once and
20
+ * every consumer reads; a mutable one would let a caller edit the
21
+ * metadata another caller is dispatching against, and nothing would
22
+ * report it.
23
+ *
24
+ * ## Where the exception narrowing lives
25
+ *
26
+ * §11.4 sends a caught rejection to `isOneOf`, which chapter 8 §8.7
27
+ * defines and `registry.ts` exports. It is not re-exported here: two
28
+ * modules exporting one name through `export *` is a name the barrel
29
+ * silently drops, and one definition of a type guard is the point of
30
+ * having it.
31
+ */
32
+ import type { DdsTypeDescriptor, DdsTypeRef } from "./types.js";
33
+ /**
34
+ * How a parameter is passed. DDS-TS 1.0 §11.5.
35
+ *
36
+ * The three IDL directions of grammar rule (87), spelled as the IDL
37
+ * spells them. `in` and `inout` reach the method's parameter list;
38
+ * `out` and `inout` reach the resolved value (§11.2).
39
+ */
40
+ export type ParameterMode = "in" | "out" | "inout";
41
+ /**
42
+ * One parameter of one operation. DDS-TS 1.0 §11.5.
43
+ */
44
+ export interface OperationParameterDescriptor {
45
+ /** The IDL parameter name, as declared. */
46
+ readonly name: string;
47
+ /** `in`, `out` or `inout`. */
48
+ readonly mode: ParameterMode;
49
+ /** What the parameter holds. */
50
+ readonly type: DdsTypeRef;
51
+ }
52
+ /**
53
+ * One operation of an interface. DDS-TS 1.0 §11.5.
54
+ *
55
+ * `returnType` is a {@link DdsTypeRef} or the marker `{ kind: "void" }`.
56
+ * A marker and not `undefined`: an absent property and a `void` return
57
+ * are different facts, and under `exactOptionalPropertyTypes` a consumer
58
+ * would have to know which one an older generator wrote.
59
+ *
60
+ * `parameters` holds **every** parameter in declaration order, including
61
+ * the `out` ones the client method does not take. The descriptor
62
+ * describes the IDL operation; the method signature is one projection of
63
+ * it, and a descriptor that carried only the projection could not
64
+ * reconstruct the reply.
65
+ */
66
+ export interface OperationDescriptor {
67
+ /** The IDL operation name, as declared. */
68
+ readonly name: string;
69
+ /** Whether the IDL wrote `oneway`. */
70
+ readonly oneway: boolean;
71
+ /** The declared return, or the `void` marker. */
72
+ readonly returnType: DdsTypeRef | {
73
+ kind: "void";
74
+ };
75
+ /** Every parameter, in declaration order. */
76
+ readonly parameters: ReadonlyArray<OperationParameterDescriptor>;
77
+ /** The descriptors of the exceptions the `raises` clause names. */
78
+ readonly raises: ReadonlyArray<DdsTypeDescriptor>;
79
+ }
80
+ /**
81
+ * One attribute of an interface. DDS-TS 1.0 §11.5.
82
+ *
83
+ * The client and handler interfaces carry `get_<name>` and, unless
84
+ * `readonly`, `set_<name>` (§11.3). `getRaises` and `setRaises` are the
85
+ * `getraises` and `setraises` clauses of IDL 4.2 grammar rule (89),
86
+ * which have nowhere else to go: neither reaches a method signature,
87
+ * because §11.4 rejects the promise rather than declaring a `throws`.
88
+ */
89
+ export interface AttributeDescriptor {
90
+ /** The IDL attribute name, as declared. */
91
+ readonly name: string;
92
+ /** Whether the IDL wrote `readonly`. */
93
+ readonly readonly: boolean;
94
+ /** What the attribute holds. */
95
+ readonly type: DdsTypeRef;
96
+ /** The descriptors of the exceptions `getraises` names. */
97
+ readonly getRaises: ReadonlyArray<DdsTypeDescriptor>;
98
+ /** The descriptors of the exceptions `setraises` names. */
99
+ readonly setRaises: ReadonlyArray<DdsTypeDescriptor>;
100
+ }
101
+ /**
102
+ * One IDL interface, as the metadata a runtime dispatches on.
103
+ * DDS-TS 1.0 §11.5.
104
+ *
105
+ * `TClient` and `THandler` appear in the type and in neither member.
106
+ * That is §11.5's shape and it is deliberate: the parameters tie a
107
+ * descriptor constant to the two interfaces the same generator run
108
+ * emitted, so a runtime factory typed
109
+ * `(d: ServiceDescriptor<C, H>) => C` hands back the client of *this*
110
+ * service rather than an `unknown`.
111
+ *
112
+ * `inherits` reflects the IDL inheritance specification (grammar rule
113
+ * (78)) in declaration order and nothing is flattened into the child:
114
+ * §11.6 says so outright, and the client interface already `extends` the
115
+ * parents', so a flattened copy would be a second, divergent statement
116
+ * of the same operation set.
117
+ */
118
+ export interface ServiceDescriptor<TClient, THandler> {
119
+ /** The qualified IDL name — `robot::Counter`, not `Counter`. */
120
+ readonly name: string;
121
+ /** The parent services, in declaration order. */
122
+ readonly inherits: ReadonlyArray<ServiceDescriptor<unknown, unknown>>;
123
+ /** The operations this interface declares itself. */
124
+ readonly operations: ReadonlyArray<OperationDescriptor>;
125
+ /** The attributes this interface declares itself. */
126
+ readonly attributes: ReadonlyArray<AttributeDescriptor>;
127
+ }
128
+ /**
129
+ * The two type parameters, kept reachable for a consumer that has a
130
+ * descriptor and wants the interfaces it was emitted with.
131
+ *
132
+ * `ClientOf<typeof CounterService>` is `CounterClient`. Without these a
133
+ * caller would have to name both halves again at every call site, which
134
+ * is the drift `ServiceDescriptor`'s parameters exist to prevent.
135
+ */
136
+ export type ClientOf<D> = D extends ServiceDescriptor<infer TClient, unknown> ? TClient : never;
137
+ /** The handler half of a service descriptor. See {@link ClientOf}. */
138
+ export type HandlerOf<D> = D extends ServiceDescriptor<unknown, infer THandler> ? THandler : never;
139
+ /**
140
+ * Whether an operation's reply carries more than a single value.
141
+ *
142
+ * §11.2: an operation that declares `out` or `inout` parameters resolves
143
+ * to an object literal carrying the return under `result` and one
144
+ * property per `out`/`inout` parameter. A runtime building the resolved
145
+ * value asks this before deciding which shape to construct, and asking
146
+ * it here rather than at each call site keeps one reading of the rule.
147
+ */
148
+ export declare function hasAggregatedResult(operation: OperationDescriptor): boolean;
149
+ /**
150
+ * The parameters a client method takes, in declaration order.
151
+ *
152
+ * §11.2: "parameter list is composed of the IDL `in` and `inout`
153
+ * parameters, in declaration order".
154
+ */
155
+ export declare function inputParameters(operation: OperationDescriptor): ReadonlyArray<OperationParameterDescriptor>;
156
+ /**
157
+ * The properties the resolved value carries beside `result`, in
158
+ * declaration order.
159
+ *
160
+ * §11.2: one property per `out`/`inout` parameter, named after the IDL
161
+ * parameter.
162
+ */
163
+ export declare function outputParameters(operation: OperationDescriptor): ReadonlyArray<OperationParameterDescriptor>;
164
+ /**
165
+ * An operation of a service or of any service it inherits from.
166
+ *
167
+ * §11.6 keeps the descriptor's `operations` array to what the interface
168
+ * declares itself, so a lookup by name has to walk `inherits`. Depth
169
+ * first and in declaration order, which is the order CORBA multiple
170
+ * inheritance resolves a name in; the first match wins, so a redeclared
171
+ * operation resolves to the most derived one.
172
+ *
173
+ * Returns `undefined` for a name no interface in the chain declares.
174
+ */
175
+ export declare function findOperation(service: ServiceDescriptor<unknown, unknown>, name: string): OperationDescriptor | undefined;
176
+ /**
177
+ * An attribute of a service or of any service it inherits from.
178
+ * See {@link findOperation} for the resolution order.
179
+ */
180
+ export declare function findAttribute(service: ServiceDescriptor<unknown, unknown>, name: string): AttributeDescriptor | undefined;
@@ -0,0 +1,74 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright 2026 ZeroDDS Contributors
3
+ /**
4
+ * Whether an operation's reply carries more than a single value.
5
+ *
6
+ * §11.2: an operation that declares `out` or `inout` parameters resolves
7
+ * to an object literal carrying the return under `result` and one
8
+ * property per `out`/`inout` parameter. A runtime building the resolved
9
+ * value asks this before deciding which shape to construct, and asking
10
+ * it here rather than at each call site keeps one reading of the rule.
11
+ */
12
+ export function hasAggregatedResult(operation) {
13
+ return operation.parameters.some((parameter) => parameter.mode === "out" || parameter.mode === "inout");
14
+ }
15
+ /**
16
+ * The parameters a client method takes, in declaration order.
17
+ *
18
+ * §11.2: "parameter list is composed of the IDL `in` and `inout`
19
+ * parameters, in declaration order".
20
+ */
21
+ export function inputParameters(operation) {
22
+ return operation.parameters.filter((parameter) => parameter.mode === "in" || parameter.mode === "inout");
23
+ }
24
+ /**
25
+ * The properties the resolved value carries beside `result`, in
26
+ * declaration order.
27
+ *
28
+ * §11.2: one property per `out`/`inout` parameter, named after the IDL
29
+ * parameter.
30
+ */
31
+ export function outputParameters(operation) {
32
+ return operation.parameters.filter((parameter) => parameter.mode === "out" || parameter.mode === "inout");
33
+ }
34
+ /**
35
+ * An operation of a service or of any service it inherits from.
36
+ *
37
+ * §11.6 keeps the descriptor's `operations` array to what the interface
38
+ * declares itself, so a lookup by name has to walk `inherits`. Depth
39
+ * first and in declaration order, which is the order CORBA multiple
40
+ * inheritance resolves a name in; the first match wins, so a redeclared
41
+ * operation resolves to the most derived one.
42
+ *
43
+ * Returns `undefined` for a name no interface in the chain declares.
44
+ */
45
+ export function findOperation(service, name) {
46
+ const own = service.operations.find((operation) => operation.name === name);
47
+ if (own !== undefined) {
48
+ return own;
49
+ }
50
+ for (const base of service.inherits) {
51
+ const inherited = findOperation(base, name);
52
+ if (inherited !== undefined) {
53
+ return inherited;
54
+ }
55
+ }
56
+ return undefined;
57
+ }
58
+ /**
59
+ * An attribute of a service or of any service it inherits from.
60
+ * See {@link findOperation} for the resolution order.
61
+ */
62
+ export function findAttribute(service, name) {
63
+ const own = service.attributes.find((attribute) => attribute.name === name);
64
+ if (own !== undefined) {
65
+ return own;
66
+ }
67
+ for (const base of service.inherits) {
68
+ const inherited = findAttribute(base, name);
69
+ if (inherited !== undefined) {
70
+ return inherited;
71
+ }
72
+ }
73
+ return undefined;
74
+ }