@wandelbots/nova-js 4.3.0 → 4.4.0-pr.320.c17b82c
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 +24 -1
- package/dist/experimental/nats/index.d.mts.map +1 -1
- package/dist/experimental/nats/index.mjs +37 -1
- package/dist/experimental/nats/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/experimental/nats/index.ts +4 -0
- package/src/lib/experimental/nats/matchSubject.ts +49 -0
|
@@ -11,6 +11,29 @@ import { ConnectionOptions, Msg, NatsConnection } from "@nats-io/nats-core";
|
|
|
11
11
|
*/
|
|
12
12
|
declare function buildNatsServerUrl(instanceUrl: string): string;
|
|
13
13
|
//#endregion
|
|
14
|
+
//#region src/lib/experimental/nats/matchSubject.d.ts
|
|
15
|
+
/**
|
|
16
|
+
* The inverse of `buildSubject`: extracts the `{param}` values of an
|
|
17
|
+
* AsyncAPI-style channel address template from a concrete NATS subject.
|
|
18
|
+
*
|
|
19
|
+
* Useful when subscribing with a wildcard param (e.g. `{ cell: "*" }`) to
|
|
20
|
+
* recover which entity a received message belongs to from `msg.subject`,
|
|
21
|
+
* instead of indexing into `subject.split(".")` by hand:
|
|
22
|
+
*
|
|
23
|
+
* matchSubject("nova.v2.cells.{cell}.status", "nova.v2.cells.a.status")
|
|
24
|
+
* // -> { cell: "a" }
|
|
25
|
+
*
|
|
26
|
+
* Matching is per dot-separated token: a `{param}` template token captures
|
|
27
|
+
* the concrete token under the param's name, a literal `*` token matches any
|
|
28
|
+
* single token without capturing, a trailing `>` token matches one or more
|
|
29
|
+
* remaining tokens, and any other token must match exactly. Placeholders
|
|
30
|
+
* embedded inside a larger token (e.g. `prefix-{param}`) are not supported.
|
|
31
|
+
*
|
|
32
|
+
* Returns the captured params (an empty object for a template without
|
|
33
|
+
* params), or `undefined` when the subject does not match the template.
|
|
34
|
+
*/
|
|
35
|
+
declare function matchSubject(template: string, subject: string): Record<string, string> | undefined;
|
|
36
|
+
//#endregion
|
|
14
37
|
//#region src/lib/experimental/nats/generated/types.d.ts
|
|
15
38
|
/**
|
|
16
39
|
* AUTO-GENERATED FILE - DO NOT EDIT.
|
|
@@ -3230,5 +3253,5 @@ declare class NovaNatsClient {
|
|
|
3230
3253
|
publish<K extends NatsPublishSubject>(subject: K, params: NatsOperationParams[K], payload: NatsPublishPayloads[K]): Promise<void>;
|
|
3231
3254
|
}
|
|
3232
3255
|
//#endregion
|
|
3233
|
-
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 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 };
|
|
3256
|
+
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 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, matchSubject };
|
|
3234
3257
|
//# 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":";;;;;;;;;;;iBAUgB,mBAAmB;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/lib/experimental/nats/buildNatsServerUrl.ts","../../../src/lib/experimental/nats/matchSubject.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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"}
|
|
@@ -14,6 +14,42 @@ function buildNatsServerUrl(instanceUrl) {
|
|
|
14
14
|
return `${url.protocol === "https:" ? "wss:" : "ws:"}//${url.host}/api/nats`;
|
|
15
15
|
}
|
|
16
16
|
//#endregion
|
|
17
|
+
//#region src/lib/experimental/nats/matchSubject.ts
|
|
18
|
+
/**
|
|
19
|
+
* The inverse of `buildSubject`: extracts the `{param}` values of an
|
|
20
|
+
* AsyncAPI-style channel address template from a concrete NATS subject.
|
|
21
|
+
*
|
|
22
|
+
* Useful when subscribing with a wildcard param (e.g. `{ cell: "*" }`) to
|
|
23
|
+
* recover which entity a received message belongs to from `msg.subject`,
|
|
24
|
+
* instead of indexing into `subject.split(".")` by hand:
|
|
25
|
+
*
|
|
26
|
+
* matchSubject("nova.v2.cells.{cell}.status", "nova.v2.cells.a.status")
|
|
27
|
+
* // -> { cell: "a" }
|
|
28
|
+
*
|
|
29
|
+
* Matching is per dot-separated token: a `{param}` template token captures
|
|
30
|
+
* the concrete token under the param's name, a literal `*` token matches any
|
|
31
|
+
* single token without capturing, a trailing `>` token matches one or more
|
|
32
|
+
* remaining tokens, and any other token must match exactly. Placeholders
|
|
33
|
+
* embedded inside a larger token (e.g. `prefix-{param}`) are not supported.
|
|
34
|
+
*
|
|
35
|
+
* Returns the captured params (an empty object for a template without
|
|
36
|
+
* params), or `undefined` when the subject does not match the template.
|
|
37
|
+
*/
|
|
38
|
+
function matchSubject(template, subject) {
|
|
39
|
+
const templateTokens = template.split(".");
|
|
40
|
+
const subjectTokens = subject.split(".");
|
|
41
|
+
const params = {};
|
|
42
|
+
for (const [i, token] of templateTokens.entries()) {
|
|
43
|
+
if (token === ">" && i === templateTokens.length - 1) return subjectTokens.length > i ? params : void 0;
|
|
44
|
+
const subjectToken = subjectTokens[i];
|
|
45
|
+
if (subjectToken === void 0) return void 0;
|
|
46
|
+
if (token.startsWith("{") && token.endsWith("}") && token.length > 2) params[token.slice(1, -1)] = subjectToken;
|
|
47
|
+
else if (token !== "*" && token !== subjectToken) return;
|
|
48
|
+
}
|
|
49
|
+
if (subjectTokens.length !== templateTokens.length) return void 0;
|
|
50
|
+
return params;
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
17
53
|
//#region src/lib/experimental/nats/buildSubject.ts
|
|
18
54
|
/**
|
|
19
55
|
* Builds a NATS subject from an AsyncAPI-style channel address template
|
|
@@ -151,6 +187,6 @@ var NovaNatsClient = class {
|
|
|
151
187
|
}
|
|
152
188
|
};
|
|
153
189
|
//#endregion
|
|
154
|
-
export { NovaNatsClient, buildNatsServerUrl };
|
|
190
|
+
export { NovaNatsClient, buildNatsServerUrl, matchSubject };
|
|
155
191
|
|
|
156
192
|
//# 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\ntype NatsMessageHandler<K extends NatsSubscribeSubject> = (\n payload: NatsSubscribePayloads[K],\n msg: Msg,\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 * 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 ;(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 await handler(msg.json<NatsSubscribePayloads[K]>(), msg)\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":";;;;;;;;;;;AAUA,SAAgB,mBAAmB,aAA6B;CAC9D,MAAM,MAAM,qBAAqB,WAAW;CAE5C,OAAO,GADU,IAAI,aAAa,WAAW,SAAS,MACnC,IAAI,IAAI,KAAK;AAClC;;;;;;;;ACRA,SAAS,mBAAmB,MAAuB;CACjD,MAAM,OAAO,KAAK,WAAW,CAAC;CAC9B,OACG,QAAQ,MAAM,QAAQ,MACtB,QAAQ,MAAM,QAAQ,MACtB,QAAQ,MAAM,QAAQ,OACvB,SAAS,OACT,SAAS;AAEb;AAEA,SAAS,oBAAoB,OAAwB;CAInD,IAAI,UAAU,KAAK,OAAO;CAC1B,IAAI,MAAM,WAAW,GAAG,OAAO;CAC/B,KAAK,MAAM,QAAQ,OACjB,IAAI,CAAC,mBAAmB,IAAI,GAAG,OAAO;CAExC,OAAO;AACT;AAEA,SAAgB,aACd,UACA,QACQ;CAIR,IAAI,SAAS;CACb,IAAI,SAAS;CAEb,OAAO,SAAS,SAAS,QAAQ;EAC/B,MAAM,YAAY,SAAS,QAAQ,KAAK,MAAM;EAC9C,IAAI,cAAc,IAAI;GACpB,UAAU,SAAS,MAAM,MAAM;GAC/B;EACF;EAEA,MAAM,aAAa,SAAS,QAAQ,KAAK,YAAY,CAAC;EACtD,IAAI,eAAe,IAAI;GACrB,UAAU,SAAS,MAAM,MAAM;GAC/B;EACF;EAEA,UAAU,SAAS,MAAM,QAAQ,SAAS;EAC1C,MAAM,YAAY,SAAS,MAAM,YAAY,GAAG,UAAU;EAC1D,MAAM,QAAQ,OAAO;EACrB,IAAI,UAAU,KAAA,GACZ,MAAM,IAAI,MACR,wCAAwC,UAAU,iBAAiB,SAAS,EAC9E;EAEF,IAAI,CAAC,oBAAoB,KAAK,GAC5B,MAAM,IAAI,MACR,wCAAwC,UAAU,MAAM,MAAM,qEAChE;EAEF,UAAU;EAEV,SAAS,aAAa;CACxB;CAEA,OAAO;AACT;;;;;;;;;ACjCA,IAAa,iBAAb,MAA4B;CAC1B;CACA,oBAA4D;CAE5D,YAAY,MAAY,SAA+B,CAAC,GAAG;EACzD,KAAK,SAAS;GACZ,SAAS,mBAAmB,KAAK,YAAY,IAAI;GAIjD,GAAI,KAAK,cAAc,EAAE,OAAO,KAAK,YAAY,IAAI,CAAC;GACtD,GAAG;EACL;CACF;;;;;;CAOA,UAAmC;EACjC,IAAI,CAAC,KAAK,mBACR,KAAK,oBAAoB,UAAU,KAAK,MAAM,CAAC,CAAC,OAAO,QAAiB;GAEtE,KAAK,oBAAoB;GACzB,MAAM;EACR,CAAC;EAEH,OAAO,KAAK;CACd;;CAGA,MAAM,QAAuB;EAC3B,MAAM,oBAAoB,KAAK;EAC/B,KAAK,oBAAoB;EACzB,IAAI,CAAC,mBAAmB;EACxB,IAAI;GAEF,OAAM,MADW,kBAAA,CACR,MAAM;EACjB,QAAQ,CAER;CACF;;;;;;;;;;;;;;;CAgBA,MAAM,UACJ,SACA,GAAG,MACkB;EACrB,MAAM,CAAC,QAAQ,WACb,KAAK,WAAW,IACX,CAAC,CAAC,GAAG,KAAK,EAAE,IACZ,CAAC,KAAK,IAAI,KAAK,EAAE;EAExB,MAAM,KAAK,MAAM,KAAK,QAAQ;EAC9B,MAAM,kBAAkB,aAAa,SAAS,MAAM;EACpD,MAAM,MAAM,GAAG,UAAU,eAAe;EAEvC,CAAC,YAAY;GACZ,WAAW,MAAM,OAAO,KAGtB,IAAI;IACF,MAAM,QAAQ,IAAI,KAA+B,GAAG,GAAG;GACzD,SAAS,KAAK;IACZ,QAAQ,MACN,2CAA2C,gBAAgB,IAC3D,GACF;GACF;EAEJ,EAAA,CAAG,CAAC,CAAC,OAAO,QAAiB;GAC3B,QAAQ,MACN,0CAA0C,gBAAgB,IAC1D,GACF;EACF,CAAC;EAED,aAAa,IAAI,YAAY;CAC/B;;;;;;;;;CAUA,MAAM,QACJ,SACA,QACA,SACA,OAA6B,CAAC,GACC;EAC/B,MAAM,KAAK,MAAM,KAAK,QAAQ;EAC9B,MAAM,kBAAkB,aAAa,SAAS,MAAM;EAIpD,QAAO,MAHW,GAAG,QAAQ,iBAAiB,KAAK,UAAU,OAAO,GAAG,EACrE,SAAS,KAAK,WAAW,IAC3B,CAAC,EAAA,CACU,KAA2B;CACxC;;;;;;;;;CAUA,MAAM,QACJ,SACA,QACA,SACe;EACf,MAAM,KAAK,MAAM,KAAK,QAAQ;EAC9B,MAAM,kBAAkB,aAAa,SAAS,MAAM;EACpD,GAAG,QAAQ,iBAAiB,KAAK,UAAU,OAAO,CAAC;CACrD;AACF"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../src/lib/experimental/nats/buildNatsServerUrl.ts","../../../src/lib/experimental/nats/matchSubject.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 * The inverse of `buildSubject`: extracts the `{param}` values of an\n * AsyncAPI-style channel address template from a concrete NATS subject.\n *\n * Useful when subscribing with a wildcard param (e.g. `{ cell: \"*\" }`) to\n * recover which entity a received message belongs to from `msg.subject`,\n * instead of indexing into `subject.split(\".\")` by hand:\n *\n * matchSubject(\"nova.v2.cells.{cell}.status\", \"nova.v2.cells.a.status\")\n * // -> { cell: \"a\" }\n *\n * Matching is per dot-separated token: a `{param}` template token captures\n * the concrete token under the param's name, a literal `*` token matches any\n * single token without capturing, a trailing `>` token matches one or more\n * remaining tokens, and any other token must match exactly. Placeholders\n * embedded inside a larger token (e.g. `prefix-{param}`) are not supported.\n *\n * Returns the captured params (an empty object for a template without\n * params), or `undefined` when the subject does not match the template.\n */\nexport function matchSubject(\n template: string,\n subject: string,\n): Record<string, string> | undefined {\n const templateTokens = template.split(\".\")\n const subjectTokens = subject.split(\".\")\n\n const params: Record<string, string> = {}\n\n for (const [i, token] of templateTokens.entries()) {\n if (token === \">\" && i === templateTokens.length - 1) {\n // `>` must match at least one remaining token.\n return subjectTokens.length > i ? params : undefined\n }\n\n const subjectToken = subjectTokens[i]\n if (subjectToken === undefined) return undefined\n\n if (token.startsWith(\"{\") && token.endsWith(\"}\") && token.length > 2) {\n params[token.slice(1, -1)] = subjectToken\n } else if (token !== \"*\" && token !== subjectToken) {\n return undefined\n }\n }\n\n if (subjectTokens.length !== templateTokens.length) return undefined\n\n return params\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\ntype NatsMessageHandler<K extends NatsSubscribeSubject> = (\n payload: NatsSubscribePayloads[K],\n msg: Msg,\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 * 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 ;(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 await handler(msg.json<NatsSubscribePayloads[K]>(), msg)\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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"}
|
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.4.0-pr.320.c17b82c",
|
|
5
5
|
"description": "Official JS client for the Wandelbots API",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"packageManager": "pnpm@11.9.0+sha512.bd682d5d03fe525ef7c9fd6780c6884d1e756ac4c9c9fe00c538782824310dcf90e3ddc4f53835f06dfaebd5085e41855e0bcbb3b60de2ac5bbab89e5036f03b",
|
|
@@ -5,6 +5,10 @@
|
|
|
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
|
+
// Extract the `{param}` values of a channel template back out of a received
|
|
9
|
+
// message's concrete subject (the inverse of what the client does when
|
|
10
|
+
// subscribing) — needed by consumers subscribing with wildcard params.
|
|
11
|
+
export { matchSubject } from "../../lib/experimental/nats/matchSubject.ts"
|
|
8
12
|
export type {
|
|
9
13
|
NatsOperationParams,
|
|
10
14
|
NatsPublishPayloads,
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The inverse of `buildSubject`: extracts the `{param}` values of an
|
|
3
|
+
* AsyncAPI-style channel address template from a concrete NATS subject.
|
|
4
|
+
*
|
|
5
|
+
* Useful when subscribing with a wildcard param (e.g. `{ cell: "*" }`) to
|
|
6
|
+
* recover which entity a received message belongs to from `msg.subject`,
|
|
7
|
+
* instead of indexing into `subject.split(".")` by hand:
|
|
8
|
+
*
|
|
9
|
+
* matchSubject("nova.v2.cells.{cell}.status", "nova.v2.cells.a.status")
|
|
10
|
+
* // -> { cell: "a" }
|
|
11
|
+
*
|
|
12
|
+
* Matching is per dot-separated token: a `{param}` template token captures
|
|
13
|
+
* the concrete token under the param's name, a literal `*` token matches any
|
|
14
|
+
* single token without capturing, a trailing `>` token matches one or more
|
|
15
|
+
* remaining tokens, and any other token must match exactly. Placeholders
|
|
16
|
+
* embedded inside a larger token (e.g. `prefix-{param}`) are not supported.
|
|
17
|
+
*
|
|
18
|
+
* Returns the captured params (an empty object for a template without
|
|
19
|
+
* params), or `undefined` when the subject does not match the template.
|
|
20
|
+
*/
|
|
21
|
+
export function matchSubject(
|
|
22
|
+
template: string,
|
|
23
|
+
subject: string,
|
|
24
|
+
): Record<string, string> | undefined {
|
|
25
|
+
const templateTokens = template.split(".")
|
|
26
|
+
const subjectTokens = subject.split(".")
|
|
27
|
+
|
|
28
|
+
const params: Record<string, string> = {}
|
|
29
|
+
|
|
30
|
+
for (const [i, token] of templateTokens.entries()) {
|
|
31
|
+
if (token === ">" && i === templateTokens.length - 1) {
|
|
32
|
+
// `>` must match at least one remaining token.
|
|
33
|
+
return subjectTokens.length > i ? params : undefined
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const subjectToken = subjectTokens[i]
|
|
37
|
+
if (subjectToken === undefined) return undefined
|
|
38
|
+
|
|
39
|
+
if (token.startsWith("{") && token.endsWith("}") && token.length > 2) {
|
|
40
|
+
params[token.slice(1, -1)] = subjectToken
|
|
41
|
+
} else if (token !== "*" && token !== subjectToken) {
|
|
42
|
+
return undefined
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (subjectTokens.length !== templateTokens.length) return undefined
|
|
47
|
+
|
|
48
|
+
return params
|
|
49
|
+
}
|