@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,84 @@
1
+ import type { DdsTypeDescriptor, GuardedTypeOf } from "./types.js";
2
+ /**
3
+ * Add a descriptor to the registry.
4
+ *
5
+ * DDS-TS 1.0 §8.7. Registering the same type twice is silent — a module
6
+ * imported from two places runs its registration twice and that is not
7
+ * an error. Registering a **different** type under a name already taken
8
+ * throws, and it has to: the second registration would otherwise decide
9
+ * what every `{ kind: "ref" }` to that name resolves to, and every
10
+ * consumer downstream would read one type's samples through another
11
+ * type's members with nothing reporting it.
12
+ *
13
+ * @throws TypeError when the name is taken by a structurally different
14
+ * descriptor.
15
+ */
16
+ export declare function registerType(d: DdsTypeDescriptor): void;
17
+ /**
18
+ * The descriptor registered under a qualified IDL name, or `undefined`.
19
+ *
20
+ * DDS-TS 1.0 §8.7. `undefined` and not a throw: a consumer that reached
21
+ * a `ref` to a type whose module was never imported is asking a question
22
+ * this can answer, and the caller decides whether that is a failure.
23
+ */
24
+ export declare function lookupType(qualifiedName: string): DdsTypeDescriptor | undefined;
25
+ /**
26
+ * The values of a sample's key members, in declaration order.
27
+ *
28
+ * DDS-TS 1.0 §8.7. Declaration order is the order the descriptor lists
29
+ * them, which is the order the IDL declared them and the order X-Types
30
+ * 1.3 §7.6.8 hashes them in — so the array can be fed to a key hash
31
+ * without being sorted first.
32
+ *
33
+ * The sample's shape is **not** checked. A caller that does not trust
34
+ * where the sample came from runs `descriptor.typeGuard` first; one that
35
+ * just decoded it through the generated codec already knows.
36
+ *
37
+ * A type with no `@key` member returns `[]`, which is the whole of the
38
+ * DDS single-instance case and not an error.
39
+ */
40
+ export declare function getKey<T>(sample: T, descriptor: DdsTypeDescriptor<T>): ReadonlyArray<unknown>;
41
+ /**
42
+ * The topic name the type is bound to, or `undefined`.
43
+ *
44
+ * DDS-TS 1.0 §8.7. `descriptor.topic` unchanged — a function rather than
45
+ * a property access so that a consumer holding only the reflection API
46
+ * has one, and so that the `@topic` binding has a name to be looked up
47
+ * by.
48
+ */
49
+ export declare function getTopic<T>(descriptor: DdsTypeDescriptor<T>): string | undefined;
50
+ /**
51
+ * A partial value, completed from the descriptor's `@default` values.
52
+ *
53
+ * DDS-TS 1.0 §8.7. Every member the descriptor records a
54
+ * non-`undefined` `default` for and `partial` does not carry is filled
55
+ * from that default. A member with neither stays **absent** — not
56
+ * `undefined`, absent — because a `@optional` member that is present and
57
+ * undefined is a different sample from one that is not there at all, and
58
+ * under XCDR2 the two are different octets.
59
+ */
60
+ export declare function withDefaults<T>(descriptor: DdsTypeDescriptor<T>, partial: Partial<T>): T;
61
+ /**
62
+ * Whether a rejection carries one of a set of IDL exceptions.
63
+ *
64
+ * DDS-TS 1.0 §8.7. A generated Operations-Profile client rejects with an
65
+ * `Error` whose `cause` is the decoded IDL exception payload, so a
66
+ * catch-handler asks this and gets the union of the listed types back as
67
+ * a narrowing:
68
+ *
69
+ * ```ts
70
+ * catch (err) {
71
+ * if (isOneOf(err, [NotFoundType, DeniedType])) {
72
+ * err.cause; // NotFound | Denied
73
+ * }
74
+ * }
75
+ * ```
76
+ *
77
+ * Never throws. A `catch` block is the one place where a helper that
78
+ * threw would replace the error being handled with its own, so anything
79
+ * that is not an `Error` — a thrown string, a rejected `undefined` — is
80
+ * simply `false`.
81
+ */
82
+ export declare function isOneOf<DS extends ReadonlyArray<DdsTypeDescriptor>>(err: unknown, descriptors: DS): err is Error & {
83
+ cause: GuardedTypeOf<DS[number]>;
84
+ };
@@ -0,0 +1,166 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright 2026 ZeroDDS Contributors
3
+ /**
4
+ * The reflection half of the descriptor runtime.
5
+ *
6
+ * DDS-TS 1.0 §8.7. A registry keyed by qualified IDL name, and the five
7
+ * functions that read a descriptor: the key members out of a sample, the
8
+ * topic name off the type, the defaults into a partial value, and — in
9
+ * `./dds_any.js` and `./equal.js` — the box and the key comparison.
10
+ *
11
+ * # Why there is a registry at all
12
+ *
13
+ * Because `DdsTypeRef.kind === "ref"` carries a **name** and not a
14
+ * descriptor (§8.2). That is what makes a descriptor graph acyclic and
15
+ * therefore JSON-roundtrippable, and it is what lets two structs refer to
16
+ * each other when neither can be initialised first. The cost is that a
17
+ * consumer walking the graph has to resolve those names somewhere, and
18
+ * this is the somewhere: every generated module ends with
19
+ * `registerType(<Type>Type)`, and {@link lookupType} answers.
20
+ */
21
+ import { descriptorsEqual } from "./equal.js";
22
+ /**
23
+ * Every descriptor registered so far, by qualified IDL name.
24
+ *
25
+ * One map for the module graph. Two bundles that each carry their own
26
+ * copy of this runtime have two registries and each sees its own
27
+ * registrations, which §8.7 names as out of scope for the equivalence
28
+ * check.
29
+ */
30
+ const registry = new Map();
31
+ /**
32
+ * Add a descriptor to the registry.
33
+ *
34
+ * DDS-TS 1.0 §8.7. Registering the same type twice is silent — a module
35
+ * imported from two places runs its registration twice and that is not
36
+ * an error. Registering a **different** type under a name already taken
37
+ * throws, and it has to: the second registration would otherwise decide
38
+ * what every `{ kind: "ref" }` to that name resolves to, and every
39
+ * consumer downstream would read one type's samples through another
40
+ * type's members with nothing reporting it.
41
+ *
42
+ * @throws TypeError when the name is taken by a structurally different
43
+ * descriptor.
44
+ */
45
+ export function registerType(d) {
46
+ const seen = registry.get(d.name);
47
+ if (seen === undefined) {
48
+ registry.set(d.name, d);
49
+ return;
50
+ }
51
+ if (descriptorsEqual(seen, d)) {
52
+ return;
53
+ }
54
+ throw new TypeError(`registerType: \`${d.name}\` is already registered as a different type`);
55
+ }
56
+ /**
57
+ * The descriptor registered under a qualified IDL name, or `undefined`.
58
+ *
59
+ * DDS-TS 1.0 §8.7. `undefined` and not a throw: a consumer that reached
60
+ * a `ref` to a type whose module was never imported is asking a question
61
+ * this can answer, and the caller decides whether that is a failure.
62
+ */
63
+ export function lookupType(qualifiedName) {
64
+ return registry.get(qualifiedName);
65
+ }
66
+ /**
67
+ * The values of a sample's key members, in declaration order.
68
+ *
69
+ * DDS-TS 1.0 §8.7. Declaration order is the order the descriptor lists
70
+ * them, which is the order the IDL declared them and the order X-Types
71
+ * 1.3 §7.6.8 hashes them in — so the array can be fed to a key hash
72
+ * without being sorted first.
73
+ *
74
+ * The sample's shape is **not** checked. A caller that does not trust
75
+ * where the sample came from runs `descriptor.typeGuard` first; one that
76
+ * just decoded it through the generated codec already knows.
77
+ *
78
+ * A type with no `@key` member returns `[]`, which is the whole of the
79
+ * DDS single-instance case and not an error.
80
+ */
81
+ export function getKey(sample, descriptor) {
82
+ const bag = sample;
83
+ const out = [];
84
+ for (const member of descriptor.fields) {
85
+ if (member.base === true && member.type.kind === "ref") {
86
+ // The keys of the base come first, as they are written first (§7.4.1.1.5.1).
87
+ const base = lookupType(member.type.name);
88
+ const held = bag[member.name];
89
+ if (base !== undefined && held !== undefined) {
90
+ out.push(...getKey(held, base));
91
+ }
92
+ continue;
93
+ }
94
+ if (member.key) {
95
+ out.push(bag[member.name]);
96
+ }
97
+ }
98
+ return out;
99
+ }
100
+ /**
101
+ * The topic name the type is bound to, or `undefined`.
102
+ *
103
+ * DDS-TS 1.0 §8.7. `descriptor.topic` unchanged — a function rather than
104
+ * a property access so that a consumer holding only the reflection API
105
+ * has one, and so that the `@topic` binding has a name to be looked up
106
+ * by.
107
+ */
108
+ export function getTopic(descriptor) {
109
+ return descriptor.topic;
110
+ }
111
+ /**
112
+ * A partial value, completed from the descriptor's `@default` values.
113
+ *
114
+ * DDS-TS 1.0 §8.7. Every member the descriptor records a
115
+ * non-`undefined` `default` for and `partial` does not carry is filled
116
+ * from that default. A member with neither stays **absent** — not
117
+ * `undefined`, absent — because a `@optional` member that is present and
118
+ * undefined is a different sample from one that is not there at all, and
119
+ * under XCDR2 the two are different octets.
120
+ */
121
+ export function withDefaults(descriptor, partial) {
122
+ const out = { ...partial };
123
+ for (const member of descriptor.fields) {
124
+ if (member.base === true && member.type.kind === "ref") {
125
+ const base = lookupType(member.type.name);
126
+ if (base !== undefined) {
127
+ const held = (out[member.name] ?? {});
128
+ out[member.name] = withDefaults(base, held);
129
+ }
130
+ continue;
131
+ }
132
+ if (member.default === undefined || member.name in out) {
133
+ continue;
134
+ }
135
+ out[member.name] = member.default;
136
+ }
137
+ return out;
138
+ }
139
+ /**
140
+ * Whether a rejection carries one of a set of IDL exceptions.
141
+ *
142
+ * DDS-TS 1.0 §8.7. A generated Operations-Profile client rejects with an
143
+ * `Error` whose `cause` is the decoded IDL exception payload, so a
144
+ * catch-handler asks this and gets the union of the listed types back as
145
+ * a narrowing:
146
+ *
147
+ * ```ts
148
+ * catch (err) {
149
+ * if (isOneOf(err, [NotFoundType, DeniedType])) {
150
+ * err.cause; // NotFound | Denied
151
+ * }
152
+ * }
153
+ * ```
154
+ *
155
+ * Never throws. A `catch` block is the one place where a helper that
156
+ * threw would replace the error being handled with its own, so anything
157
+ * that is not an `Error` — a thrown string, a rejected `undefined` — is
158
+ * simply `false`.
159
+ */
160
+ export function isOneOf(err, descriptors) {
161
+ if (!(err instanceof Error)) {
162
+ return false;
163
+ }
164
+ const cause = err.cause;
165
+ return descriptors.some((descriptor) => descriptor.typeGuard(cause));
166
+ }
@@ -0,0 +1,9 @@
1
+ import type { WasmBackend } from "./wasm.js";
2
+ /**
3
+ * An in-memory {@link WasmBackend}.
4
+ *
5
+ * Handles start at 1 and are never reused, so a handle that has been
6
+ * deleted names nothing else afterwards — which is what lets the
7
+ * binding layer's `RangeError` say which handle it was.
8
+ */
9
+ export declare function createTestBackend(): WasmBackend;
@@ -0,0 +1,173 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright 2026 ZeroDDS Contributors
3
+ /**
4
+ * A DDS data path in TypeScript, for measuring the binding layer against.
5
+ *
6
+ * DDS-TS 1.0 §C.5 requires a conformant WASM binding to pass four tests:
7
+ * a publish/subscribe round trip, a listener that fires, a delete that
8
+ * cascades, and a boxed `any` that survives the trip. Those are tests of
9
+ * {@link bindWasmBackend} — the handle checks, the cascade, the copies,
10
+ * the deferral — and running them against a real `.wasm` module would
11
+ * measure the module as well, in a test that needs a build of it to fail
12
+ * or pass.
13
+ *
14
+ * So this: a {@link WasmBackend} that keeps its samples in a `Map`.
15
+ * Every rule Annex C states about the *binding* is measurable against
16
+ * it, and a failure is the binding's, because there is no wire and no
17
+ * module.
18
+ *
19
+ * What it is not: a DDS implementation. There is no discovery, no
20
+ * reliability, no history depth, no QoS at all. Matching is by domain
21
+ * and topic name, delivery is immediate and in order, and the sample
22
+ * states it reports are the ones a fresh take produces. A test that
23
+ * needs any of the rest belongs against the Rust core.
24
+ *
25
+ * It is deliberately synchronous — `writeSample` runs the listener
26
+ * straight through, in the writer's own turn. That is precisely what
27
+ * §C.2.4 forbids a *binding* from doing, and it is what makes the
28
+ * deferral in {@link bindWasmBackend} measurable: if the wrapper ever
29
+ * stopped queueing, this backend would deliver inside `writeSample` and
30
+ * the C.5.2 test would see it.
31
+ */
32
+ import { makeDdsGuid } from "./wasm.js";
33
+ /** The twelve-octet prefix every writer of one backend instance shares. */
34
+ const GUID_PREFIX_LENGTH = 12;
35
+ /**
36
+ * An in-memory {@link WasmBackend}.
37
+ *
38
+ * Handles start at 1 and are never reused, so a handle that has been
39
+ * deleted names nothing else afterwards — which is what lets the
40
+ * binding layer's `RangeError` say which handle it was.
41
+ */
42
+ export function createTestBackend() {
43
+ const participants = new Map();
44
+ const topics = new Map();
45
+ const publishers = new Map();
46
+ const subscribers = new Map();
47
+ const writers = new Map();
48
+ const readers = new Map();
49
+ let nextHandle = 1;
50
+ /** The next unused handle. */
51
+ function fresh() {
52
+ const handle = nextHandle;
53
+ nextHandle += 1;
54
+ return handle;
55
+ }
56
+ /** The domain a topic sits in. */
57
+ function domainOf(topic) {
58
+ return participants.get(topic.participant)?.domainId ?? -1;
59
+ }
60
+ /** A GUID that names one writer and no other. */
61
+ function guidOf(writer) {
62
+ const prefix = new Uint8Array(GUID_PREFIX_LENGTH);
63
+ // Octets 0..1 are the vendor id in DDSI-RTPS 2.5 §8.2.4.1; the
64
+ // rest of the prefix is this backend's own numbering, which is all
65
+ // an in-memory data path has to tell its writers apart with.
66
+ prefix[0] = 0x01;
67
+ prefix[1] = 0x20;
68
+ prefix[8] = (writer >>> 24) & 0xff;
69
+ prefix[9] = (writer >>> 16) & 0xff;
70
+ prefix[10] = (writer >>> 8) & 0xff;
71
+ prefix[11] = writer & 0xff;
72
+ // The entity kind of a user-defined writer with key: DDSI-RTPS 2.5
73
+ // §9.3.1.2, `0x02` in the low octet.
74
+ return makeDdsGuid(prefix, ((writer << 8) >>> 0) | 0x02);
75
+ }
76
+ return {
77
+ createParticipant(domainId) {
78
+ const handle = fresh();
79
+ participants.set(handle, { domainId });
80
+ return handle;
81
+ },
82
+ deleteParticipant(p) {
83
+ participants.delete(p);
84
+ },
85
+ createTopic(p, name, typeName) {
86
+ const handle = fresh();
87
+ topics.set(handle, { participant: p, name, typeName });
88
+ return handle;
89
+ },
90
+ deleteTopic(t) {
91
+ topics.delete(t);
92
+ },
93
+ createPublisher(p) {
94
+ const handle = fresh();
95
+ publishers.set(handle, { participant: p });
96
+ return handle;
97
+ },
98
+ createSubscriber(p) {
99
+ const handle = fresh();
100
+ subscribers.set(handle, { participant: p });
101
+ return handle;
102
+ },
103
+ deletePublisher(pub) {
104
+ publishers.delete(pub);
105
+ },
106
+ deleteSubscriber(sub) {
107
+ subscribers.delete(sub);
108
+ },
109
+ createDataWriter(pub, topic) {
110
+ const handle = fresh();
111
+ writers.set(handle, { publisher: pub, topic, next: 1n });
112
+ return handle;
113
+ },
114
+ createDataReader(sub, topic) {
115
+ const handle = fresh();
116
+ readers.set(handle, { subscriber: sub, topic, queue: [], listener: null });
117
+ return handle;
118
+ },
119
+ writeSample(w, xcdr2) {
120
+ const writer = writers.get(w);
121
+ const topic = writer === undefined ? undefined : topics.get(writer.topic);
122
+ if (writer === undefined || topic === undefined) {
123
+ throw new RangeError(`writer ${w} has no topic`);
124
+ }
125
+ const sequenceNumber = writer.next;
126
+ writer.next += 1n;
127
+ const info = {
128
+ validData: true,
129
+ sampleState: "not_read",
130
+ viewState: "new",
131
+ instanceState: "alive",
132
+ sourceTimestampNs: BigInt(Date.now()) * 1000000n,
133
+ sequenceNumber,
134
+ publicationHandle: guidOf(w),
135
+ instanceHandle: BigInt(w),
136
+ };
137
+ for (const [handle, reader] of readers) {
138
+ const other = topics.get(reader.topic);
139
+ if (other === undefined ||
140
+ other.name !== topic.name ||
141
+ domainOf(other) !== domainOf(topic)) {
142
+ continue;
143
+ }
144
+ // A copy per reader: two readers that were handed one array
145
+ // would see each other's writes into it.
146
+ reader.queue.push({ bytes: xcdr2.slice(), info });
147
+ // Straight through, in the writer's turn. See the file comment:
148
+ // the deferral is the binding layer's and this is what proves
149
+ // it happens.
150
+ reader.listener?.(handle);
151
+ }
152
+ },
153
+ takeSamples(r, max) {
154
+ const reader = readers.get(r);
155
+ if (reader === undefined) {
156
+ return [];
157
+ }
158
+ return reader.queue.splice(0, max);
159
+ },
160
+ deleteDataWriter(w) {
161
+ writers.delete(w);
162
+ },
163
+ deleteDataReader(r) {
164
+ readers.delete(r);
165
+ },
166
+ setDataAvailableListener(r, cb) {
167
+ const reader = readers.get(r);
168
+ if (reader !== undefined) {
169
+ reader.listener = cb;
170
+ }
171
+ },
172
+ };
173
+ }
@@ -0,0 +1,246 @@
1
+ /**
2
+ * The descriptor side-table, as the values a TypeScript program can read.
3
+ *
4
+ * DDS-TS 1.0 chapter 8. A TypeScript `interface` is erased at compile
5
+ * time and a plain object literal carries no run-time type identity, so
6
+ * the identity travels beside the data as a first-class value: one
7
+ * {@link DdsTypeDescriptor} per IDL type, emitted by the code generator
8
+ * and registered with the process-wide registry
9
+ * (`registerType` in `./registry.js`).
10
+ *
11
+ * The content set is deliberately close to the `TypeObject` /
12
+ * `MinimalTypeObject` structures of OMG X-Types 1.3 §7.3 — extensibility,
13
+ * member ids, key membership, type references — so that a descriptor can
14
+ * be turned into a `TypeObject` and back without losing anything
15
+ * (DDS-TS 1.0 §6.2).
16
+ *
17
+ * Nothing in this file has a run-time representation: it is types only.
18
+ * The functions that read these shapes live in `./registry.js`,
19
+ * `./dds_any.js` and `./equal.js`.
20
+ */
21
+ /**
22
+ * The three extensibility kinds of OMG X-Types 1.3 §7.2.2.4.4.
23
+ *
24
+ * DDS-TS 1.0 §8.1. Spelled in lower case rather than as the
25
+ * `FINAL`/`APPENDABLE`/`MUTABLE` of the IDL annotation, because the
26
+ * descriptor is JSON-roundtrippable and these are its values.
27
+ */
28
+ export type ExtensibilityKind = "final" | "appendable" | "mutable";
29
+ /**
30
+ * The IDL primitive types, under the names the descriptor uses.
31
+ *
32
+ * DDS-TS 1.0 §8.2. `int16` and not `short`: the IDL 4.2 §7.4.1.4.4.2
33
+ * explicit-width spellings are unambiguous, and a descriptor that said
34
+ * `long` would be read differently by a C consumer than by a Java one.
35
+ * `longDouble` is the sixteen-octet carrier of §8.6, not a `number`.
36
+ *
37
+ * `int8` is one name past the list §8.2 gives. IDL 4.2 §7.4.1.4.4.2
38
+ * declares `int8` alongside `uint8`, the list names `octet` — which is
39
+ * `uint8` — and names nothing for the signed one. A generator meeting
40
+ * `int8 v;` has three choices: write `octet`, which is the wrong
41
+ * signedness; refuse the member, which drops a type IDL admits; or
42
+ * carry a name the list does not have. The third is the only one that
43
+ * does not lose something, and it is additive: every value §8.2 lists
44
+ * still means what §8.2 says.
45
+ */
46
+ export type PrimitiveName = "boolean" | "octet" | "int8" | "int16" | "uint16" | "int32" | "uint32" | "int64" | "uint64" | "float" | "double" | "longDouble" | "char" | "wchar";
47
+ /**
48
+ * The type of one member, in side-table form.
49
+ *
50
+ * DDS-TS 1.0 §8.2. A discriminated union over `kind`, which is what lets
51
+ * a consumer walk a descriptor graph with the compiler checking every
52
+ * arm.
53
+ *
54
+ * The `ref` kind carries the **qualified IDL name** of the referenced
55
+ * type and not a descriptor reference. That rule is normative and it
56
+ * buys three things: mutually-recursive struct and union references that
57
+ * no source-line order could initialise, a value graph with no cycles in
58
+ * it (so `JSON.stringify` round-trips a descriptor), and the same shape
59
+ * OMG X-Types 1.3 uses for `TypeIdentifier` lookup. Resolution happens at
60
+ * use time, through `lookupType(name)`.
61
+ *
62
+ * The `any` kind carries no name at all: the run-time identity of an IDL
63
+ * `any` is by construction not known statically, and a consumer that
64
+ * reaches one defers to the boxed value's `typeId` (DDS-TS 1.0 §7.3.4).
65
+ */
66
+ export type DdsTypeRef = {
67
+ kind: "primitive";
68
+ name: PrimitiveName;
69
+ }
70
+ /** A bitset member or a bitmask bit value; `width` is 1..64. */
71
+ | {
72
+ kind: "bitfield";
73
+ width: number;
74
+ } | {
75
+ kind: "string";
76
+ bound?: number | undefined;
77
+ wide: boolean;
78
+ } | {
79
+ kind: "sequence";
80
+ element: DdsTypeRef;
81
+ bound?: number | undefined;
82
+ } | {
83
+ kind: "array";
84
+ element: DdsTypeRef;
85
+ lengths: ReadonlyArray<number>;
86
+ } | {
87
+ kind: "map";
88
+ key: DdsTypeRef;
89
+ value: DdsTypeRef;
90
+ bound?: number | undefined;
91
+ }
92
+ /** A qualified IDL name; resolve through `lookupType`. */
93
+ | {
94
+ kind: "ref";
95
+ name: string;
96
+ } | {
97
+ kind: "any";
98
+ };
99
+ /**
100
+ * One member of a type, with the annotation metadata that governs it.
101
+ *
102
+ * DDS-TS 1.0 §8.3. `unit`, `min` and `max` are carried for a consumer
103
+ * that wants them and are **not** enforced by the type guard: a guard
104
+ * that rejected an out-of-range value would refuse a sample the wire
105
+ * accepted, which is a different contract from the one the IDL wrote.
106
+ */
107
+ export interface DdsMemberDescriptor {
108
+ /** The IDL member name, as declared. */
109
+ readonly name: string;
110
+ /** The effective member id — `@id`, `@autoid`, or the position. */
111
+ readonly id: number;
112
+ /** What the member holds. */
113
+ readonly type: DdsTypeRef;
114
+ /** `@key`. */
115
+ readonly key: boolean;
116
+ /** `@optional`. */
117
+ readonly optional: boolean;
118
+ /** `@must_understand`. */
119
+ readonly mustUnderstand: boolean;
120
+ /** `@default(V)`, or an enumerator's ordinal, or a bit position. */
121
+ readonly default?: unknown;
122
+ /** `@unit("…")` — documentation, never validated. */
123
+ readonly unit?: string | undefined;
124
+ /** `@min(V)` — documentation, never validated. */
125
+ readonly min?: unknown;
126
+ /** `@max(V)` — documentation, never validated. */
127
+ readonly max?: unknown;
128
+ /** `@hashid("…")`, the string the id was hashed from. */
129
+ readonly hashid?: string | undefined;
130
+ /**
131
+ * The case labels that select this member. Union descriptors only
132
+ * (DDS-TS 1.0 §7.5.1); `undefined` marks the `default:` case.
133
+ *
134
+ * `bigint` is one member past the element type §8.3 gives. A
135
+ * `union U switch (long long)` is legal IDL 4.2 and its case labels
136
+ * reach past 2^53, where a `number` holds two of them as one value;
137
+ * the arm's `discriminator` property is a `bigint` for that reason
138
+ * (§7.5), and a label list that could not carry one would disagree
139
+ * with the type it describes. Additive: every label §8.3 admits is
140
+ * still admitted.
141
+ */
142
+ readonly labels?: ReadonlyArray<number | string | boolean | bigint> | undefined;
143
+ /**
144
+ * A map key whose JavaScript equality is not value equality — a
145
+ * struct, an array, a sequence (DDS-TS 1.0 §7.9.1, diagnostic
146
+ * `DDS-TS-W003`). A `Map` keyed by one compares its keys by
147
+ * reference; the flag is what tells a consumer to compare with
148
+ * `equalKey` instead.
149
+ */
150
+ readonly keyEqualityHazard?: boolean | undefined;
151
+ /**
152
+ * The member is the base structure, the first member `parent` (XTypes
153
+ * 1.3 §7.5.1.4.1.3): its type is the `ref` to the base, and the members
154
+ * of that type are members of this one for `getKey` and `withDefaults`.
155
+ */
156
+ readonly base?: boolean | undefined;
157
+ }
158
+ /**
159
+ * Which IDL declaration a descriptor came from.
160
+ *
161
+ * DDS-TS 1.0 §8.3. `exception` and not a flag on `struct`: an exception
162
+ * maps to the same shape but crosses an Operations-Profile boundary
163
+ * differently, and a consumer switching on `kind` should see the
164
+ * difference.
165
+ */
166
+ export type DescriptorKind = "struct" | "union" | "enum" | "bitset" | "bitmask" | "exception" | "alias" | "interface";
167
+ /**
168
+ * One IDL type, as the value that carries its metadata.
169
+ *
170
+ * DDS-TS 1.0 §8.3. The generated `export const <Type>Type` constant.
171
+ *
172
+ * `typeGuard` is the one member that is not data: a function the
173
+ * generator emits beside the descriptor, checking the presence and type
174
+ * of every **required** member. It does not check `@unit`, `@min` or
175
+ * `@max`, and it does not require optional members.
176
+ */
177
+ export interface DdsTypeDescriptor<T = unknown> {
178
+ /** Which IDL declaration this is. */
179
+ readonly kind: DescriptorKind;
180
+ /** The qualified IDL name — `robot::Pose`, not `Pose`. */
181
+ readonly name: string;
182
+ /** `@final` / `@appendable` / `@mutable`, resolved. */
183
+ readonly extensibility: ExtensibilityKind;
184
+ /** `@nested` — whether the type may stand as a topic type. */
185
+ readonly nested: boolean;
186
+ /** `@topic("…")`, where the IDL bound one. */
187
+ readonly topic?: string | undefined;
188
+ /** `@autoid(SEQUENTIAL|HASH)`, where the IDL set one. */
189
+ readonly autoid?: "sequential" | "hash" | undefined;
190
+ /**
191
+ * `@bit_bound(N)`. An enumeration's effective bound (default 32), a
192
+ * bitmask's, or a bitset's total width.
193
+ */
194
+ readonly bitBound?: number | undefined;
195
+ /** A union with a `default:` case (DDS-TS 1.0 §7.5.1). */
196
+ readonly hasDefault?: boolean | undefined;
197
+ /**
198
+ * A union with no `default:` case whose listed cases do not exhaust
199
+ * the discriminator (DDS-TS 1.0 §7.5.2, diagnostic `DDS-TS-W004`).
200
+ */
201
+ readonly hasImplicitDefault?: boolean | undefined;
202
+ /**
203
+ * The members, in declaration order. Empty for an enumeration's
204
+ * holder-only forms and for an `interface`, whose operations live in
205
+ * a Service Descriptor instead.
206
+ */
207
+ readonly fields: ReadonlyArray<DdsMemberDescriptor>;
208
+ /** The emitted `is<Type>` predicate. */
209
+ readonly typeGuard: (v: unknown) => v is T;
210
+ }
211
+ /**
212
+ * The marker every generated IDL `exception` interface extends.
213
+ *
214
+ * DDS-TS 1.0 §8.3.1. A property and not a class: TypeScript's structural
215
+ * typing has nothing else that separates an exception payload from an
216
+ * ordinary struct at compile time, and this PSM emits no classes.
217
+ */
218
+ export interface DdsException {
219
+ readonly __dds_exception: true;
220
+ }
221
+ /**
222
+ * A boxed IDL `any`: a value together with the identity of its type.
223
+ *
224
+ * DDS-TS 1.0 §8.4 and §7.3.4. `typeId` is the qualified IDL name and
225
+ * indexes the registry, so `lookupType(box.typeId)` answers with the
226
+ * descriptor that describes `value`.
227
+ *
228
+ * Construct with `boxAny`, narrow with `unboxAny` — both in
229
+ * `./dds_any.js`. Reading `value` directly is legal and gives back
230
+ * `unknown`, which is the point: an IDL `any` mapped to TypeScript `any`
231
+ * would switch the checking off rather than carry the identity.
232
+ */
233
+ export interface DdsAny {
234
+ /** The qualified IDL name of the boxed value's type. */
235
+ readonly typeId: string;
236
+ /** The payload. `unknown`, and narrowed through the descriptor. */
237
+ readonly value: unknown;
238
+ }
239
+ /**
240
+ * The type a descriptor guards.
241
+ *
242
+ * DDS-TS 1.0 §8.7. `GuardedTypeOf<typeof PoseType>` is `Pose`, which is
243
+ * what lets `isOneOf` state the union of a list of exception
244
+ * descriptors as one narrowed type.
245
+ */
246
+ export type GuardedTypeOf<D> = D extends DdsTypeDescriptor<infer T> ? T : never;
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright 2026 ZeroDDS Contributors
3
+ export {};