@wandelbots/nova-js 4.4.0 → 4.5.0-pr.321.1a3f8f3
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/dist/experimental/nats/index.d.mts +54 -3
- package/dist/experimental/nats/index.d.mts.map +1 -1
- package/dist/experimental/nats/index.mjs +65 -8
- package/dist/experimental/nats/index.mjs.map +1 -1
- package/package.json +2 -1
- package/src/experimental/nats/index.ts +12 -9
- package/src/lib/experimental/nats/NovaNatsClient.ts +145 -44
- package/src/lib/experimental/nats/generated/operations.ts +20 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { t as Nova } from "../../Nova-DdbUm145.mjs";
|
|
2
|
+
import { JsMsg } from "@nats-io/jetstream";
|
|
2
3
|
import { ConnectionOptions, Msg, NatsConnection } from "@nats-io/nats-core";
|
|
3
4
|
//#region src/lib/experimental/nats/buildNatsServerUrl.d.ts
|
|
4
5
|
/**
|
|
@@ -2858,6 +2859,23 @@ interface NatsSubscribePayloads {
|
|
|
2858
2859
|
"nova.v2.events.cells.{cell}.controllers.{controller}.deleted": RobotControllerDeletedEvent;
|
|
2859
2860
|
}
|
|
2860
2861
|
type NatsSubscribeSubject = keyof NatsSubscribePayloads;
|
|
2862
|
+
/**
|
|
2863
|
+
* The JetStream stream backing each subject that retains only its latest
|
|
2864
|
+
* message (`max_msgs_per_subject: 1` in src/asyncapi.yaml), so that
|
|
2865
|
+
* message is the subject's current state.
|
|
2866
|
+
*/
|
|
2867
|
+
declare const natsStreamBySubject: {
|
|
2868
|
+
readonly "nova.v2.cells.{cell}": "system-state";
|
|
2869
|
+
readonly "nova.v2.cells.{cell}.apps.{app}": "system-state";
|
|
2870
|
+
readonly "nova.v2.cells.{cell}.controllers.{controller}": "system-state";
|
|
2871
|
+
readonly "nova.v2.cells.{cell}.status": "system-state";
|
|
2872
|
+
readonly "nova.v2.system.status": "system-state";
|
|
2873
|
+
readonly "nova.v2.cells.{cell}.collision.setups.{setup}": "system-state";
|
|
2874
|
+
readonly "nova.v2.cells.{cell}.bus-ios.status": "system-state";
|
|
2875
|
+
readonly "nova.v2.cells.{cell}.controllers.{controller}.motion-groups.{motion-group}.description": "system-state";
|
|
2876
|
+
};
|
|
2877
|
+
/** Subjects whose current value can be replayed via `{ replayLast: true }`. */
|
|
2878
|
+
type NatsPersistedSubject = keyof typeof natsStreamBySubject;
|
|
2861
2879
|
/** Request payload types for subjects the client sends requests to. */
|
|
2862
2880
|
interface NatsRequestPayloads {
|
|
2863
2881
|
/**
|
|
@@ -3180,11 +3198,38 @@ type NovaNatsClientConfig = ConnectionOptions;
|
|
|
3180
3198
|
* a handler knows which entity a message belongs to. Typed per subject via
|
|
3181
3199
|
* the generated `NatsOperationParams`.
|
|
3182
3200
|
*/
|
|
3183
|
-
type NatsSubscribeMsg<K extends NatsSubscribeSubject> = Msg & {
|
|
3201
|
+
type NatsSubscribeMsg<K extends NatsSubscribeSubject> = (Msg | JsMsg) & {
|
|
3184
3202
|
subjectParams: NatsOperationParams[K];
|
|
3185
3203
|
};
|
|
3186
3204
|
type NatsMessageHandler<K extends NatsSubscribeSubject> = (payload: NatsSubscribePayloads[K], msg: NatsSubscribeMsg<K>) => void | Promise<void>;
|
|
3187
|
-
|
|
3205
|
+
/**
|
|
3206
|
+
* Extra options for {@link NovaNatsClient.subscribe}. `replayLast` is only
|
|
3207
|
+
* offered on subjects the spec marks as retaining their latest message, so
|
|
3208
|
+
* asking for a replay that could never arrive is a compile error.
|
|
3209
|
+
*/
|
|
3210
|
+
type NatsSubscribeOptions<K extends NatsSubscribeSubject> = K extends NatsPersistedSubject ? {
|
|
3211
|
+
/**
|
|
3212
|
+
* Deliver the subject's current value immediately on subscribe,
|
|
3213
|
+
* before any subsequent updates. With a wildcard subscription (e.g.
|
|
3214
|
+
* `{ cell: "*" }`) the current value of every matching subject is
|
|
3215
|
+
* delivered, not just one.
|
|
3216
|
+
*/
|
|
3217
|
+
replayLast?: boolean;
|
|
3218
|
+
/**
|
|
3219
|
+
* Called once every retained message has been passed to the handler,
|
|
3220
|
+
* i.e. when the handler has seen the subject's current state and
|
|
3221
|
+
* everything after it is a live update. Fires exactly once, and also
|
|
3222
|
+
* when there is nothing retained at all (an empty wildcard, e.g. no
|
|
3223
|
+
* apps installed) — where waiting for a first message would hang
|
|
3224
|
+
* forever. Only meaningful together with `replayLast`; without it
|
|
3225
|
+
* there is no retained state to wait for.
|
|
3226
|
+
*/
|
|
3227
|
+
onReplayComplete?: () => void;
|
|
3228
|
+
} : {
|
|
3229
|
+
replayLast?: never;
|
|
3230
|
+
onReplayComplete?: never;
|
|
3231
|
+
};
|
|
3232
|
+
type SubscribeArgs<K extends NatsSubscribeSubject> = keyof NatsOperationParams[K] extends never ? [handler: NatsMessageHandler<K>, opts?: NatsSubscribeOptions<K>] : [params: NatsOperationParams[K], handler: NatsMessageHandler<K>, opts?: NatsSubscribeOptions<K>];
|
|
3188
3233
|
/**
|
|
3189
3234
|
* Typed NATS client for the Wandelbots NOVA messaging API, generated from
|
|
3190
3235
|
* src/asyncapi.yaml (see scripts/generate-nats-client.ts).
|
|
@@ -3222,6 +3267,12 @@ declare class NovaNatsClient {
|
|
|
3222
3267
|
* nats.subscribe("nova.v2.cells.{cell}.status", { cell: "*" },
|
|
3223
3268
|
* (services, msg) => console.log(msg.subjectParams.cell, services))
|
|
3224
3269
|
*
|
|
3270
|
+
* On subjects that retain their latest message, pass `{ replayLast: true }`
|
|
3271
|
+
* to receive the current value immediately instead of waiting for the next
|
|
3272
|
+
* update — useful for a subscriber that starts after the last change:
|
|
3273
|
+
*
|
|
3274
|
+
* nats.subscribe("nova.v2.system.status", onStatus, { replayLast: true })
|
|
3275
|
+
*
|
|
3225
3276
|
* Returns a function that unsubscribes when called.
|
|
3226
3277
|
*/
|
|
3227
3278
|
subscribe<K extends NatsSubscribeSubject>(subject: K, ...args: SubscribeArgs<K>): Promise<() => void>;
|
|
@@ -3247,5 +3298,5 @@ declare class NovaNatsClient {
|
|
|
3247
3298
|
publish<K extends NatsPublishSubject>(subject: K, params: NatsOperationParams[K], payload: NatsPublishPayloads[K]): Promise<void>;
|
|
3248
3299
|
}
|
|
3249
3300
|
//#endregion
|
|
3250
|
-
export { type AbbController, type AddVirtualControllerMotionGroupRequest, type App, type AppCreatedEvent, type AppDeletedEvent, type AppEventData, type AppUpdatedEvent, type Box, type BusIOsState, type BusIOsStateEnum, type Capacity, type Capsule, type CartesianLimits, type Cell, type CellCreatedEvent, type CellCycleEvent, type CellDeletedEvent, type CellDescription, type CellEventData, type CellName, type CellUpdatedEvent, type CloudEvent, type Collider, type ColliderDictionary, type ColliderDictionary1, type ColliderDictionary2, type CollisionMotionGroupLink, type CollisionMotionGroupTool, type CollisionMotionGroupTool1, type CollisionSetup, type ContainerEnvironment, type ContainerImage, type ContainerResources, type ContainerStorage, type ControllerNetworkInterface, type ConvexHull, type Cylinder, type DHParameter, type Execute, type Execute1, type FanucController, type IOBooleanValue, type IOFloatValue, type IOIntegerValue, type IOValue, type ImageCredentials, type JoggingDetails, type JoggingPausedByUser, type JoggingPausedNearCollision, type JoggingPausedNearJointLimit, type JoggingPausedNearSingularity, type JoggingPausedOnIO, type JoggingRunning, type JointLimits, type JointTypeEnum, type Joints, type Joints1, type Joints2, type Joints3, type KukaController, type LimitRange, type LimitSet, type LinkChain, type LinkChain1, type ListIOValuesResponse, type Location, type Manufacturer, type MotionGroupDescription, type MotionGroupFromJSON, type MotionGroupFromModel, type MotionGroupModel, type MotionGroupState, type MotionGroupState1, type MotionGroupState_JointLimitReached, type MotionGroupState_JointLimitReached1, type NatsErrorPayload, type NatsOperationParams, type NatsPublishPayloads, type NatsPublishSubject, type NatsReplyPayloads, type NatsRequestPayloads, type NatsRequestSubject, type NatsSubscribeMsg, type NatsSubscribePayloads, type NatsSubscribeSubject, type NetworkState, type NetworkStatusChangedEvent, NovaNatsClient, type NovaNatsClientConfig, type OperatingState, type OperationLimits, type OperationMode, type Payload, type PayloadDictionary, type Plane, type Pose, type Pose1, type Pose2, type Pose3, type Pose4, type Pose5, type ProgramRunState, type ProgramRunState1, type ProgramStatus, type Rectangle, type RectangularCapsule, type RobotController, type RobotControllerCreatedEvent, type RobotControllerDeletedEvent, type RobotControllerEventData, type RobotControllerState, type RobotControllerUpdatedEvent, type RobotSystemMode, type RotationVector, type SafetyStateType, type SafetyToolColliders, type SelectIOs, type ServiceGroup, type ServiceStatus, type ServiceStatusList, type ServiceStatusPhase, type ServiceStatusSeverity, type Sphere, type StreamIOValuesResponse, type SystemUpdateCompletedEvent, type SystemUpdateStartedEvent, type TcpOffset, type TcpOffsetDictionary, type TrajectoryDetails, type TrajectoryEnded, type TrajectoryPausedByUser, type TrajectoryPausedOnIO, type TrajectoryRunning, type TrajectoryWaitForIO, type UniversalrobotsController, type Vector3D, type Vector3D1, type Vector3D2, type VirtualController, type YaskawaController, buildNatsServerUrl };
|
|
3301
|
+
export { type AbbController, type AddVirtualControllerMotionGroupRequest, type App, type AppCreatedEvent, type AppDeletedEvent, type AppEventData, type AppUpdatedEvent, type Box, type BusIOsState, type BusIOsStateEnum, type Capacity, type Capsule, type CartesianLimits, type Cell, type CellCreatedEvent, type CellCycleEvent, type CellDeletedEvent, type CellDescription, type CellEventData, type CellName, type CellUpdatedEvent, type CloudEvent, type Collider, type ColliderDictionary, type ColliderDictionary1, type ColliderDictionary2, type CollisionMotionGroupLink, type CollisionMotionGroupTool, type CollisionMotionGroupTool1, type CollisionSetup, type ContainerEnvironment, type ContainerImage, type ContainerResources, type ContainerStorage, type ControllerNetworkInterface, type ConvexHull, type Cylinder, type DHParameter, type Execute, type Execute1, type FanucController, type IOBooleanValue, type IOFloatValue, type IOIntegerValue, type IOValue, type ImageCredentials, type JoggingDetails, type JoggingPausedByUser, type JoggingPausedNearCollision, type JoggingPausedNearJointLimit, type JoggingPausedNearSingularity, type JoggingPausedOnIO, type JoggingRunning, type JointLimits, type JointTypeEnum, type Joints, type Joints1, type Joints2, type Joints3, type KukaController, type LimitRange, type LimitSet, type LinkChain, type LinkChain1, type ListIOValuesResponse, type Location, type Manufacturer, type MotionGroupDescription, type MotionGroupFromJSON, type MotionGroupFromModel, type MotionGroupModel, type MotionGroupState, type MotionGroupState1, type MotionGroupState_JointLimitReached, type MotionGroupState_JointLimitReached1, type NatsErrorPayload, type NatsOperationParams, type NatsPersistedSubject, type NatsPublishPayloads, type NatsPublishSubject, type NatsReplyPayloads, type NatsRequestPayloads, type NatsRequestSubject, type NatsSubscribeMsg, type NatsSubscribeOptions, type NatsSubscribePayloads, type NatsSubscribeSubject, type NetworkState, type NetworkStatusChangedEvent, NovaNatsClient, type NovaNatsClientConfig, type OperatingState, type OperationLimits, type OperationMode, type Payload, type PayloadDictionary, type Plane, type Pose, type Pose1, type Pose2, type Pose3, type Pose4, type Pose5, type ProgramRunState, type ProgramRunState1, type ProgramStatus, type Rectangle, type RectangularCapsule, type RobotController, type RobotControllerCreatedEvent, type RobotControllerDeletedEvent, type RobotControllerEventData, type RobotControllerState, type RobotControllerUpdatedEvent, type RobotSystemMode, type RotationVector, type SafetyStateType, type SafetyToolColliders, type SelectIOs, type ServiceGroup, type ServiceStatus, type ServiceStatusList, type ServiceStatusPhase, type ServiceStatusSeverity, type Sphere, type StreamIOValuesResponse, type SystemUpdateCompletedEvent, type SystemUpdateStartedEvent, type TcpOffset, type TcpOffsetDictionary, type TrajectoryDetails, type TrajectoryEnded, type TrajectoryPausedByUser, type TrajectoryPausedOnIO, type TrajectoryRunning, type TrajectoryWaitForIO, type UniversalrobotsController, type Vector3D, type Vector3D1, type Vector3D2, type VirtualController, type YaskawaController, buildNatsServerUrl, natsStreamBySubject };
|
|
3251
3302
|
//# sourceMappingURL=index.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/lib/experimental/nats/buildNatsServerUrl.ts","../../../src/lib/experimental/nats/generated/types.ts","../../../src/lib/experimental/nats/generated/operations.ts","../../../src/lib/experimental/nats/NovaNatsClient.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/lib/experimental/nats/buildNatsServerUrl.ts","../../../src/lib/experimental/nats/generated/types.ts","../../../src/lib/experimental/nats/generated/operations.ts","../../../src/lib/experimental/nats/NovaNatsClient.ts"],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { a as parseNovaInstanceUrl } from "../../converters-DnG1fX23.mjs";
|
|
2
|
+
import { DeliverPolicy, jetstream } from "@nats-io/jetstream";
|
|
2
3
|
import { wsconnect } from "@nats-io/nats-core";
|
|
3
4
|
//#region src/lib/experimental/nats/buildNatsServerUrl.ts
|
|
4
5
|
/**
|
|
@@ -14,6 +15,23 @@ function buildNatsServerUrl(instanceUrl) {
|
|
|
14
15
|
return `${url.protocol === "https:" ? "wss:" : "ws:"}//${url.host}/api/nats`;
|
|
15
16
|
}
|
|
16
17
|
//#endregion
|
|
18
|
+
//#region src/lib/experimental/nats/generated/operations.ts
|
|
19
|
+
/**
|
|
20
|
+
* The JetStream stream backing each subject that retains only its latest
|
|
21
|
+
* message (`max_msgs_per_subject: 1` in src/asyncapi.yaml), so that
|
|
22
|
+
* message is the subject's current state.
|
|
23
|
+
*/
|
|
24
|
+
const natsStreamBySubject = {
|
|
25
|
+
"nova.v2.cells.{cell}": "system-state",
|
|
26
|
+
"nova.v2.cells.{cell}.apps.{app}": "system-state",
|
|
27
|
+
"nova.v2.cells.{cell}.controllers.{controller}": "system-state",
|
|
28
|
+
"nova.v2.cells.{cell}.status": "system-state",
|
|
29
|
+
"nova.v2.system.status": "system-state",
|
|
30
|
+
"nova.v2.cells.{cell}.collision.setups.{setup}": "system-state",
|
|
31
|
+
"nova.v2.cells.{cell}.bus-ios.status": "system-state",
|
|
32
|
+
"nova.v2.cells.{cell}.controllers.{controller}.motion-groups.{motion-group}.description": "system-state"
|
|
33
|
+
};
|
|
34
|
+
//#endregion
|
|
17
35
|
//#region src/lib/experimental/nats/buildSubject.ts
|
|
18
36
|
/**
|
|
19
37
|
* Builds a NATS subject from an AsyncAPI-style channel address template
|
|
@@ -112,26 +130,65 @@ var NovaNatsClient = class {
|
|
|
112
130
|
* nats.subscribe("nova.v2.cells.{cell}.status", { cell: "*" },
|
|
113
131
|
* (services, msg) => console.log(msg.subjectParams.cell, services))
|
|
114
132
|
*
|
|
133
|
+
* On subjects that retain their latest message, pass `{ replayLast: true }`
|
|
134
|
+
* to receive the current value immediately instead of waiting for the next
|
|
135
|
+
* update — useful for a subscriber that starts after the last change:
|
|
136
|
+
*
|
|
137
|
+
* nats.subscribe("nova.v2.system.status", onStatus, { replayLast: true })
|
|
138
|
+
*
|
|
115
139
|
* Returns a function that unsubscribes when called.
|
|
116
140
|
*/
|
|
117
141
|
async subscribe(subject, ...args) {
|
|
118
|
-
const
|
|
142
|
+
const hasParams = typeof args[0] !== "function";
|
|
143
|
+
const params = hasParams ? args[0] : {};
|
|
144
|
+
const handler = hasParams ? args[1] : args[0];
|
|
145
|
+
const opts = hasParams ? args[2] : args[1];
|
|
119
146
|
const nc = await this.connect();
|
|
120
147
|
const resolvedSubject = buildSubject(subject, params);
|
|
121
|
-
const sub = nc.subscribe(resolvedSubject);
|
|
122
148
|
const paramPositions = [];
|
|
123
149
|
for (const [index, token] of subject.split(".").entries()) if (token.startsWith("{") && token.endsWith("}")) paramPositions.push([token.slice(1, -1), index]);
|
|
124
|
-
|
|
125
|
-
|
|
150
|
+
const deliver = async (msg) => {
|
|
151
|
+
try {
|
|
126
152
|
const subjectTokens = msg.subject.split(".");
|
|
127
153
|
const subjectParams = Object.fromEntries(paramPositions.map(([name, index]) => [name, subjectTokens[index] ?? ""]));
|
|
128
154
|
await handler(msg.json(), Object.assign(msg, { subjectParams }));
|
|
129
155
|
} catch (err) {
|
|
130
156
|
console.error(`Error handling NATS message on subject "${resolvedSubject}"`, err);
|
|
131
157
|
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
|
|
158
|
+
};
|
|
159
|
+
const deliverAll = (messages, afterDeliver) => {
|
|
160
|
+
(async () => {
|
|
161
|
+
for await (const msg of messages) {
|
|
162
|
+
await deliver(msg);
|
|
163
|
+
afterDeliver?.(msg);
|
|
164
|
+
}
|
|
165
|
+
})().catch((err) => {
|
|
166
|
+
console.error(`NATS subscription iterator failed for "${resolvedSubject}"`, err);
|
|
167
|
+
});
|
|
168
|
+
};
|
|
169
|
+
if (opts?.replayLast) {
|
|
170
|
+
const consumer = await jetstream(nc).consumers.get(natsStreamBySubject[subject], {
|
|
171
|
+
filter_subjects: [resolvedSubject],
|
|
172
|
+
deliver_policy: DeliverPolicy.LastPerSubject
|
|
173
|
+
});
|
|
174
|
+
const { num_pending } = await consumer.info();
|
|
175
|
+
let replayComplete = false;
|
|
176
|
+
const completeReplay = () => {
|
|
177
|
+
if (replayComplete) return;
|
|
178
|
+
replayComplete = true;
|
|
179
|
+
opts.onReplayComplete?.();
|
|
180
|
+
};
|
|
181
|
+
const messages = await consumer.consume();
|
|
182
|
+
deliverAll(messages, (msg) => {
|
|
183
|
+
if ("info" in msg && msg.info.pending === 0) completeReplay();
|
|
184
|
+
});
|
|
185
|
+
if (num_pending === 0) completeReplay();
|
|
186
|
+
return () => {
|
|
187
|
+
messages.stop();
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
const sub = nc.subscribe(resolvedSubject);
|
|
191
|
+
deliverAll(sub);
|
|
135
192
|
return () => sub.unsubscribe();
|
|
136
193
|
}
|
|
137
194
|
/**
|
|
@@ -162,6 +219,6 @@ var NovaNatsClient = class {
|
|
|
162
219
|
}
|
|
163
220
|
};
|
|
164
221
|
//#endregion
|
|
165
|
-
export { NovaNatsClient, buildNatsServerUrl };
|
|
222
|
+
export { NovaNatsClient, buildNatsServerUrl, natsStreamBySubject };
|
|
166
223
|
|
|
167
224
|
//# sourceMappingURL=index.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../../src/lib/experimental/nats/buildNatsServerUrl.ts","../../../src/lib/experimental/nats/buildSubject.ts","../../../src/lib/experimental/nats/NovaNatsClient.ts"],"sourcesContent":["import { parseNovaInstanceUrl } from \"../../converters.ts\"\n\n/**\n * Builds the WebSocket URL for a NOVA instance's NATS gateway from its\n * instance URL, e.g. `https://foo.instance.wandelbots.io` becomes\n * `wss://foo.instance.wandelbots.io/api/nats`.\n *\n * Pass the result as `servers` in the `NovaNatsClientConfig` passed to\n * `NovaNatsClient`.\n */\nexport function buildNatsServerUrl(instanceUrl: string): string {\n const url = parseNovaInstanceUrl(instanceUrl)\n const protocol = url.protocol === \"https:\" ? \"wss:\" : \"ws:\"\n return `${protocol}//${url.host}/api/nats`\n}\n","/**\n * Builds a NATS subject from an AsyncAPI-style channel address template\n * (e.g. `\"{instance}.v2.cells.{cell}\"`) by substituting each `{param}`\n * placeholder with the corresponding value from `params`.\n */\n\nfunction isValidSubjectChar(char: string): boolean {\n const code = char.charCodeAt(0)\n return (\n (code >= 48 && code <= 57) || // 0-9\n (code >= 65 && code <= 90) || // A-Z\n (code >= 97 && code <= 122) || // a-z\n char === \"-\" ||\n char === \"_\"\n )\n}\n\nfunction isValidSubjectValue(value: string): boolean {\n // A bare \"*\" is the NATS single-token wildcard, allowed on its own (e.g.\n // subscribing to all cells with `{ cell: \"*\" }\") but not as part of a\n // larger value, since it wouldn't act as a wildcard there anyway.\n if (value === \"*\") return true\n if (value.length === 0) return false\n for (const char of value) {\n if (!isValidSubjectChar(char)) return false\n }\n return true\n}\n\nexport function buildSubject(\n template: string,\n params: Record<string, string>,\n): string {\n // Scanned manually (rather than with a regex like /\\{([^}]+)\\}/g) to avoid\n // a polynomial-time backtracking blowup on pathological input, e.g. a\n // template consisting of many \"{\" characters with no closing \"}\".\n let result = \"\"\n let cursor = 0\n\n while (cursor < template.length) {\n const openIndex = template.indexOf(\"{\", cursor)\n if (openIndex === -1) {\n result += template.slice(cursor)\n break\n }\n\n const closeIndex = template.indexOf(\"}\", openIndex + 1)\n if (closeIndex === -1) {\n result += template.slice(cursor)\n break\n }\n\n result += template.slice(cursor, openIndex)\n const paramName = template.slice(openIndex + 1, closeIndex)\n const value = params[paramName]\n if (value === undefined) {\n throw new Error(\n `Missing value for subject parameter \"${paramName}\" in template \"${template}\"`,\n )\n }\n if (!isValidSubjectValue(value)) {\n throw new Error(\n `Invalid value for subject parameter \"${paramName}\": \"${value}\" (must be non-empty and contain only letters, digits, \"-\", and \"_\")`,\n )\n }\n result += value\n\n cursor = closeIndex + 1\n }\n\n return result\n}\n","import {\n type ConnectionOptions,\n type Msg,\n type NatsConnection,\n wsconnect,\n} from \"@nats-io/nats-core\"\nimport type { Nova } from \"../../Nova.ts\"\nimport { buildNatsServerUrl } from \"./buildNatsServerUrl.ts\"\nimport { buildSubject } from \"./buildSubject.ts\"\nimport type {\n NatsOperationParams,\n NatsPublishPayloads,\n NatsPublishSubject,\n NatsReplyPayloads,\n NatsRequestPayloads,\n NatsRequestSubject,\n NatsSubscribePayloads,\n NatsSubscribeSubject,\n} from \"./generated/operations.ts\"\n\nexport type NovaNatsClientConfig = ConnectionOptions\n\n/**\n * A received message, annotated with the values of the subject template's\n * `{param}` placeholders as extracted from the message's concrete subject.\n * With a wildcard subscription (e.g. `{ cell: \"*\" }`), `subjectParams` is how\n * a handler knows which entity a message belongs to. Typed per subject via\n * the generated `NatsOperationParams`.\n */\nexport type NatsSubscribeMsg<K extends NatsSubscribeSubject> = Msg & {\n subjectParams: NatsOperationParams[K]\n}\n\ntype NatsMessageHandler<K extends NatsSubscribeSubject> = (\n payload: NatsSubscribePayloads[K],\n msg: NatsSubscribeMsg<K>,\n) => void | Promise<void>\n\ntype SubscribeArgs<K extends NatsSubscribeSubject> =\n keyof NatsOperationParams[K] extends never\n ? [handler: NatsMessageHandler<K>]\n : [params: NatsOperationParams[K], handler: NatsMessageHandler<K>]\n\n/**\n * Typed NATS client for the Wandelbots NOVA messaging API, generated from\n * src/asyncapi.yaml (see scripts/generate-nats-client.ts).\n *\n * Connects over WebSocket via `@nats-io/nats-core`'s `wsconnect`.\n */\nexport class NovaNatsClient {\n readonly config: NovaNatsClientConfig\n private connectionPromise: Promise<NatsConnection> | null = null\n\n constructor(nova: Nova, config: NovaNatsClientConfig = {}) {\n this.config = {\n servers: buildNatsServerUrl(nova.instanceUrl.href),\n // Reuse the Nova instance's access token for NATS auth, if it has one\n // (e.g. from login or a passed-in config.accessToken). Explicit auth\n // options in `config` (token/user/pass/authenticator) still win.\n ...(nova.accessToken ? { token: nova.accessToken } : {}),\n ...config,\n }\n }\n\n /**\n * Connects to NATS if not already connected or connecting, and returns the\n * connection. Safe to call concurrently: all callers share the same\n * in-flight connection attempt instead of each starting their own.\n */\n connect(): Promise<NatsConnection> {\n if (!this.connectionPromise) {\n this.connectionPromise = wsconnect(this.config).catch((err: unknown) => {\n // Allow a subsequent connect() call to retry after a failed attempt.\n this.connectionPromise = null\n throw err\n })\n }\n return this.connectionPromise\n }\n\n /** Closes the underlying NATS connection, if open or connecting. */\n async close(): Promise<void> {\n const connectionPromise = this.connectionPromise\n this.connectionPromise = null\n if (!connectionPromise) return\n try {\n const nc = await connectionPromise\n await nc.close()\n } catch {\n // Connection never succeeded; nothing to close.\n }\n }\n\n /**\n * Subscribes to a NATS subject published by the server, invoking `handler`\n * with the JSON-decoded payload of every message received.\n *\n * `subject` is the subject template as it appears on the wire, e.g.\n * `\"nova.v2.cells.{cell}\"`, with `{param}` placeholders filled in from\n * `params`.\n *\n * Errors decoding a message or thrown/rejected by `handler` are caught and\n * logged per-message, so one bad message doesn't stop later messages on\n * the same subscription from being handled.\n *\n * Each message is annotated with `msg.subjectParams` — the template's\n * `{param}` values extracted from the message's concrete subject — so a\n * wildcard subscriber knows which entity a message belongs to:\n *\n * nats.subscribe(\"nova.v2.cells.{cell}.status\", { cell: \"*\" },\n * (services, msg) => console.log(msg.subjectParams.cell, services))\n *\n * Returns a function that unsubscribes when called.\n */\n async subscribe<K extends NatsSubscribeSubject>(\n subject: K,\n ...args: SubscribeArgs<K>\n ): Promise<() => void> {\n const [params, handler] =\n args.length === 1\n ? ([{}, args[0]] as const)\n : ([args[0], args[1]] as const)\n\n const nc = await this.connect()\n const resolvedSubject = buildSubject(subject, params)\n const sub = nc.subscribe(resolvedSubject)\n\n // The token positions of the template's {param} placeholders, computed\n // once here so each message's subjectParams is a plain index pick: a\n // delivered message always matches the subscribed pattern token for\n // token, so its params sit at the same positions as in the template.\n const paramPositions: [name: string, index: number][] = []\n for (const [index, token] of subject.split(\".\").entries()) {\n if (token.startsWith(\"{\") && token.endsWith(\"}\")) {\n paramPositions.push([token.slice(1, -1), index])\n }\n }\n\n ;(async () => {\n for await (const msg of sub) {\n // Handled per-message: a bad payload or a throwing/rejecting handler\n // should not stop the subscription from processing later messages.\n try {\n const subjectTokens = msg.subject.split(\".\")\n const subjectParams = Object.fromEntries(\n paramPositions.map(([name, index]) => [\n name,\n subjectTokens[index] ?? \"\",\n ]),\n ) as NatsOperationParams[K]\n await handler(\n msg.json<NatsSubscribePayloads[K]>(),\n Object.assign(msg, { subjectParams }),\n )\n } catch (err) {\n console.error(\n `Error handling NATS message on subject \"${resolvedSubject}\"`,\n err,\n )\n }\n }\n })().catch((err: unknown) => {\n console.error(\n `NATS subscription iterator failed for \"${resolvedSubject}\"`,\n err,\n )\n })\n\n return () => sub.unsubscribe()\n }\n\n /**\n * Sends a request payload for a NATS subject the server receives, and\n * waits for the JSON-decoded reply.\n *\n * `subject` is the subject template as it appears on the wire, e.g.\n * `\"nova.v2.cells.{cell}.bus-ios.ios.set\"`, with `{param}` placeholders\n * filled in from `params`.\n */\n async request<K extends NatsRequestSubject>(\n subject: K,\n params: NatsOperationParams[K],\n payload: NatsRequestPayloads[K],\n opts: { timeout?: number } = {},\n ): Promise<NatsReplyPayloads[K]> {\n const nc = await this.connect()\n const resolvedSubject = buildSubject(subject, params)\n const msg = await nc.request(resolvedSubject, JSON.stringify(payload), {\n timeout: opts.timeout ?? 5000,\n })\n return msg.json<NatsReplyPayloads[K]>()\n }\n\n /**\n * Publishes a JSON payload to any NATS subject defined in the spec,\n * without waiting for a reply.\n *\n * `subject` is the subject template as it appears on the wire, e.g.\n * `\"nova.v2.cells.{cell}.bus-ios.ios.set\"`, with `{param}` placeholders\n * filled in from `params`.\n */\n async publish<K extends NatsPublishSubject>(\n subject: K,\n params: NatsOperationParams[K],\n payload: NatsPublishPayloads[K],\n ): Promise<void> {\n const nc = await this.connect()\n const resolvedSubject = buildSubject(subject, params)\n nc.publish(resolvedSubject, JSON.stringify(payload))\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../src/lib/experimental/nats/buildNatsServerUrl.ts","../../../src/lib/experimental/nats/generated/operations.ts","../../../src/lib/experimental/nats/buildSubject.ts","../../../src/lib/experimental/nats/NovaNatsClient.ts"],"sourcesContent":["import { parseNovaInstanceUrl } from \"../../converters.ts\"\n\n/**\n * Builds the WebSocket URL for a NOVA instance's NATS gateway from its\n * instance URL, e.g. `https://foo.instance.wandelbots.io` becomes\n * `wss://foo.instance.wandelbots.io/api/nats`.\n *\n * Pass the result as `servers` in the `NovaNatsClientConfig` passed to\n * `NovaNatsClient`.\n */\nexport function buildNatsServerUrl(instanceUrl: string): string {\n const url = parseNovaInstanceUrl(instanceUrl)\n const protocol = url.protocol === \"https:\" ? \"wss:\" : \"ws:\"\n return `${protocol}//${url.host}/api/nats`\n}\n","/**\n * AUTO-GENERATED FILE - DO NOT EDIT.\n * Generated from src/asyncapi.yaml by scripts/generate-nats-client.ts.\n * Run `pnpm generate:nats` to regenerate.\n */\n\nimport type {\n App,\n AppCreatedEvent,\n AppDeletedEvent,\n AppUpdatedEvent,\n BusIOsState,\n Cell,\n CellCreatedEvent,\n CellCycleEvent,\n CellDeletedEvent,\n CellUpdatedEvent,\n CollisionSetup,\n ListIOValuesResponse,\n MotionGroupDescription,\n NatsErrorPayload,\n NetworkStatusChangedEvent,\n ProgramStatus,\n RobotController,\n RobotControllerCreatedEvent,\n RobotControllerDeletedEvent,\n RobotControllerState,\n RobotControllerUpdatedEvent,\n SelectIOs,\n ServiceStatusList,\n StreamIOValuesResponse,\n SystemUpdateCompletedEvent,\n SystemUpdateStartedEvent,\n} from \"./types.ts\"\n\n/** Subject parameters required by each NATS subject, e.g. \"nova.v2.cells.{cell}\". */\nexport interface NatsOperationParams {\n /**\n * Cell Configuration\n *\n * Publishes the configuration for a cell.\n *\n * The latest status message is persisted NATS JetStream, documented in `x-nats-jetstream-stream`\n *\n * @operationId publishCell\n */\n \"nova.v2.cells.{cell}\": {\n /**\n * Unique identifier addressing a cell in all API calls.\n */\n cell: string\n }\n /**\n * App Configuration\n *\n * Publishes the configuration for a GUI application in the cell.\n *\n * The latest status message is persisted NATS JetStream, documented in `x-nats-jetstream-stream`\n *\n * @operationId publishApp\n */\n \"nova.v2.cells.{cell}.apps.{app}\": {\n /**\n * Unique identifier addressing a cell in all API calls.\n */\n cell: string\n /**\n * Name of the provided application.\n * Must be unique within the cell and is used as an identifier for addressing the application in all API calls, e.g., when updating the application.\n */\n app: string\n }\n /**\n * Program Status\n *\n * Publishes status messages for programs running in an app within a cell.\n * The status messages provide information about the current state of a program run.\n *\n * @operationId publishProgramStatus\n */\n \"nova.v2.cells.{cell}.programs\": {\n /**\n * Unique identifier addressing a cell in all API calls.\n */\n cell: string\n }\n /**\n * Robot Controller Configuration\n *\n * Publishes the configuration of a robot controller.\n *\n * @operationId publishRobotController\n */\n \"nova.v2.cells.{cell}.controllers.{controller}\": {\n /**\n * Unique identifier addressing a cell in all API calls.\n */\n cell: string\n /**\n * Unique identifier to address a controller in the cell.\n */\n controller: string\n }\n /**\n * Service Status\n *\n * Publishes the status of all cell resources.\n *\n * The latest status message is persisted NATS JetStream, documented in `x-nats-jetstream-stream`\n *\n * @operationId publishCellStatus\n */\n \"nova.v2.cells.{cell}.status\": {\n /**\n * Unique identifier addressing a cell in all API calls.\n */\n cell: string\n }\n /**\n * Cell Cycle Event\n *\n * Publishes the cycle events for a cell.\n *\n * @operationId publishCellCycle\n */\n \"nova.v2.cells.{cell}.cycle\": {\n /**\n * Unique identifier addressing a cell in all API calls.\n */\n cell: string\n }\n /**\n * Wandelbots NOVA status\n *\n * Publishes the status of all system services.\n *\n * The latest status message is persisted NATS JetStream, documented in `x-nats-jetstream-stream`\n *\n * @operationId publishSystemStatus\n */\n \"nova.v2.system.status\": Record<never, never>\n /**\n * Collision Setup\n *\n * Publishes the stored collision setup.\n *\n * The latest status message is persisted NATS JetStream, documented in `x-nats-jetstream-stream`\n *\n * @operationId publishCollisionSetup\n */\n \"nova.v2.cells.{cell}.collision.setups.{setup}\": {\n /**\n * Unique identifier addressing a cell in all API calls.\n */\n cell: string\n /**\n * Unique identifier addressing a collision setup.\n */\n setup: string\n }\n /**\n * BUS Inputs/Outputs Service Status\n *\n * Publishes the status of BUS inputs/outputs service.\n *\n * The latest status message is persisted NATS JetStream, documented in `x-nats-jetstream-stream`.\n *\n * @operationId publishBUSIOStatus\n */\n \"nova.v2.cells.{cell}.bus-ios.status\": {\n /**\n * Unique identifier addressing a cell in all API calls.\n */\n cell: string\n }\n /**\n * BUS Input/Output Values\n *\n * Publishes updates of BUS input/output values.\n *\n * @operationId publishBUSIOsIOs\n */\n \"nova.v2.cells.{cell}.bus-ios.ios\": {\n /**\n * Unique identifier addressing a cell in all API calls.\n */\n cell: string\n }\n /**\n * Set Output Values\n *\n * Set output values published with the BUS inputs/outputs service.\n * If you're using a virtual service, you can set inputs as well.\n *\n * @operationId setBUSIOsIOs\n */\n \"nova.v2.cells.{cell}.bus-ios.ios.set\": {\n /**\n * Unique identifier addressing a cell in all API calls.\n */\n cell: string\n }\n /**\n * Select Input/Output Values\n *\n * Select input/output values published by the controller.\n *\n * @operationId selectRobotControllerIOs\n */\n \"nova.v2.cells.{cell}.controllers.{controller}.ios.select\": {\n /**\n * Unique identifier addressing a cell in all API calls.\n */\n cell: string\n /**\n * Unique identifier to address a controller in a cell.\n */\n controller: string\n }\n /**\n * Input/Output Values\n *\n * Publishes updates of input/output values.\n *\n * @operationId publishRobotControllerIOs\n */\n \"nova.v2.cells.{cell}.controllers.{controller}.ios\": {\n /**\n * Unique identifier addressing a cell in all API calls.\n */\n cell: string\n /**\n * Unique identifier to address a controller in the cell.\n */\n controller: string\n }\n /**\n * State of Robot Controller\n *\n * Publishes the current state of a robot controller.\n *\n * @operationId publishRobotControllersState\n */\n \"nova.v2.cells.{cell}.controllers.{controller}.state\": {\n /**\n * Unique identifier addressing a cell in all API calls.\n */\n cell: string\n /**\n * Unique identifier to address a controller in the cell.\n */\n controller: string\n }\n /**\n * Description of Motion Group\n *\n * Publishes the description of a motion group, including TCPs, mounting, safety zones, limits, etc.\n *\n * @operationId publishMotionGroupDescription\n */\n \"nova.v2.cells.{cell}.controllers.{controller}.motion-groups.{motion-group}.description\": {\n /**\n * Unique identifier addressing a cell in all API calls.\n */\n cell: string\n /**\n * Unique identifier addressing a controller in the cell.\n */\n controller: string\n /**\n * Motion group identifier.\n */\n \"motion-group\": string\n }\n /**\n * System Update Started\n *\n * Publishes an event when a system update process is initiated.\n *\n * This event is triggered once the service-manager begins a system update process,\n * providing details about the update metadata, trigger information, and pre-update checks.\n *\n * The event follows CloudEvents v1.0 specification and is persisted in NATS JetStream\n * for reliable delivery and event replay capabilities.\n *\n * @operationId eventSystemUpdateStarted\n */\n \"nova.v2.events.system.update.started\": Record<never, never>\n /**\n * System Update Completed\n *\n * Publishes an event when a system update process is completed.\n *\n * This event is triggered once the service-manager completes a system update process,\n * providing comprehensive results including success status, component outcomes,\n * error details, and post-update validation results.\n *\n * The event follows CloudEvents v1.0 specification and is persisted in NATS JetStream\n * for reliable delivery and event replay capabilities.\n *\n * @operationId eventSystemUpdateCompleted\n */\n \"nova.v2.events.system.update.completed\": Record<never, never>\n /**\n * System Network Status Changed\n *\n * Publishes an event when a system network status changes.\n *\n * This event is triggered once system-info service detects a change in the system network status,\n * providing details about the new network state and related information.\n *\n * The event follows CloudEvents v1.0 specification and is persisted in NATS JetStream\n * for reliable delivery and event replay capabilities.\n *\n * @operationId eventSystemNetworkStatusChanged\n */\n \"nova.v2.events.system.network.status.changed\": Record<never, never>\n /**\n * Cell Created\n *\n * Publishes an event when a cell foundation release is created.\n *\n * The event follows CloudEvents v1.0 and is persisted in NATS JetStream for reliable delivery and replay capabilities.\n *\n * @operationId eventCellCreated\n */\n \"nova.v2.events.cells.{cell}.created\": {\n /**\n * Unique identifier addressing a cell in all API calls.\n */\n cell: string\n }\n /**\n * Cell Updated\n *\n * Publishes an event when a cell foundation release is updated.\n *\n * The event follows CloudEvents v1.0 and is persisted in NATS JetStream for reliable delivery and replay capabilities.\n *\n * @operationId eventCellUpdated\n */\n \"nova.v2.events.cells.{cell}.updated\": {\n /**\n * Unique identifier addressing a cell in all API calls.\n */\n cell: string\n }\n /**\n * Cell Deleted\n *\n * Publishes an event when a cell foundation release is deleted.\n *\n * The event follows CloudEvents v1.0 and is persisted in NATS JetStream for reliable delivery and replay capabilities.\n *\n * @operationId eventCellDeleted\n */\n \"nova.v2.events.cells.{cell}.deleted\": {\n /**\n * Unique identifier addressing a cell in all API calls.\n */\n cell: string\n }\n /**\n * App Created\n *\n * Publishes an event when an app release is created in a cell.\n *\n * The event follows CloudEvents v1.0 and is persisted in NATS JetStream for reliable delivery and replay capabilities.\n *\n * @operationId eventAppCreated\n */\n \"nova.v2.events.cells.{cell}.apps.{app}.created\": {\n /**\n * Unique identifier addressing a cell in all API calls.\n */\n cell: string\n /**\n * Unique identifier addressing an app in the cell.\n */\n app: string\n }\n /**\n * App Updated\n *\n * Publishes an event when an app release is updated in a cell.\n *\n * The event follows CloudEvents v1.0 and is persisted in NATS JetStream for reliable delivery and replay capabilities.\n *\n * @operationId eventAppUpdated\n */\n \"nova.v2.events.cells.{cell}.apps.{app}.updated\": {\n /**\n * Unique identifier addressing a cell in all API calls.\n */\n cell: string\n /**\n * Unique identifier addressing an app in the cell.\n */\n app: string\n }\n /**\n * App Deleted\n *\n * Publishes an event when an app release is deleted from a cell.\n *\n * The event follows CloudEvents v1.0 and is persisted in NATS JetStream for reliable delivery and replay capabilities.\n *\n * @operationId eventAppDeleted\n */\n \"nova.v2.events.cells.{cell}.apps.{app}.deleted\": {\n /**\n * Unique identifier addressing a cell in all API calls.\n */\n cell: string\n /**\n * Unique identifier addressing an app in the cell.\n */\n app: string\n }\n /**\n * Robot Controller Created\n *\n * Publishes an event when a robot controller release is created in a cell.\n *\n * The event follows CloudEvents v1.0 and is persisted in NATS JetStream for reliable delivery and replay capabilities.\n *\n * @operationId eventRobotControllerCreated\n */\n \"nova.v2.events.cells.{cell}.controllers.{controller}.created\": {\n /**\n * Unique identifier addressing a cell in all API calls.\n */\n cell: string\n /**\n * Unique identifier to address a controller in the cell.\n */\n controller: string\n }\n /**\n * Robot Controller Updated\n *\n * Publishes an event when a robot controller release is updated in a cell.\n *\n * The event follows CloudEvents v1.0 and is persisted in NATS JetStream for reliable delivery and replay capabilities.\n *\n * @operationId eventRobotControllerUpdated\n */\n \"nova.v2.events.cells.{cell}.controllers.{controller}.updated\": {\n /**\n * Unique identifier addressing a cell in all API calls.\n */\n cell: string\n /**\n * Unique identifier to address a controller in the cell.\n */\n controller: string\n }\n /**\n * Robot Controller Deleted\n *\n * Publishes an event when a robot controller release is deleted from a cell.\n *\n * The event follows CloudEvents v1.0 and is persisted in NATS JetStream for reliable delivery and replay capabilities.\n *\n * @operationId eventRobotControllerDeleted\n */\n \"nova.v2.events.cells.{cell}.controllers.{controller}.deleted\": {\n /**\n * Unique identifier addressing a cell in all API calls.\n */\n cell: string\n /**\n * Unique identifier to address a controller in the cell.\n */\n controller: string\n }\n}\n\n/** Payload types for subjects the server publishes and the client subscribes to. */\nexport interface NatsSubscribePayloads {\n /**\n * Cell Configuration\n *\n * Publishes the configuration for a cell.\n *\n * The latest status message is persisted NATS JetStream, documented in `x-nats-jetstream-stream`\n *\n * @operationId publishCell\n */\n \"nova.v2.cells.{cell}\": Cell\n /**\n * App Configuration\n *\n * Publishes the configuration for a GUI application in the cell.\n *\n * The latest status message is persisted NATS JetStream, documented in `x-nats-jetstream-stream`\n *\n * @operationId publishApp\n */\n \"nova.v2.cells.{cell}.apps.{app}\": App\n /**\n * Program Status\n *\n * Publishes status messages for programs running in an app within a cell.\n * The status messages provide information about the current state of a program run.\n *\n * @operationId publishProgramStatus\n */\n \"nova.v2.cells.{cell}.programs\": ProgramStatus\n /**\n * Robot Controller Configuration\n *\n * Publishes the configuration of a robot controller.\n *\n * @operationId publishRobotController\n */\n \"nova.v2.cells.{cell}.controllers.{controller}\": RobotController\n /**\n * Service Status\n *\n * Publishes the status of all cell resources.\n *\n * The latest status message is persisted NATS JetStream, documented in `x-nats-jetstream-stream`\n *\n * @operationId publishCellStatus\n */\n \"nova.v2.cells.{cell}.status\": ServiceStatusList\n /**\n * Cell Cycle Event\n *\n * Publishes the cycle events for a cell.\n *\n * @operationId publishCellCycle\n */\n \"nova.v2.cells.{cell}.cycle\": CellCycleEvent\n /**\n * Wandelbots NOVA status\n *\n * Publishes the status of all system services.\n *\n * The latest status message is persisted NATS JetStream, documented in `x-nats-jetstream-stream`\n *\n * @operationId publishSystemStatus\n */\n \"nova.v2.system.status\": ServiceStatusList\n /**\n * Collision Setup\n *\n * Publishes the stored collision setup.\n *\n * The latest status message is persisted NATS JetStream, documented in `x-nats-jetstream-stream`\n *\n * @operationId publishCollisionSetup\n */\n \"nova.v2.cells.{cell}.collision.setups.{setup}\": CollisionSetup\n /**\n * BUS Inputs/Outputs Service Status\n *\n * Publishes the status of BUS inputs/outputs service.\n *\n * The latest status message is persisted NATS JetStream, documented in `x-nats-jetstream-stream`.\n *\n * @operationId publishBUSIOStatus\n */\n \"nova.v2.cells.{cell}.bus-ios.status\": BusIOsState\n /**\n * BUS Input/Output Values\n *\n * Publishes updates of BUS input/output values.\n *\n * @operationId publishBUSIOsIOs\n */\n \"nova.v2.cells.{cell}.bus-ios.ios\": ListIOValuesResponse\n /**\n * Input/Output Values\n *\n * Publishes updates of input/output values.\n *\n * @operationId publishRobotControllerIOs\n */\n \"nova.v2.cells.{cell}.controllers.{controller}.ios\": StreamIOValuesResponse\n /**\n * State of Robot Controller\n *\n * Publishes the current state of a robot controller.\n *\n * @operationId publishRobotControllersState\n */\n \"nova.v2.cells.{cell}.controllers.{controller}.state\": RobotControllerState\n /**\n * Description of Motion Group\n *\n * Publishes the description of a motion group, including TCPs, mounting, safety zones, limits, etc.\n *\n * @operationId publishMotionGroupDescription\n */\n \"nova.v2.cells.{cell}.controllers.{controller}.motion-groups.{motion-group}.description\": MotionGroupDescription\n /**\n * System Update Started\n *\n * Publishes an event when a system update process is initiated.\n *\n * This event is triggered once the service-manager begins a system update process,\n * providing details about the update metadata, trigger information, and pre-update checks.\n *\n * The event follows CloudEvents v1.0 specification and is persisted in NATS JetStream\n * for reliable delivery and event replay capabilities.\n *\n * @operationId eventSystemUpdateStarted\n */\n \"nova.v2.events.system.update.started\": SystemUpdateStartedEvent\n /**\n * System Update Completed\n *\n * Publishes an event when a system update process is completed.\n *\n * This event is triggered once the service-manager completes a system update process,\n * providing comprehensive results including success status, component outcomes,\n * error details, and post-update validation results.\n *\n * The event follows CloudEvents v1.0 specification and is persisted in NATS JetStream\n * for reliable delivery and event replay capabilities.\n *\n * @operationId eventSystemUpdateCompleted\n */\n \"nova.v2.events.system.update.completed\": SystemUpdateCompletedEvent\n /**\n * System Network Status Changed\n *\n * Publishes an event when a system network status changes.\n *\n * This event is triggered once system-info service detects a change in the system network status,\n * providing details about the new network state and related information.\n *\n * The event follows CloudEvents v1.0 specification and is persisted in NATS JetStream\n * for reliable delivery and event replay capabilities.\n *\n * @operationId eventSystemNetworkStatusChanged\n */\n \"nova.v2.events.system.network.status.changed\": NetworkStatusChangedEvent\n /**\n * Cell Created\n *\n * Publishes an event when a cell foundation release is created.\n *\n * The event follows CloudEvents v1.0 and is persisted in NATS JetStream for reliable delivery and replay capabilities.\n *\n * @operationId eventCellCreated\n */\n \"nova.v2.events.cells.{cell}.created\": CellCreatedEvent\n /**\n * Cell Updated\n *\n * Publishes an event when a cell foundation release is updated.\n *\n * The event follows CloudEvents v1.0 and is persisted in NATS JetStream for reliable delivery and replay capabilities.\n *\n * @operationId eventCellUpdated\n */\n \"nova.v2.events.cells.{cell}.updated\": CellUpdatedEvent\n /**\n * Cell Deleted\n *\n * Publishes an event when a cell foundation release is deleted.\n *\n * The event follows CloudEvents v1.0 and is persisted in NATS JetStream for reliable delivery and replay capabilities.\n *\n * @operationId eventCellDeleted\n */\n \"nova.v2.events.cells.{cell}.deleted\": CellDeletedEvent\n /**\n * App Created\n *\n * Publishes an event when an app release is created in a cell.\n *\n * The event follows CloudEvents v1.0 and is persisted in NATS JetStream for reliable delivery and replay capabilities.\n *\n * @operationId eventAppCreated\n */\n \"nova.v2.events.cells.{cell}.apps.{app}.created\": AppCreatedEvent\n /**\n * App Updated\n *\n * Publishes an event when an app release is updated in a cell.\n *\n * The event follows CloudEvents v1.0 and is persisted in NATS JetStream for reliable delivery and replay capabilities.\n *\n * @operationId eventAppUpdated\n */\n \"nova.v2.events.cells.{cell}.apps.{app}.updated\": AppUpdatedEvent\n /**\n * App Deleted\n *\n * Publishes an event when an app release is deleted from a cell.\n *\n * The event follows CloudEvents v1.0 and is persisted in NATS JetStream for reliable delivery and replay capabilities.\n *\n * @operationId eventAppDeleted\n */\n \"nova.v2.events.cells.{cell}.apps.{app}.deleted\": AppDeletedEvent\n /**\n * Robot Controller Created\n *\n * Publishes an event when a robot controller release is created in a cell.\n *\n * The event follows CloudEvents v1.0 and is persisted in NATS JetStream for reliable delivery and replay capabilities.\n *\n * @operationId eventRobotControllerCreated\n */\n \"nova.v2.events.cells.{cell}.controllers.{controller}.created\": RobotControllerCreatedEvent\n /**\n * Robot Controller Updated\n *\n * Publishes an event when a robot controller release is updated in a cell.\n *\n * The event follows CloudEvents v1.0 and is persisted in NATS JetStream for reliable delivery and replay capabilities.\n *\n * @operationId eventRobotControllerUpdated\n */\n \"nova.v2.events.cells.{cell}.controllers.{controller}.updated\": RobotControllerUpdatedEvent\n /**\n * Robot Controller Deleted\n *\n * Publishes an event when a robot controller release is deleted from a cell.\n *\n * The event follows CloudEvents v1.0 and is persisted in NATS JetStream for reliable delivery and replay capabilities.\n *\n * @operationId eventRobotControllerDeleted\n */\n \"nova.v2.events.cells.{cell}.controllers.{controller}.deleted\": RobotControllerDeletedEvent\n}\n\nexport type NatsSubscribeSubject = keyof NatsSubscribePayloads\n\n/**\n * The JetStream stream backing each subject that retains only its latest\n * message (`max_msgs_per_subject: 1` in src/asyncapi.yaml), so that\n * message is the subject's current state.\n */\nexport const natsStreamBySubject = {\n \"nova.v2.cells.{cell}\": \"system-state\",\n \"nova.v2.cells.{cell}.apps.{app}\": \"system-state\",\n \"nova.v2.cells.{cell}.controllers.{controller}\": \"system-state\",\n \"nova.v2.cells.{cell}.status\": \"system-state\",\n \"nova.v2.system.status\": \"system-state\",\n \"nova.v2.cells.{cell}.collision.setups.{setup}\": \"system-state\",\n \"nova.v2.cells.{cell}.bus-ios.status\": \"system-state\",\n \"nova.v2.cells.{cell}.controllers.{controller}.motion-groups.{motion-group}.description\":\n \"system-state\",\n} as const\n\n/** Subjects whose current value can be replayed via `{ replayLast: true }`. */\nexport type NatsPersistedSubject = keyof typeof natsStreamBySubject\n\n/** Request payload types for subjects the client sends requests to. */\nexport interface NatsRequestPayloads {\n /**\n * Set Output Values\n *\n * Set output values published with the BUS inputs/outputs service.\n * If you're using a virtual service, you can set inputs as well.\n *\n * @operationId setBUSIOsIOs\n */\n \"nova.v2.cells.{cell}.bus-ios.ios.set\": ListIOValuesResponse\n /**\n * Select Input/Output Values\n *\n * Select input/output values published by the controller.\n *\n * @operationId selectRobotControllerIOs\n */\n \"nova.v2.cells.{cell}.controllers.{controller}.ios.select\": SelectIOs\n}\n\n/** Reply payload types for request/reply subjects. */\nexport interface NatsReplyPayloads {\n /**\n * Set Output Values\n *\n * Set output values published with the BUS inputs/outputs service.\n * If you're using a virtual service, you can set inputs as well.\n *\n * @operationId setBUSIOsIOs\n */\n \"nova.v2.cells.{cell}.bus-ios.ios.set\": NatsErrorPayload\n /**\n * Select Input/Output Values\n *\n * Select input/output values published by the controller.\n *\n * @operationId selectRobotControllerIOs\n */\n \"nova.v2.cells.{cell}.controllers.{controller}.ios.select\": NatsErrorPayload\n}\n\nexport type NatsRequestSubject = keyof NatsRequestPayloads\n\n/** Payload types for every subject defined in the spec, publishable via NovaNatsClient#publish. */\nexport interface NatsPublishPayloads {\n /**\n * Cell Configuration\n *\n * Publishes the configuration for a cell.\n *\n * The latest status message is persisted NATS JetStream, documented in `x-nats-jetstream-stream`\n *\n * @operationId publishCell\n */\n \"nova.v2.cells.{cell}\": Cell\n /**\n * App Configuration\n *\n * Publishes the configuration for a GUI application in the cell.\n *\n * The latest status message is persisted NATS JetStream, documented in `x-nats-jetstream-stream`\n *\n * @operationId publishApp\n */\n \"nova.v2.cells.{cell}.apps.{app}\": App\n /**\n * Program Status\n *\n * Publishes status messages for programs running in an app within a cell.\n * The status messages provide information about the current state of a program run.\n *\n * @operationId publishProgramStatus\n */\n \"nova.v2.cells.{cell}.programs\": ProgramStatus\n /**\n * Robot Controller Configuration\n *\n * Publishes the configuration of a robot controller.\n *\n * @operationId publishRobotController\n */\n \"nova.v2.cells.{cell}.controllers.{controller}\": RobotController\n /**\n * Service Status\n *\n * Publishes the status of all cell resources.\n *\n * The latest status message is persisted NATS JetStream, documented in `x-nats-jetstream-stream`\n *\n * @operationId publishCellStatus\n */\n \"nova.v2.cells.{cell}.status\": ServiceStatusList\n /**\n * Cell Cycle Event\n *\n * Publishes the cycle events for a cell.\n *\n * @operationId publishCellCycle\n */\n \"nova.v2.cells.{cell}.cycle\": CellCycleEvent\n /**\n * Wandelbots NOVA status\n *\n * Publishes the status of all system services.\n *\n * The latest status message is persisted NATS JetStream, documented in `x-nats-jetstream-stream`\n *\n * @operationId publishSystemStatus\n */\n \"nova.v2.system.status\": ServiceStatusList\n /**\n * Collision Setup\n *\n * Publishes the stored collision setup.\n *\n * The latest status message is persisted NATS JetStream, documented in `x-nats-jetstream-stream`\n *\n * @operationId publishCollisionSetup\n */\n \"nova.v2.cells.{cell}.collision.setups.{setup}\": CollisionSetup\n /**\n * BUS Inputs/Outputs Service Status\n *\n * Publishes the status of BUS inputs/outputs service.\n *\n * The latest status message is persisted NATS JetStream, documented in `x-nats-jetstream-stream`.\n *\n * @operationId publishBUSIOStatus\n */\n \"nova.v2.cells.{cell}.bus-ios.status\": BusIOsState\n /**\n * BUS Input/Output Values\n *\n * Publishes updates of BUS input/output values.\n *\n * @operationId publishBUSIOsIOs\n */\n \"nova.v2.cells.{cell}.bus-ios.ios\": ListIOValuesResponse\n /**\n * Set Output Values\n *\n * Set output values published with the BUS inputs/outputs service.\n * If you're using a virtual service, you can set inputs as well.\n *\n * @operationId setBUSIOsIOs\n */\n \"nova.v2.cells.{cell}.bus-ios.ios.set\": ListIOValuesResponse\n /**\n * Select Input/Output Values\n *\n * Select input/output values published by the controller.\n *\n * @operationId selectRobotControllerIOs\n */\n \"nova.v2.cells.{cell}.controllers.{controller}.ios.select\": SelectIOs\n /**\n * Input/Output Values\n *\n * Publishes updates of input/output values.\n *\n * @operationId publishRobotControllerIOs\n */\n \"nova.v2.cells.{cell}.controllers.{controller}.ios\": StreamIOValuesResponse\n /**\n * State of Robot Controller\n *\n * Publishes the current state of a robot controller.\n *\n * @operationId publishRobotControllersState\n */\n \"nova.v2.cells.{cell}.controllers.{controller}.state\": RobotControllerState\n /**\n * Description of Motion Group\n *\n * Publishes the description of a motion group, including TCPs, mounting, safety zones, limits, etc.\n *\n * @operationId publishMotionGroupDescription\n */\n \"nova.v2.cells.{cell}.controllers.{controller}.motion-groups.{motion-group}.description\": MotionGroupDescription\n /**\n * System Update Started\n *\n * Publishes an event when a system update process is initiated.\n *\n * This event is triggered once the service-manager begins a system update process,\n * providing details about the update metadata, trigger information, and pre-update checks.\n *\n * The event follows CloudEvents v1.0 specification and is persisted in NATS JetStream\n * for reliable delivery and event replay capabilities.\n *\n * @operationId eventSystemUpdateStarted\n */\n \"nova.v2.events.system.update.started\": SystemUpdateStartedEvent\n /**\n * System Update Completed\n *\n * Publishes an event when a system update process is completed.\n *\n * This event is triggered once the service-manager completes a system update process,\n * providing comprehensive results including success status, component outcomes,\n * error details, and post-update validation results.\n *\n * The event follows CloudEvents v1.0 specification and is persisted in NATS JetStream\n * for reliable delivery and event replay capabilities.\n *\n * @operationId eventSystemUpdateCompleted\n */\n \"nova.v2.events.system.update.completed\": SystemUpdateCompletedEvent\n /**\n * System Network Status Changed\n *\n * Publishes an event when a system network status changes.\n *\n * This event is triggered once system-info service detects a change in the system network status,\n * providing details about the new network state and related information.\n *\n * The event follows CloudEvents v1.0 specification and is persisted in NATS JetStream\n * for reliable delivery and event replay capabilities.\n *\n * @operationId eventSystemNetworkStatusChanged\n */\n \"nova.v2.events.system.network.status.changed\": NetworkStatusChangedEvent\n /**\n * Cell Created\n *\n * Publishes an event when a cell foundation release is created.\n *\n * The event follows CloudEvents v1.0 and is persisted in NATS JetStream for reliable delivery and replay capabilities.\n *\n * @operationId eventCellCreated\n */\n \"nova.v2.events.cells.{cell}.created\": CellCreatedEvent\n /**\n * Cell Updated\n *\n * Publishes an event when a cell foundation release is updated.\n *\n * The event follows CloudEvents v1.0 and is persisted in NATS JetStream for reliable delivery and replay capabilities.\n *\n * @operationId eventCellUpdated\n */\n \"nova.v2.events.cells.{cell}.updated\": CellUpdatedEvent\n /**\n * Cell Deleted\n *\n * Publishes an event when a cell foundation release is deleted.\n *\n * The event follows CloudEvents v1.0 and is persisted in NATS JetStream for reliable delivery and replay capabilities.\n *\n * @operationId eventCellDeleted\n */\n \"nova.v2.events.cells.{cell}.deleted\": CellDeletedEvent\n /**\n * App Created\n *\n * Publishes an event when an app release is created in a cell.\n *\n * The event follows CloudEvents v1.0 and is persisted in NATS JetStream for reliable delivery and replay capabilities.\n *\n * @operationId eventAppCreated\n */\n \"nova.v2.events.cells.{cell}.apps.{app}.created\": AppCreatedEvent\n /**\n * App Updated\n *\n * Publishes an event when an app release is updated in a cell.\n *\n * The event follows CloudEvents v1.0 and is persisted in NATS JetStream for reliable delivery and replay capabilities.\n *\n * @operationId eventAppUpdated\n */\n \"nova.v2.events.cells.{cell}.apps.{app}.updated\": AppUpdatedEvent\n /**\n * App Deleted\n *\n * Publishes an event when an app release is deleted from a cell.\n *\n * The event follows CloudEvents v1.0 and is persisted in NATS JetStream for reliable delivery and replay capabilities.\n *\n * @operationId eventAppDeleted\n */\n \"nova.v2.events.cells.{cell}.apps.{app}.deleted\": AppDeletedEvent\n /**\n * Robot Controller Created\n *\n * Publishes an event when a robot controller release is created in a cell.\n *\n * The event follows CloudEvents v1.0 and is persisted in NATS JetStream for reliable delivery and replay capabilities.\n *\n * @operationId eventRobotControllerCreated\n */\n \"nova.v2.events.cells.{cell}.controllers.{controller}.created\": RobotControllerCreatedEvent\n /**\n * Robot Controller Updated\n *\n * Publishes an event when a robot controller release is updated in a cell.\n *\n * The event follows CloudEvents v1.0 and is persisted in NATS JetStream for reliable delivery and replay capabilities.\n *\n * @operationId eventRobotControllerUpdated\n */\n \"nova.v2.events.cells.{cell}.controllers.{controller}.updated\": RobotControllerUpdatedEvent\n /**\n * Robot Controller Deleted\n *\n * Publishes an event when a robot controller release is deleted from a cell.\n *\n * The event follows CloudEvents v1.0 and is persisted in NATS JetStream for reliable delivery and replay capabilities.\n *\n * @operationId eventRobotControllerDeleted\n */\n \"nova.v2.events.cells.{cell}.controllers.{controller}.deleted\": RobotControllerDeletedEvent\n}\n\nexport type NatsPublishSubject = keyof NatsPublishPayloads\n","/**\n * Builds a NATS subject from an AsyncAPI-style channel address template\n * (e.g. `\"{instance}.v2.cells.{cell}\"`) by substituting each `{param}`\n * placeholder with the corresponding value from `params`.\n */\n\nfunction isValidSubjectChar(char: string): boolean {\n const code = char.charCodeAt(0)\n return (\n (code >= 48 && code <= 57) || // 0-9\n (code >= 65 && code <= 90) || // A-Z\n (code >= 97 && code <= 122) || // a-z\n char === \"-\" ||\n char === \"_\"\n )\n}\n\nfunction isValidSubjectValue(value: string): boolean {\n // A bare \"*\" is the NATS single-token wildcard, allowed on its own (e.g.\n // subscribing to all cells with `{ cell: \"*\" }\") but not as part of a\n // larger value, since it wouldn't act as a wildcard there anyway.\n if (value === \"*\") return true\n if (value.length === 0) return false\n for (const char of value) {\n if (!isValidSubjectChar(char)) return false\n }\n return true\n}\n\nexport function buildSubject(\n template: string,\n params: Record<string, string>,\n): string {\n // Scanned manually (rather than with a regex like /\\{([^}]+)\\}/g) to avoid\n // a polynomial-time backtracking blowup on pathological input, e.g. a\n // template consisting of many \"{\" characters with no closing \"}\".\n let result = \"\"\n let cursor = 0\n\n while (cursor < template.length) {\n const openIndex = template.indexOf(\"{\", cursor)\n if (openIndex === -1) {\n result += template.slice(cursor)\n break\n }\n\n const closeIndex = template.indexOf(\"}\", openIndex + 1)\n if (closeIndex === -1) {\n result += template.slice(cursor)\n break\n }\n\n result += template.slice(cursor, openIndex)\n const paramName = template.slice(openIndex + 1, closeIndex)\n const value = params[paramName]\n if (value === undefined) {\n throw new Error(\n `Missing value for subject parameter \"${paramName}\" in template \"${template}\"`,\n )\n }\n if (!isValidSubjectValue(value)) {\n throw new Error(\n `Invalid value for subject parameter \"${paramName}\": \"${value}\" (must be non-empty and contain only letters, digits, \"-\", and \"_\")`,\n )\n }\n result += value\n\n cursor = closeIndex + 1\n }\n\n return result\n}\n","import { DeliverPolicy, jetstream, type JsMsg } from \"@nats-io/jetstream\"\nimport {\n type ConnectionOptions,\n type Msg,\n type NatsConnection,\n wsconnect,\n} from \"@nats-io/nats-core\"\nimport type { Nova } from \"../../Nova.ts\"\nimport { buildNatsServerUrl } from \"./buildNatsServerUrl.ts\"\nimport { buildSubject } from \"./buildSubject.ts\"\nimport {\n type NatsOperationParams,\n type NatsPersistedSubject,\n type NatsPublishPayloads,\n type NatsPublishSubject,\n type NatsReplyPayloads,\n type NatsRequestPayloads,\n type NatsRequestSubject,\n natsStreamBySubject,\n type NatsSubscribePayloads,\n type NatsSubscribeSubject,\n} from \"./generated/operations.ts\"\n\nexport type NovaNatsClientConfig = ConnectionOptions\n\n/**\n * A received message, annotated with the values of the subject template's\n * `{param}` placeholders as extracted from the message's concrete subject.\n * With a wildcard subscription (e.g. `{ cell: \"*\" }`), `subjectParams` is how\n * a handler knows which entity a message belongs to. Typed per subject via\n * the generated `NatsOperationParams`.\n */\nexport type NatsSubscribeMsg<K extends NatsSubscribeSubject> = (Msg | JsMsg) & {\n subjectParams: NatsOperationParams[K]\n}\n\ntype NatsMessageHandler<K extends NatsSubscribeSubject> = (\n payload: NatsSubscribePayloads[K],\n msg: NatsSubscribeMsg<K>,\n) => void | Promise<void>\n\n/**\n * Extra options for {@link NovaNatsClient.subscribe}. `replayLast` is only\n * offered on subjects the spec marks as retaining their latest message, so\n * asking for a replay that could never arrive is a compile error.\n */\nexport type NatsSubscribeOptions<K extends NatsSubscribeSubject> =\n K extends NatsPersistedSubject\n ? {\n /**\n * Deliver the subject's current value immediately on subscribe,\n * before any subsequent updates. With a wildcard subscription (e.g.\n * `{ cell: \"*\" }`) the current value of every matching subject is\n * delivered, not just one.\n */\n replayLast?: boolean\n /**\n * Called once every retained message has been passed to the handler,\n * i.e. when the handler has seen the subject's current state and\n * everything after it is a live update. Fires exactly once, and also\n * when there is nothing retained at all (an empty wildcard, e.g. no\n * apps installed) — where waiting for a first message would hang\n * forever. Only meaningful together with `replayLast`; without it\n * there is no retained state to wait for.\n */\n onReplayComplete?: () => void\n }\n : // Not `Record<never, never>`: that behaves like `{}` and so accepts any\n // object, letting `replayLast` through unchecked. Typing the property as\n // `never` is what makes passing it an error.\n { replayLast?: never; onReplayComplete?: never }\n\ntype SubscribeArgs<K extends NatsSubscribeSubject> =\n keyof NatsOperationParams[K] extends never\n ? [handler: NatsMessageHandler<K>, opts?: NatsSubscribeOptions<K>]\n : [\n params: NatsOperationParams[K],\n handler: NatsMessageHandler<K>,\n opts?: NatsSubscribeOptions<K>,\n ]\n\n/**\n * Typed NATS client for the Wandelbots NOVA messaging API, generated from\n * src/asyncapi.yaml (see scripts/generate-nats-client.ts).\n *\n * Connects over WebSocket via `@nats-io/nats-core`'s `wsconnect`.\n */\nexport class NovaNatsClient {\n readonly config: NovaNatsClientConfig\n private connectionPromise: Promise<NatsConnection> | null = null\n\n constructor(nova: Nova, config: NovaNatsClientConfig = {}) {\n this.config = {\n servers: buildNatsServerUrl(nova.instanceUrl.href),\n // Reuse the Nova instance's access token for NATS auth, if it has one\n // (e.g. from login or a passed-in config.accessToken). Explicit auth\n // options in `config` (token/user/pass/authenticator) still win.\n ...(nova.accessToken ? { token: nova.accessToken } : {}),\n ...config,\n }\n }\n\n /**\n * Connects to NATS if not already connected or connecting, and returns the\n * connection. Safe to call concurrently: all callers share the same\n * in-flight connection attempt instead of each starting their own.\n */\n connect(): Promise<NatsConnection> {\n if (!this.connectionPromise) {\n this.connectionPromise = wsconnect(this.config).catch((err: unknown) => {\n // Allow a subsequent connect() call to retry after a failed attempt.\n this.connectionPromise = null\n throw err\n })\n }\n return this.connectionPromise\n }\n\n /** Closes the underlying NATS connection, if open or connecting. */\n async close(): Promise<void> {\n const connectionPromise = this.connectionPromise\n this.connectionPromise = null\n if (!connectionPromise) return\n try {\n const nc = await connectionPromise\n await nc.close()\n } catch {\n // Connection never succeeded; nothing to close.\n }\n }\n\n /**\n * Subscribes to a NATS subject published by the server, invoking `handler`\n * with the JSON-decoded payload of every message received.\n *\n * `subject` is the subject template as it appears on the wire, e.g.\n * `\"nova.v2.cells.{cell}\"`, with `{param}` placeholders filled in from\n * `params`.\n *\n * Errors decoding a message or thrown/rejected by `handler` are caught and\n * logged per-message, so one bad message doesn't stop later messages on\n * the same subscription from being handled.\n *\n * Each message is annotated with `msg.subjectParams` — the template's\n * `{param}` values extracted from the message's concrete subject — so a\n * wildcard subscriber knows which entity a message belongs to:\n *\n * nats.subscribe(\"nova.v2.cells.{cell}.status\", { cell: \"*\" },\n * (services, msg) => console.log(msg.subjectParams.cell, services))\n *\n * On subjects that retain their latest message, pass `{ replayLast: true }`\n * to receive the current value immediately instead of waiting for the next\n * update — useful for a subscriber that starts after the last change:\n *\n * nats.subscribe(\"nova.v2.system.status\", onStatus, { replayLast: true })\n *\n * Returns a function that unsubscribes when called.\n */\n async subscribe<K extends NatsSubscribeSubject>(\n subject: K,\n ...args: SubscribeArgs<K>\n ): Promise<() => void> {\n // `params` is omitted for subjects with no {param} placeholders, so the\n // handler is the first argument in that case.\n const hasParams = typeof args[0] !== \"function\"\n const params = (hasParams ? args[0] : {}) as NatsOperationParams[K]\n const handler = (hasParams ? args[1] : args[0]) as NatsMessageHandler<K>\n const opts = (hasParams ? args[2] : args[1]) as\n | { replayLast?: boolean; onReplayComplete?: () => void }\n | undefined\n\n const nc = await this.connect()\n const resolvedSubject = buildSubject(subject, params)\n\n // The token positions of the template's {param} placeholders, computed\n // once here so each message's subjectParams is a plain index pick: a\n // delivered message always matches the subscribed pattern token for\n // token, so its params sit at the same positions as in the template.\n const paramPositions: [name: string, index: number][] = []\n for (const [index, token] of subject.split(\".\").entries()) {\n if (token.startsWith(\"{\") && token.endsWith(\"}\")) {\n paramPositions.push([token.slice(1, -1), index])\n }\n }\n\n const deliver = async (msg: Msg | JsMsg) => {\n // Handled per-message: a bad payload or a throwing/rejecting handler\n // should not stop the subscription from processing later messages.\n try {\n const subjectTokens = msg.subject.split(\".\")\n const subjectParams = Object.fromEntries(\n paramPositions.map(([name, index]) => [\n name,\n subjectTokens[index] ?? \"\",\n ]),\n ) as NatsOperationParams[K]\n await handler(\n msg.json<NatsSubscribePayloads[K]>(),\n Object.assign(msg, { subjectParams }),\n )\n } catch (err) {\n console.error(\n `Error handling NATS message on subject \"${resolvedSubject}\"`,\n err,\n )\n }\n }\n\n const deliverAll = (\n messages: AsyncIterable<Msg | JsMsg>,\n afterDeliver?: (msg: Msg | JsMsg) => void,\n ) => {\n ;(async () => {\n for await (const msg of messages) {\n await deliver(msg)\n afterDeliver?.(msg)\n }\n })().catch((err: unknown) => {\n console.error(\n `NATS subscription iterator failed for \"${resolvedSubject}\"`,\n err,\n )\n })\n }\n\n if (opts?.replayLast) {\n // Delivered by JetStream rather than core NATS: an ordered consumer\n // starting at `last_per_subject` yields each matching subject's retained\n // message and then continues with live ones on the same iterator, so\n // there is no gap — and no possible duplicate — between the replayed\n // value and the updates that follow it. Ordered consumers are ephemeral\n // and need no acking, so stopping the iterator is the whole teardown.\n const consumer = await jetstream(nc).consumers.get(\n natsStreamBySubject[subject as NatsPersistedSubject],\n {\n filter_subjects: [resolvedSubject],\n deliver_policy: DeliverPolicy.LastPerSubject,\n },\n )\n\n // How many retained messages this consumer will replay before it is\n // caught up. Read before consuming, because a subject with nothing\n // retained never delivers a message to end the replay on, and a caller\n // waiting for one would wait forever.\n const { num_pending } = await consumer.info()\n\n let replayComplete = false\n const completeReplay = () => {\n if (replayComplete) return\n replayComplete = true\n opts.onReplayComplete?.()\n }\n\n const messages = await consumer.consume()\n // A JsMsg's `info.pending` counts what is still queued behind it, so\n // the first message to report 0 is the last of the replay. Live\n // messages report 0 too, which is why this only fires once.\n deliverAll(messages, (msg) => {\n if (\"info\" in msg && msg.info.pending === 0) completeReplay()\n })\n if (num_pending === 0) completeReplay()\n\n return () => {\n messages.stop()\n }\n }\n\n const sub = nc.subscribe(resolvedSubject)\n deliverAll(sub)\n return () => sub.unsubscribe()\n }\n\n /**\n * Sends a request payload for a NATS subject the server receives, and\n * waits for the JSON-decoded reply.\n *\n * `subject` is the subject template as it appears on the wire, e.g.\n * `\"nova.v2.cells.{cell}.bus-ios.ios.set\"`, with `{param}` placeholders\n * filled in from `params`.\n */\n async request<K extends NatsRequestSubject>(\n subject: K,\n params: NatsOperationParams[K],\n payload: NatsRequestPayloads[K],\n opts: { timeout?: number } = {},\n ): Promise<NatsReplyPayloads[K]> {\n const nc = await this.connect()\n const resolvedSubject = buildSubject(subject, params)\n const msg = await nc.request(resolvedSubject, JSON.stringify(payload), {\n timeout: opts.timeout ?? 5000,\n })\n return msg.json<NatsReplyPayloads[K]>()\n }\n\n /**\n * Publishes a JSON payload to any NATS subject defined in the spec,\n * without waiting for a reply.\n *\n * `subject` is the subject template as it appears on the wire, e.g.\n * `\"nova.v2.cells.{cell}.bus-ios.ios.set\"`, with `{param}` placeholders\n * filled in from `params`.\n */\n async publish<K extends NatsPublishSubject>(\n subject: K,\n params: NatsOperationParams[K],\n payload: NatsPublishPayloads[K],\n ): Promise<void> {\n const nc = await this.connect()\n const resolvedSubject = buildSubject(subject, params)\n nc.publish(resolvedSubject, JSON.stringify(payload))\n }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wandelbots/nova-js",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.5.0-pr.321.1a3f8f3",
|
|
5
5
|
"description": "Official JS client for the Wandelbots API",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"packageManager": "pnpm@11.9.0+sha512.bd682d5d03fe525ef7c9fd6780c6884d1e756ac4c9c9fe00c538782824310dcf90e3ddc4f53835f06dfaebd5085e41855e0bcbb3b60de2ac5bbab89e5036f03b",
|
|
@@ -80,6 +80,7 @@
|
|
|
80
80
|
},
|
|
81
81
|
"dependencies": {
|
|
82
82
|
"@auth0/auth0-spa-js": "^2.24.1",
|
|
83
|
+
"@nats-io/jetstream": "^3.4.0",
|
|
83
84
|
"@nats-io/nats-core": "^3.4.0",
|
|
84
85
|
"axios": "^1.19.0",
|
|
85
86
|
"path-to-regexp": "^8.4.2",
|
|
@@ -5,15 +5,17 @@
|
|
|
5
5
|
* This API is experimental and may change without a major version bump.
|
|
6
6
|
*/
|
|
7
7
|
export { buildNatsServerUrl } from "../../lib/experimental/nats/buildNatsServerUrl.ts"
|
|
8
|
-
export
|
|
9
|
-
NatsOperationParams,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
8
|
+
export {
|
|
9
|
+
type NatsOperationParams,
|
|
10
|
+
type NatsPersistedSubject,
|
|
11
|
+
type NatsPublishPayloads,
|
|
12
|
+
type NatsPublishSubject,
|
|
13
|
+
type NatsReplyPayloads,
|
|
14
|
+
type NatsRequestPayloads,
|
|
15
|
+
type NatsRequestSubject,
|
|
16
|
+
natsStreamBySubject,
|
|
17
|
+
type NatsSubscribePayloads,
|
|
18
|
+
type NatsSubscribeSubject,
|
|
17
19
|
} from "../../lib/experimental/nats/generated/operations.ts"
|
|
18
20
|
// Message payload types for every schema in src/asyncapi.yaml, e.g. `Cell`,
|
|
19
21
|
// `App`, `ProgramStatus` (see NatsSubscribePayloads/NatsRequestPayloads for
|
|
@@ -22,5 +24,6 @@ export type * from "../../lib/experimental/nats/generated/types.ts"
|
|
|
22
24
|
export { NovaNatsClient } from "../../lib/experimental/nats/NovaNatsClient.ts"
|
|
23
25
|
export type {
|
|
24
26
|
NatsSubscribeMsg,
|
|
27
|
+
NatsSubscribeOptions,
|
|
25
28
|
NovaNatsClientConfig,
|
|
26
29
|
} from "../../lib/experimental/nats/NovaNatsClient.ts"
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DeliverPolicy, jetstream, type JsMsg } from "@nats-io/jetstream"
|
|
1
2
|
import {
|
|
2
3
|
type ConnectionOptions,
|
|
3
4
|
type Msg,
|
|
@@ -7,15 +8,17 @@ import {
|
|
|
7
8
|
import type { Nova } from "../../Nova.ts"
|
|
8
9
|
import { buildNatsServerUrl } from "./buildNatsServerUrl.ts"
|
|
9
10
|
import { buildSubject } from "./buildSubject.ts"
|
|
10
|
-
import
|
|
11
|
-
NatsOperationParams,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
import {
|
|
12
|
+
type NatsOperationParams,
|
|
13
|
+
type NatsPersistedSubject,
|
|
14
|
+
type NatsPublishPayloads,
|
|
15
|
+
type NatsPublishSubject,
|
|
16
|
+
type NatsReplyPayloads,
|
|
17
|
+
type NatsRequestPayloads,
|
|
18
|
+
type NatsRequestSubject,
|
|
19
|
+
natsStreamBySubject,
|
|
20
|
+
type NatsSubscribePayloads,
|
|
21
|
+
type NatsSubscribeSubject,
|
|
19
22
|
} from "./generated/operations.ts"
|
|
20
23
|
|
|
21
24
|
export type NovaNatsClientConfig = ConnectionOptions
|
|
@@ -27,7 +30,7 @@ export type NovaNatsClientConfig = ConnectionOptions
|
|
|
27
30
|
* a handler knows which entity a message belongs to. Typed per subject via
|
|
28
31
|
* the generated `NatsOperationParams`.
|
|
29
32
|
*/
|
|
30
|
-
export type NatsSubscribeMsg<K extends NatsSubscribeSubject> = Msg & {
|
|
33
|
+
export type NatsSubscribeMsg<K extends NatsSubscribeSubject> = (Msg | JsMsg) & {
|
|
31
34
|
subjectParams: NatsOperationParams[K]
|
|
32
35
|
}
|
|
33
36
|
|
|
@@ -36,10 +39,45 @@ type NatsMessageHandler<K extends NatsSubscribeSubject> = (
|
|
|
36
39
|
msg: NatsSubscribeMsg<K>,
|
|
37
40
|
) => void | Promise<void>
|
|
38
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Extra options for {@link NovaNatsClient.subscribe}. `replayLast` is only
|
|
44
|
+
* offered on subjects the spec marks as retaining their latest message, so
|
|
45
|
+
* asking for a replay that could never arrive is a compile error.
|
|
46
|
+
*/
|
|
47
|
+
export type NatsSubscribeOptions<K extends NatsSubscribeSubject> =
|
|
48
|
+
K extends NatsPersistedSubject
|
|
49
|
+
? {
|
|
50
|
+
/**
|
|
51
|
+
* Deliver the subject's current value immediately on subscribe,
|
|
52
|
+
* before any subsequent updates. With a wildcard subscription (e.g.
|
|
53
|
+
* `{ cell: "*" }`) the current value of every matching subject is
|
|
54
|
+
* delivered, not just one.
|
|
55
|
+
*/
|
|
56
|
+
replayLast?: boolean
|
|
57
|
+
/**
|
|
58
|
+
* Called once every retained message has been passed to the handler,
|
|
59
|
+
* i.e. when the handler has seen the subject's current state and
|
|
60
|
+
* everything after it is a live update. Fires exactly once, and also
|
|
61
|
+
* when there is nothing retained at all (an empty wildcard, e.g. no
|
|
62
|
+
* apps installed) — where waiting for a first message would hang
|
|
63
|
+
* forever. Only meaningful together with `replayLast`; without it
|
|
64
|
+
* there is no retained state to wait for.
|
|
65
|
+
*/
|
|
66
|
+
onReplayComplete?: () => void
|
|
67
|
+
}
|
|
68
|
+
: // Not `Record<never, never>`: that behaves like `{}` and so accepts any
|
|
69
|
+
// object, letting `replayLast` through unchecked. Typing the property as
|
|
70
|
+
// `never` is what makes passing it an error.
|
|
71
|
+
{ replayLast?: never; onReplayComplete?: never }
|
|
72
|
+
|
|
39
73
|
type SubscribeArgs<K extends NatsSubscribeSubject> =
|
|
40
74
|
keyof NatsOperationParams[K] extends never
|
|
41
|
-
? [handler: NatsMessageHandler<K>]
|
|
42
|
-
: [
|
|
75
|
+
? [handler: NatsMessageHandler<K>, opts?: NatsSubscribeOptions<K>]
|
|
76
|
+
: [
|
|
77
|
+
params: NatsOperationParams[K],
|
|
78
|
+
handler: NatsMessageHandler<K>,
|
|
79
|
+
opts?: NatsSubscribeOptions<K>,
|
|
80
|
+
]
|
|
43
81
|
|
|
44
82
|
/**
|
|
45
83
|
* Typed NATS client for the Wandelbots NOVA messaging API, generated from
|
|
@@ -110,20 +148,29 @@ export class NovaNatsClient {
|
|
|
110
148
|
* nats.subscribe("nova.v2.cells.{cell}.status", { cell: "*" },
|
|
111
149
|
* (services, msg) => console.log(msg.subjectParams.cell, services))
|
|
112
150
|
*
|
|
151
|
+
* On subjects that retain their latest message, pass `{ replayLast: true }`
|
|
152
|
+
* to receive the current value immediately instead of waiting for the next
|
|
153
|
+
* update — useful for a subscriber that starts after the last change:
|
|
154
|
+
*
|
|
155
|
+
* nats.subscribe("nova.v2.system.status", onStatus, { replayLast: true })
|
|
156
|
+
*
|
|
113
157
|
* Returns a function that unsubscribes when called.
|
|
114
158
|
*/
|
|
115
159
|
async subscribe<K extends NatsSubscribeSubject>(
|
|
116
160
|
subject: K,
|
|
117
161
|
...args: SubscribeArgs<K>
|
|
118
162
|
): Promise<() => void> {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
163
|
+
// `params` is omitted for subjects with no {param} placeholders, so the
|
|
164
|
+
// handler is the first argument in that case.
|
|
165
|
+
const hasParams = typeof args[0] !== "function"
|
|
166
|
+
const params = (hasParams ? args[0] : {}) as NatsOperationParams[K]
|
|
167
|
+
const handler = (hasParams ? args[1] : args[0]) as NatsMessageHandler<K>
|
|
168
|
+
const opts = (hasParams ? args[2] : args[1]) as
|
|
169
|
+
| { replayLast?: boolean; onReplayComplete?: () => void }
|
|
170
|
+
| undefined
|
|
123
171
|
|
|
124
172
|
const nc = await this.connect()
|
|
125
173
|
const resolvedSubject = buildSubject(subject, params)
|
|
126
|
-
const sub = nc.subscribe(resolvedSubject)
|
|
127
174
|
|
|
128
175
|
// The token positions of the template's {param} placeholders, computed
|
|
129
176
|
// once here so each message's subjectParams is a plain index pick: a
|
|
@@ -136,36 +183,90 @@ export class NovaNatsClient {
|
|
|
136
183
|
}
|
|
137
184
|
}
|
|
138
185
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
)
|
|
160
|
-
}
|
|
186
|
+
const deliver = async (msg: Msg | JsMsg) => {
|
|
187
|
+
// Handled per-message: a bad payload or a throwing/rejecting handler
|
|
188
|
+
// should not stop the subscription from processing later messages.
|
|
189
|
+
try {
|
|
190
|
+
const subjectTokens = msg.subject.split(".")
|
|
191
|
+
const subjectParams = Object.fromEntries(
|
|
192
|
+
paramPositions.map(([name, index]) => [
|
|
193
|
+
name,
|
|
194
|
+
subjectTokens[index] ?? "",
|
|
195
|
+
]),
|
|
196
|
+
) as NatsOperationParams[K]
|
|
197
|
+
await handler(
|
|
198
|
+
msg.json<NatsSubscribePayloads[K]>(),
|
|
199
|
+
Object.assign(msg, { subjectParams }),
|
|
200
|
+
)
|
|
201
|
+
} catch (err) {
|
|
202
|
+
console.error(
|
|
203
|
+
`Error handling NATS message on subject "${resolvedSubject}"`,
|
|
204
|
+
err,
|
|
205
|
+
)
|
|
161
206
|
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const deliverAll = (
|
|
210
|
+
messages: AsyncIterable<Msg | JsMsg>,
|
|
211
|
+
afterDeliver?: (msg: Msg | JsMsg) => void,
|
|
212
|
+
) => {
|
|
213
|
+
;(async () => {
|
|
214
|
+
for await (const msg of messages) {
|
|
215
|
+
await deliver(msg)
|
|
216
|
+
afterDeliver?.(msg)
|
|
217
|
+
}
|
|
218
|
+
})().catch((err: unknown) => {
|
|
219
|
+
console.error(
|
|
220
|
+
`NATS subscription iterator failed for "${resolvedSubject}"`,
|
|
221
|
+
err,
|
|
222
|
+
)
|
|
223
|
+
})
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (opts?.replayLast) {
|
|
227
|
+
// Delivered by JetStream rather than core NATS: an ordered consumer
|
|
228
|
+
// starting at `last_per_subject` yields each matching subject's retained
|
|
229
|
+
// message and then continues with live ones on the same iterator, so
|
|
230
|
+
// there is no gap — and no possible duplicate — between the replayed
|
|
231
|
+
// value and the updates that follow it. Ordered consumers are ephemeral
|
|
232
|
+
// and need no acking, so stopping the iterator is the whole teardown.
|
|
233
|
+
const consumer = await jetstream(nc).consumers.get(
|
|
234
|
+
natsStreamBySubject[subject as NatsPersistedSubject],
|
|
235
|
+
{
|
|
236
|
+
filter_subjects: [resolvedSubject],
|
|
237
|
+
deliver_policy: DeliverPolicy.LastPerSubject,
|
|
238
|
+
},
|
|
166
239
|
)
|
|
167
|
-
})
|
|
168
240
|
|
|
241
|
+
// How many retained messages this consumer will replay before it is
|
|
242
|
+
// caught up. Read before consuming, because a subject with nothing
|
|
243
|
+
// retained never delivers a message to end the replay on, and a caller
|
|
244
|
+
// waiting for one would wait forever.
|
|
245
|
+
const { num_pending } = await consumer.info()
|
|
246
|
+
|
|
247
|
+
let replayComplete = false
|
|
248
|
+
const completeReplay = () => {
|
|
249
|
+
if (replayComplete) return
|
|
250
|
+
replayComplete = true
|
|
251
|
+
opts.onReplayComplete?.()
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const messages = await consumer.consume()
|
|
255
|
+
// A JsMsg's `info.pending` counts what is still queued behind it, so
|
|
256
|
+
// the first message to report 0 is the last of the replay. Live
|
|
257
|
+
// messages report 0 too, which is why this only fires once.
|
|
258
|
+
deliverAll(messages, (msg) => {
|
|
259
|
+
if ("info" in msg && msg.info.pending === 0) completeReplay()
|
|
260
|
+
})
|
|
261
|
+
if (num_pending === 0) completeReplay()
|
|
262
|
+
|
|
263
|
+
return () => {
|
|
264
|
+
messages.stop()
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const sub = nc.subscribe(resolvedSubject)
|
|
269
|
+
deliverAll(sub)
|
|
169
270
|
return () => sub.unsubscribe()
|
|
170
271
|
}
|
|
171
272
|
|
|
@@ -732,6 +732,26 @@ export interface NatsSubscribePayloads {
|
|
|
732
732
|
|
|
733
733
|
export type NatsSubscribeSubject = keyof NatsSubscribePayloads
|
|
734
734
|
|
|
735
|
+
/**
|
|
736
|
+
* The JetStream stream backing each subject that retains only its latest
|
|
737
|
+
* message (`max_msgs_per_subject: 1` in src/asyncapi.yaml), so that
|
|
738
|
+
* message is the subject's current state.
|
|
739
|
+
*/
|
|
740
|
+
export const natsStreamBySubject = {
|
|
741
|
+
"nova.v2.cells.{cell}": "system-state",
|
|
742
|
+
"nova.v2.cells.{cell}.apps.{app}": "system-state",
|
|
743
|
+
"nova.v2.cells.{cell}.controllers.{controller}": "system-state",
|
|
744
|
+
"nova.v2.cells.{cell}.status": "system-state",
|
|
745
|
+
"nova.v2.system.status": "system-state",
|
|
746
|
+
"nova.v2.cells.{cell}.collision.setups.{setup}": "system-state",
|
|
747
|
+
"nova.v2.cells.{cell}.bus-ios.status": "system-state",
|
|
748
|
+
"nova.v2.cells.{cell}.controllers.{controller}.motion-groups.{motion-group}.description":
|
|
749
|
+
"system-state",
|
|
750
|
+
} as const
|
|
751
|
+
|
|
752
|
+
/** Subjects whose current value can be replayed via `{ replayLast: true }`. */
|
|
753
|
+
export type NatsPersistedSubject = keyof typeof natsStreamBySubject
|
|
754
|
+
|
|
735
755
|
/** Request payload types for subjects the client sends requests to. */
|
|
736
756
|
export interface NatsRequestPayloads {
|
|
737
757
|
/**
|