@wandelbots/nova-js 4.3.0-pr.318.e48e5ca → 4.4.0-pr.320.79d16cc

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.
@@ -44,44 +44,6 @@ declare class Pose implements Pose$1 {
44
44
  toJSON(): Pose$1;
45
45
  }
46
46
  //#endregion
47
- //#region src/lib/experimental/math/augmentMath.d.ts
48
- type PoseKeys = keyof Pose$1;
49
- type IsExactlyPoseData<T> = [Exclude<keyof T, PoseKeys>] extends [never] ? [Exclude<PoseKeys, keyof T>] extends [never] ? true : false : false;
50
- type MaxDepth = [unknown, unknown, unknown, unknown, unknown, unknown, unknown, unknown, unknown, unknown];
51
- type AugmentPosesCore<T, Depth extends readonly unknown[]> = Depth["length"] extends MaxDepth["length"] ? T : IsExactlyPoseData<T> extends true ? Pose : T extends readonly (infer U)[] ? DeepPoseAugmented<U, [...Depth, unknown]>[] : T extends object ? { [K in keyof T]: DeepPoseAugmented<T[K], [...Depth, unknown]>; } : T;
52
- /**
53
- * Type-level counterpart to `augmentPoses`: recursively replaces any
54
- * `PoseData`-shaped field with `Pose` (distributing over unions, e.g. so
55
- * optional `Pose | undefined` fields keep the `| undefined`), so the static
56
- * type matches what's actually returned at runtime.
57
- */
58
- type DeepPoseAugmented<T, Depth extends readonly unknown[] = []> = T extends unknown ? AugmentPosesCore<T, Depth> : never;
59
- /**
60
- * Recursively walks a parsed API response/request body, upgrading any
61
- * `Pose`-shaped objects in place to `Pose` instances so they gain math
62
- * methods (`multiply`, `inverse`, ...) while remaining JSON/wire-compatible.
63
- * Used by `augmentMath` for `nova.api.*` responses; exported so it can also
64
- * be applied manually to e.g. websocket messages.
65
- */
66
- declare function augmentPoses<T>(value: T): DeepPoseAugmented<T>;
67
- type WithAugmentedPosesCore<T, Depth extends readonly unknown[]> = Depth["length"] extends MaxDepth["length"] ? T : T extends ((...args: infer A) => Promise<infer R>) ? (...args: A) => Promise<DeepPoseAugmented<R>> : T extends object ? { [K in keyof T]: WithAugmentedPosesCore<T[K], [...Depth, unknown]>; } : T;
68
- /**
69
- * Recursively maps every method on `Nova["api"]` (and its nested API groups)
70
- * so calls are typed as returning `DeepPoseAugmented` results, matching what
71
- * `augmentMath` does at runtime.
72
- */
73
- type WithAugmentedPoses<T> = WithAugmentedPosesCore<T, []>;
74
- type NovaWithMath = Omit<Nova, "api"> & {
75
- readonly api: WithAugmentedPoses<Nova["api"]>;
76
- };
77
- /**
78
- * Wraps a `Nova` instance so every `nova.api.*` call automatically upgrades
79
- * `Pose`-shaped fields in its response to `Pose` instances (with math
80
- * methods like `multiply`/`inverse`). Returns a new proxied view - does not
81
- * mutate the original `nova` instance.
82
- */
83
- declare function augmentMath(nova: Nova): NovaWithMath;
84
- //#endregion
85
47
  //#region src/lib/experimental/math/Quaternion.d.ts
86
48
  /**
87
49
  * A unit quaternion representing a 3D rotation. Mirrors the subset of
@@ -124,7 +86,7 @@ declare class Quaternion implements QuaternionData {
124
86
  multiply(other: QuaternionData): Quaternion;
125
87
  /** Conjugate: negates the vector part. Equal to `inverse()` for unit quaternions. */
126
88
  conjugate(): Quaternion;
127
- /** Inverse rotation. Equivalent to `conjugate()` since a `Pose`'s orientation is always a unit quaternion. */
89
+ /** Inverse: `conjugate()` scaled by `1 / squaredNorm()`, matching `Eigen::Quaternion::inverse()`. Equal to `conjugate()` for unit quaternions. */
128
90
  inverse(): Quaternion;
129
91
  /**
130
92
  * The angle (in rad, always within [0, pi]) of the rotation that takes
@@ -136,5 +98,43 @@ declare class Quaternion implements QuaternionData {
136
98
  toJSON(): QuaternionData;
137
99
  }
138
100
  //#endregion
139
- export { type DeepPoseAugmented, type NovaWithMath, Pose, type Pose$1 as PoseData, Quaternion, type QuaternionData, augmentMath, augmentPoses };
101
+ //#region src/lib/experimental/math/withMath.d.ts
102
+ type PoseKeys = keyof Pose$1;
103
+ type IsExactlyPoseData<T> = [Exclude<keyof T, PoseKeys>] extends [never] ? [Exclude<PoseKeys, keyof T>] extends [never] ? true : false : false;
104
+ type MaxDepth = [unknown, unknown, unknown, unknown, unknown, unknown, unknown, unknown, unknown, unknown];
105
+ type AugmentPosesCore<T, Depth extends readonly unknown[]> = Depth["length"] extends MaxDepth["length"] ? T : IsExactlyPoseData<T> extends true ? Pose : T extends readonly (infer U)[] ? DeepPoseAugmented<U, [...Depth, unknown]>[] : T extends object ? { [K in keyof T]: DeepPoseAugmented<T[K], [...Depth, unknown]>; } : T;
106
+ /**
107
+ * Type-level counterpart to `augmentPoses`: recursively replaces any
108
+ * `PoseData`-shaped field with `Pose` (distributing over unions, e.g. so
109
+ * optional `Pose | undefined` fields keep the `| undefined`), so the static
110
+ * type matches what's actually returned at runtime.
111
+ */
112
+ type DeepPoseAugmented<T, Depth extends readonly unknown[] = []> = T extends unknown ? AugmentPosesCore<T, Depth> : never;
113
+ /**
114
+ * Recursively walks a parsed API response/request body, upgrading any
115
+ * `Pose`-shaped objects in place to `Pose` instances so they gain math
116
+ * methods (`multiply`, `inverse`, ...) while remaining JSON/wire-compatible.
117
+ * Used by `withMath` for `nova.api.*` responses; exported so it can also
118
+ * be applied manually to e.g. websocket messages.
119
+ */
120
+ declare function augmentPoses<T>(value: T): DeepPoseAugmented<T>;
121
+ type WithAugmentedPosesCore<T, Depth extends readonly unknown[]> = Depth["length"] extends MaxDepth["length"] ? T : T extends ((...args: infer A) => Promise<infer R>) ? (...args: A) => Promise<DeepPoseAugmented<R>> : T extends object ? { [K in keyof T]: WithAugmentedPosesCore<T[K], [...Depth, unknown]>; } : T;
122
+ /**
123
+ * Recursively maps every method on `Nova["api"]` (and its nested API groups)
124
+ * so calls are typed as returning `DeepPoseAugmented` results, matching what
125
+ * `withMath` does at runtime.
126
+ */
127
+ type WithAugmentedPoses<T> = WithAugmentedPosesCore<T, []>;
128
+ type NovaWithMath = Omit<Nova, "api"> & {
129
+ readonly api: WithAugmentedPoses<Nova["api"]>;
130
+ };
131
+ /**
132
+ * Wraps a `Nova` instance so every `nova.api.*` call automatically upgrades
133
+ * `Pose`-shaped fields in its response to `Pose` instances (with math
134
+ * methods like `multiply`/`inverse`). Returns a new proxied view - does not
135
+ * mutate the original `nova` instance.
136
+ */
137
+ declare function withMath(nova: Nova): NovaWithMath;
138
+ //#endregion
139
+ export { type DeepPoseAugmented, type NovaWithMath, Pose, type Pose$1 as PoseData, Quaternion, type QuaternionData, augmentPoses, withMath };
140
140
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/lib/experimental/math/Pose.ts","../../../src/lib/experimental/math/augmentMath.ts","../../../src/lib/experimental/math/Quaternion.ts"],"mappings":";;;;;;;;;cA+Ba,gBAAgB;WAClB;WACA;EAGP,YAAA,qBACA;SAkBK,KAAK,MAAM,SAAW;SAUtB,YAAY;;;;;;;EAUnB,SAAS,OAAO,SAAW;;EAc3B,WAAW;;EAWX,eAAe;;;;;;;;;EAaf,SACE,OAAO,QACP,wBACA;;;;;;EA0BF;;EAWA,SAAS;EAKT,UAAU;;;;KC1JP,iBAAiB;KAEjB,kBAAkB,MAAM,cAAc,GAAG,8BACzC,QAAQ,gBAAgB;KAOxB;KAaA,iBACH,GACA,oCACE,wBAAwB,qBACxB,IACA,kBAAkB,kBAChB,OACA,0BAA0B,OACxB,kBAAkB,OAAO,qBACzB,sBACK,WAAW,IAAI,kBAAkB,EAAE,QAAQ,sBAC9C;;;;;;;KAQE,kBACV,GACA,yCACE,oBAAoB,iBAAiB,GAAG;;;;;;;;iBA2D5B,aAAa,GAAG,OAAO,IAAI,kBAAkB;KAKxD,uBACH,GACA,oCACE,wBAAwB,qBACxB,IACA,eAAc,YAAY,MAAM,cAAc,UACxC,MAAM,MAAM,QAAQ,kBAAkB,MAC1C,sBACK,WAAW,IAAI,uBAAuB,EAAE,QAAQ,sBACnD;;;;;;KAOH,mBAAmB,KAAK,uBAAuB;KAExC,eAAe,KAAK;WACrB,KAAK,mBAAmB;;;;;;;;iBA6BnB,YAAY,MAAM,OAAO;;;;;;;;;;KCzJ7B;EAAmB;EAAW;EAAW;EAAW;;cAInD,sBAAsB;WACxB;WACA;WACA;WACA;EAEG,YAAA,YAAO,YAAO,YAAO;SAO1B,YAAY;;;;;;SASZ,mBAAmB,2BAA2B;;;;;;;;;;;EAwBrD;;EAaA,SAAS,OAAO,iBAAiB;;EAYjC,aAAa;;EAKb,WAAW;;;;;EAQX,gBAAgB,OAAO;;EAgBvB,aAAa;EAmBb,UAAU"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/lib/experimental/math/Pose.ts","../../../src/lib/experimental/math/Quaternion.ts","../../../src/lib/experimental/math/withMath.ts"],"mappings":";;;;;;;;;cA+Ba,gBAAgB;WAClB;WACA;EAGP,YAAA,qBACA;SAkBK,KAAK,MAAM,SAAW;SAUtB,YAAY;;;;;;;EAUnB,SAAS,OAAO,SAAW;;EAc3B,WAAW;;EAWX,eAAe;;;;;;;;;EAaf,SACE,OAAO,QACP,wBACA;;;;;;EA0BF;;EAWA,SAAS;EAKT,UAAU;;;;;;;;;;;KCtJA;EAAmB;EAAW;EAAW;EAAW;;cAInD,sBAAsB;WACxB;WACA;WACA;WACA;EAEG,YAAA,YAAO,YAAO,YAAO;SAO1B,YAAY;;;;;;SASZ,mBAAmB,2BAA2B;;;;;;;;;;;EAwBrD;;EAaA,SAAS,OAAO,iBAAiB;;EAYjC,aAAa;;EAKb,WAAW;;;;;EAoBX,gBAAgB,OAAO;;EAgBvB,aAAa;EAmBb,UAAU;;;;KC3IP,iBAAiB;KAEjB,kBAAkB,MAAM,cAAc,GAAG,8BACzC,QAAQ,gBAAgB;KAOxB;KAaA,iBACH,GACA,oCACE,wBAAwB,qBACxB,IACA,kBAAkB,kBAChB,OACA,0BAA0B,OACxB,kBAAkB,OAAO,qBACzB,sBACK,WAAW,IAAI,kBAAkB,EAAE,QAAQ,sBAC9C;;;;;;;KAQE,kBACV,GACA,yCACE,oBAAoB,iBAAiB,GAAG;;;;;;;;iBA2D5B,aAAa,GAAG,OAAO,IAAI,kBAAkB;KAKxD,uBACH,GACA,oCACE,wBAAwB,qBACxB,IACA,eAAc,YAAY,MAAM,cAAc,UACxC,MAAM,MAAM,QAAQ,kBAAkB,MAC1C,sBACK,WAAW,IAAI,uBAAuB,EAAE,QAAQ,sBACnD;;;;;;KAOH,mBAAmB,KAAK,uBAAuB;KAExC,eAAe,KAAK;WACrB,KAAK,mBAAmB;;;;;;;;iBA6BnB,SAAS,MAAM,OAAO"}
@@ -64,9 +64,12 @@ var Quaternion = class Quaternion {
64
64
  conjugate() {
65
65
  return new Quaternion(this.w, -this.x, -this.y, -this.z);
66
66
  }
67
- /** Inverse rotation. Equivalent to `conjugate()` since a `Pose`'s orientation is always a unit quaternion. */
67
+ /** Inverse: `conjugate()` scaled by `1 / squaredNorm()`, matching `Eigen::Quaternion::inverse()`. Equal to `conjugate()` for unit quaternions. */
68
68
  inverse() {
69
- return this.conjugate();
69
+ const squaredNorm = this.w * this.w + this.x * this.x + this.y * this.y + this.z * this.z;
70
+ if (squaredNorm < EPSILON) return new Quaternion(0, 0, 0, 0);
71
+ const conjugate = this.conjugate();
72
+ return new Quaternion(conjugate.w / squaredNorm, conjugate.x / squaredNorm, conjugate.y / squaredNorm, conjugate.z / squaredNorm);
70
73
  }
71
74
  /**
72
75
  * The angle (in rad, always within [0, pi]) of the rotation that takes
@@ -223,7 +226,7 @@ var Pose = class Pose {
223
226
  }
224
227
  };
225
228
  //#endregion
226
- //#region src/lib/experimental/math/augmentMath.ts
229
+ //#region src/lib/experimental/math/withMath.ts
227
230
  function isPlainObject(value) {
228
231
  return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Pose);
229
232
  }
@@ -251,7 +254,7 @@ function augmentPosesInPlace(value) {
251
254
  * Recursively walks a parsed API response/request body, upgrading any
252
255
  * `Pose`-shaped objects in place to `Pose` instances so they gain math
253
256
  * methods (`multiply`, `inverse`, ...) while remaining JSON/wire-compatible.
254
- * Used by `augmentMath` for `nova.api.*` responses; exported so it can also
257
+ * Used by `withMath` for `nova.api.*` responses; exported so it can also
255
258
  * be applied manually to e.g. websocket messages.
256
259
  */
257
260
  function augmentPoses(value) {
@@ -275,13 +278,13 @@ function wrapWithPoseAugmentation(target) {
275
278
  * methods like `multiply`/`inverse`). Returns a new proxied view - does not
276
279
  * mutate the original `nova` instance.
277
280
  */
278
- function augmentMath(nova) {
281
+ function withMath(nova) {
279
282
  return new Proxy(nova, { get(target, prop) {
280
283
  if (prop === "api") return wrapWithPoseAugmentation(target.api);
281
284
  return Reflect.get(target, prop, target);
282
285
  } });
283
286
  }
284
287
  //#endregion
285
- export { Pose, Quaternion, augmentMath, augmentPoses };
288
+ export { Pose, Quaternion, augmentPoses, withMath };
286
289
 
287
290
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/lib/experimental/math/Quaternion.ts","../../../src/lib/experimental/math/Pose.ts","../../../src/lib/experimental/math/augmentMath.ts"],"sourcesContent":["/**\n * A unit quaternion representing a 3D rotation. Mirrors the subset of\n * `Eigen::Quaternion`'s API (see wb-robotix's `QuaternionBasePlugin.h`) that\n * `Pose` composition needs: Hamilton product, conjugate/inverse, vector\n * rotation, and axis-angle rotation-vector conversion. Instances are\n * immutable - every method returns a new `Quaternion`.\n */\n\nexport type QuaternionData = { w: number; x: number; y: number; z: number }\n\nconst EPSILON = 1e-12\n\nexport class Quaternion implements QuaternionData {\n readonly w: number\n readonly x: number\n readonly y: number\n readonly z: number\n\n constructor(w = 1, x = 0, y = 0, z = 0) {\n this.w = w\n this.x = x\n this.y = y\n this.z = z\n }\n\n static identity(): Quaternion {\n return new Quaternion()\n }\n\n /**\n * Construct from an axis-angle rotation vector [rx, ry, rz] (magnitude =\n * angle in rad), matching `Eigen::Quaternion::FromRotationVector` (ported\n * from wb-robotix's `QuaternionBasePlugin.h`).\n */\n static fromRotationVector(rotationVector: number[]): Quaternion {\n const rx = rotationVector[0] ?? 0\n const ry = rotationVector[1] ?? 0\n const rz = rotationVector[2] ?? 0\n\n const angle = Math.sqrt(rx * rx + ry * ry + rz * rz)\n if (angle < EPSILON) {\n return Quaternion.identity()\n }\n const half = angle / 2\n const s = Math.sin(half) / angle\n return new Quaternion(Math.cos(half), rx * s, ry * s, rz * s)\n }\n\n /**\n * Rotation vector [rx, ry, rz] representation (magnitude = angle in rad),\n * matching `Eigen::Quaternion::toRotationVector()` (ported from\n * wb-robotix's `QuaternionBasePlugin.h`): using `atan2` rather than `acos`\n * keeps the angle canonically within [0, pi] regardless of which of the\n * two antipodal unit quaternions (this or its negation) represents the\n * rotation, rather than acos's [0, 2*pi] range - important since composed\n * poses need to match the same canonical rotation vector the robot\n * controller itself would report for a pose.\n */\n toRotationVector(): number[] {\n const vecNorm = Math.sqrt(\n this.x * this.x + this.y * this.y + this.z * this.z,\n )\n if (vecNorm < EPSILON) {\n return [0, 0, 0]\n }\n const angle = 2 * Math.atan2(vecNorm, Math.abs(this.w))\n const scale = (this.w >= 0 ? angle : -angle) / vecNorm\n return [this.x * scale, this.y * scale, this.z * scale]\n }\n\n /** Hamilton product: `a.multiply(b)` applied to a vector rotates by `b` first, then by `a`. */\n multiply(other: QuaternionData): Quaternion {\n const { w: aw, x: ax, y: ay, z: az } = this\n const { w: bw, x: bx, y: by, z: bz } = other\n return new Quaternion(\n aw * bw - ax * bx - ay * by - az * bz,\n aw * bx + ax * bw + ay * bz - az * by,\n aw * by - ax * bz + ay * bw + az * bx,\n aw * bz + ax * by - ay * bx + az * bw,\n )\n }\n\n /** Conjugate: negates the vector part. Equal to `inverse()` for unit quaternions. */\n conjugate(): Quaternion {\n return new Quaternion(this.w, -this.x, -this.y, -this.z)\n }\n\n /** Inverse rotation. Equivalent to `conjugate()` since a `Pose`'s orientation is always a unit quaternion. */\n inverse(): Quaternion {\n return this.conjugate()\n }\n\n /**\n * The angle (in rad, always within [0, pi]) of the rotation that takes\n * `other` to `this`, matching Eigen's built-in `Quaternion::angularDistance()`.\n */\n angularDistance(other: QuaternionData): number {\n const relative = this.multiply({\n w: other.w,\n x: -other.x,\n y: -other.y,\n z: -other.z,\n })\n const vecNorm = Math.sqrt(\n relative.x * relative.x +\n relative.y * relative.y +\n relative.z * relative.z,\n )\n return 2 * Math.atan2(vecNorm, Math.abs(relative.w))\n }\n\n /** Rotate a 3D vector by this quaternion. */\n rotateVector(v: number[]): number[] {\n const vx = v[0] ?? 0\n const vy = v[1] ?? 0\n const vz = v[2] ?? 0\n const { w, x, y, z } = this\n\n // t = 2 * cross(q.xyz, v)\n const tx = 2 * (y * vz - z * vy)\n const ty = 2 * (z * vx - x * vz)\n const tz = 2 * (x * vy - y * vx)\n\n // v' = v + w*t + cross(q.xyz, t)\n return [\n vx + w * tx + (y * tz - z * ty),\n vy + w * ty + (z * tx - x * tz),\n vz + w * tz + (x * ty - y * tx),\n ]\n }\n\n toJSON(): QuaternionData {\n return { w: this.w, x: this.x, y: this.y, z: this.z }\n }\n}\n","import type { Pose as PoseData } from \"@wandelbots/nova-api/v2\"\nimport { Quaternion } from \"./Quaternion.ts\"\n\nconst ZERO_VECTOR = [0, 0, 0]\n\n// Matches wb-robotix's Constants.h TOLERANCE_MILLI, the default isApprox tolerance.\nconst TOLERANCE_MILLI = 1e-3\n\nfunction addVectors(a: number[], b: number[]): number[] {\n return [\n (a[0] ?? 0) + (b[0] ?? 0),\n (a[1] ?? 0) + (b[1] ?? 0),\n (a[2] ?? 0) + (b[2] ?? 0),\n ]\n}\n\nfunction negateVector(a: number[]): number[] {\n return [-(a[0] ?? 0), -(a[1] ?? 0), -(a[2] ?? 0)]\n}\n\nfunction isFiniteVector3(v: number[]): boolean {\n return v.length === 3 && v.every((n) => Number.isFinite(n))\n}\n\n/**\n * A `Pose` (position + axis-angle orientation) with methods for composing\n * and inverting transforms. Instances are plain-data compatible with the\n * wire-format `PoseData` type (own `position`/`orientation` properties only,\n * no enumerable methods), so they can be passed directly back into API calls\n * that expect a pose.\n */\nexport class Pose implements PoseData {\n readonly position: number[]\n readonly orientation: number[]\n\n constructor(\n position: number[] = ZERO_VECTOR,\n orientation: number[] = ZERO_VECTOR,\n ) {\n if (!isFiniteVector3(position)) {\n throw new Error(\n `Pose constructor: position must be an array of 3 finite numbers, got ${JSON.stringify(position)}`,\n )\n }\n if (!isFiniteVector3(orientation)) {\n throw new Error(\n `Pose constructor: orientation must be an array of 3 finite numbers, got ${JSON.stringify(orientation)}`,\n )\n }\n // Copy defensively so mutating the caller's arrays afterward can't\n // change this (supposedly immutable) instance's state.\n this.position = [...position]\n this.orientation = [...orientation]\n }\n\n static from(pose: PoseData): Pose {\n if (pose instanceof Pose) {\n return pose\n }\n return new Pose(\n pose.position ?? ZERO_VECTOR,\n pose.orientation ?? ZERO_VECTOR,\n )\n }\n\n static identity(): Pose {\n return new Pose()\n }\n\n /**\n * Compose this pose with `other`, treating `other` as being expressed in\n * this pose's coordinate frame. Equivalent to the homogeneous transform\n * product `this * other`: applying the result to a point is the same as\n * applying `other` first, then `this`.\n */\n multiply(other: PoseData): Pose {\n const q1 = Quaternion.fromRotationVector(this.orientation)\n const q2 = Quaternion.fromRotationVector(other.orientation ?? ZERO_VECTOR)\n\n const position = addVectors(\n this.position,\n q1.rotateVector(other.position ?? ZERO_VECTOR),\n )\n const orientation = q1.multiply(q2).toRotationVector()\n\n return new Pose(position, orientation)\n }\n\n /** The inverse transform, such that `pose.multiply(pose.inverse())` is the identity pose. */\n inverse(): Pose {\n const qInverse = Quaternion.fromRotationVector(this.orientation).inverse()\n\n const position = qInverse.rotateVector(negateVector(this.position))\n // Negating an axis-angle vector gives the inverse rotation directly.\n const orientation = negateVector(this.orientation)\n\n return new Pose(position, orientation)\n }\n\n /** Apply this pose's transform to a point, returning the transformed point. */\n transformPoint(point: number[]): number[] {\n const q = Quaternion.fromRotationVector(this.orientation)\n return addVectors(this.position, q.rotateVector(point))\n }\n\n /**\n * Compare to another pose using separate position/orientation tolerances,\n * matching wb-robotix's `Pose::isApprox()`: Euclidean distance for\n * position, and quaternion angular distance for orientation - not a naive\n * per-component diff, since two rotation vectors can represent nearly\n * identical rotations while differing componentwise near a\n * canonicalization boundary (e.g. close to the +/-pi wraparound).\n */\n isApprox(\n other: PoseData,\n deltaPosition = TOLERANCE_MILLI,\n deltaOrientation = TOLERANCE_MILLI,\n ): boolean {\n const otherPosition = other.position ?? ZERO_VECTOR\n const otherOrientation = other.orientation ?? ZERO_VECTOR\n\n const positionDistance = Math.sqrt(\n this.position.reduce(\n (sum, v, i) => sum + (v - (otherPosition[i] ?? 0)) ** 2,\n 0,\n ),\n )\n const orientationDistance = Quaternion.fromRotationVector(\n this.orientation,\n ).angularDistance(Quaternion.fromRotationVector(otherOrientation))\n\n return (\n positionDistance <= deltaPosition &&\n orientationDistance <= deltaOrientation\n )\n }\n\n /**\n * Position + orientation as a 6-element [x, y, z, roll, pitch, yaw] vector\n * (Euler angles in rad, XYZ Tait-Bryan / Rx*Ry*Rz convention), matching\n * wb-robotix's `Pose::toCartesian()`.\n */\n toCartesian(): number[] {\n const { w, x, y, z } = Quaternion.fromRotationVector(this.orientation)\n\n const roll = Math.atan2(2 * (w * x - y * z), 1 - 2 * (x * x + y * y))\n const pitch = Math.asin(Math.min(1, Math.max(-1, 2 * (x * z + w * y))))\n const yaw = Math.atan2(2 * (w * z - x * y), 1 - 2 * (y * y + z * z))\n\n return [...this.position, roll, pitch, yaw]\n }\n\n /** Human-readable `[x, y, z][rx, ry, rz]` representation, matching wb-robotix's `Pose::string()`. */\n toString(precision = 6): string {\n const fmt = (v: number) => Number(v.toPrecision(precision))\n return `[${this.position.map(fmt).join(\", \")}][${this.orientation.map(fmt).join(\", \")}]`\n }\n\n toJSON(): PoseData {\n return { position: this.position, orientation: this.orientation }\n }\n}\n","import type { Pose as PoseData } from \"@wandelbots/nova-api/v2\"\nimport type { Nova } from \"../../Nova.ts\"\nimport { Pose } from \"./Pose.ts\"\n\ntype PoseKeys = keyof PoseData\n\ntype IsExactlyPoseData<T> = [Exclude<keyof T, PoseKeys>] extends [never]\n ? [Exclude<PoseKeys, keyof T>] extends [never]\n ? true\n : false\n : false\n\n// Depth cap avoids \"type instantiation is excessively deep\" on nova-api's\n// large (and possibly cyclic, e.g. compound colliders) generated type graph.\ntype MaxDepth = [\n unknown,\n unknown,\n unknown,\n unknown,\n unknown,\n unknown,\n unknown,\n unknown,\n unknown,\n unknown,\n]\n\ntype AugmentPosesCore<\n T,\n Depth extends readonly unknown[],\n> = Depth[\"length\"] extends MaxDepth[\"length\"]\n ? T\n : IsExactlyPoseData<T> extends true\n ? Pose\n : T extends readonly (infer U)[]\n ? DeepPoseAugmented<U, [...Depth, unknown]>[]\n : T extends object\n ? { [K in keyof T]: DeepPoseAugmented<T[K], [...Depth, unknown]> }\n : T\n\n/**\n * Type-level counterpart to `augmentPoses`: recursively replaces any\n * `PoseData`-shaped field with `Pose` (distributing over unions, e.g. so\n * optional `Pose | undefined` fields keep the `| undefined`), so the static\n * type matches what's actually returned at runtime.\n */\nexport type DeepPoseAugmented<\n T,\n Depth extends readonly unknown[] = [],\n> = T extends unknown ? AugmentPosesCore<T, Depth> : never\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return (\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value) &&\n !(value instanceof Pose)\n )\n}\n\nfunction isNumberTriple(value: unknown): value is number[] {\n return (\n Array.isArray(value) &&\n value.length === 3 &&\n value.every((n) => typeof n === \"number\")\n )\n}\n\n/**\n * Structural check for the wire shape of `Pose` (`{ position, orientation }`,\n * each a 3-number array, and nothing else). This API has no other type with\n * this exact shape, so it reliably identifies `Pose` values without needing\n * per-endpoint knowledge of which fields are poses.\n */\nfunction isPoseShape(value: Record<string, unknown>): boolean {\n return (\n Object.keys(value).length === 2 &&\n isNumberTriple(value.position) &&\n isNumberTriple(value.orientation)\n )\n}\n\nfunction augmentPosesInPlace(value: unknown): void {\n if (Array.isArray(value)) {\n for (const item of value) {\n augmentPosesInPlace(item)\n }\n return\n }\n\n if (isPlainObject(value)) {\n if (isPoseShape(value)) {\n Object.setPrototypeOf(value, Pose.prototype)\n } else {\n for (const key of Object.keys(value)) {\n augmentPosesInPlace(value[key])\n }\n }\n }\n}\n\n/**\n * Recursively walks a parsed API response/request body, upgrading any\n * `Pose`-shaped objects in place to `Pose` instances so they gain math\n * methods (`multiply`, `inverse`, ...) while remaining JSON/wire-compatible.\n * Used by `augmentMath` for `nova.api.*` responses; exported so it can also\n * be applied manually to e.g. websocket messages.\n */\nexport function augmentPoses<T>(value: T): DeepPoseAugmented<T> {\n augmentPosesInPlace(value)\n return value as DeepPoseAugmented<T>\n}\n\ntype WithAugmentedPosesCore<\n T,\n Depth extends readonly unknown[],\n> = Depth[\"length\"] extends MaxDepth[\"length\"]\n ? T\n : T extends (...args: infer A) => Promise<infer R>\n ? (...args: A) => Promise<DeepPoseAugmented<R>>\n : T extends object\n ? { [K in keyof T]: WithAugmentedPosesCore<T[K], [...Depth, unknown]> }\n : T\n\n/**\n * Recursively maps every method on `Nova[\"api\"]` (and its nested API groups)\n * so calls are typed as returning `DeepPoseAugmented` results, matching what\n * `augmentMath` does at runtime.\n */\ntype WithAugmentedPoses<T> = WithAugmentedPosesCore<T, []>\n\nexport type NovaWithMath = Omit<Nova, \"api\"> & {\n readonly api: WithAugmentedPoses<Nova[\"api\"]>\n}\n\nfunction wrapWithPoseAugmentation<T extends object>(target: T): T {\n return new Proxy(target, {\n get(t, prop) {\n const value = Reflect.get(t, prop, t)\n if (typeof value === \"function\") {\n return (...args: unknown[]) => {\n const result = (value as (...a: unknown[]) => unknown).apply(t, args)\n return result instanceof Promise\n ? result.then((data) => augmentPoses(data))\n : result\n }\n }\n if (value !== null && typeof value === \"object\") {\n return wrapWithPoseAugmentation(value)\n }\n return value\n },\n }) as T\n}\n\n/**\n * Wraps a `Nova` instance so every `nova.api.*` call automatically upgrades\n * `Pose`-shaped fields in its response to `Pose` instances (with math\n * methods like `multiply`/`inverse`). Returns a new proxied view - does not\n * mutate the original `nova` instance.\n */\nexport function augmentMath(nova: Nova): NovaWithMath {\n const proxy = new Proxy(nova, {\n get(target, prop) {\n if (prop === \"api\") {\n return wrapWithPoseAugmentation(target.api)\n }\n // receiver is `target`, not the proxy, so `this` inside Nova's own\n // methods stays bound to the original (un-proxied) instance.\n return Reflect.get(target, prop, target)\n },\n })\n\n return proxy as unknown as NovaWithMath\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/lib/experimental/math/Quaternion.ts","../../../src/lib/experimental/math/Pose.ts","../../../src/lib/experimental/math/withMath.ts"],"sourcesContent":["/**\n * A unit quaternion representing a 3D rotation. Mirrors the subset of\n * `Eigen::Quaternion`'s API (see wb-robotix's `QuaternionBasePlugin.h`) that\n * `Pose` composition needs: Hamilton product, conjugate/inverse, vector\n * rotation, and axis-angle rotation-vector conversion. Instances are\n * immutable - every method returns a new `Quaternion`.\n */\n\nexport type QuaternionData = { w: number; x: number; y: number; z: number }\n\nconst EPSILON = 1e-12\n\nexport class Quaternion implements QuaternionData {\n readonly w: number\n readonly x: number\n readonly y: number\n readonly z: number\n\n constructor(w = 1, x = 0, y = 0, z = 0) {\n this.w = w\n this.x = x\n this.y = y\n this.z = z\n }\n\n static identity(): Quaternion {\n return new Quaternion()\n }\n\n /**\n * Construct from an axis-angle rotation vector [rx, ry, rz] (magnitude =\n * angle in rad), matching `Eigen::Quaternion::FromRotationVector` (ported\n * from wb-robotix's `QuaternionBasePlugin.h`).\n */\n static fromRotationVector(rotationVector: number[]): Quaternion {\n const rx = rotationVector[0] ?? 0\n const ry = rotationVector[1] ?? 0\n const rz = rotationVector[2] ?? 0\n\n const angle = Math.sqrt(rx * rx + ry * ry + rz * rz)\n if (angle < EPSILON) {\n return Quaternion.identity()\n }\n const half = angle / 2\n const s = Math.sin(half) / angle\n return new Quaternion(Math.cos(half), rx * s, ry * s, rz * s)\n }\n\n /**\n * Rotation vector [rx, ry, rz] representation (magnitude = angle in rad),\n * matching `Eigen::Quaternion::toRotationVector()` (ported from\n * wb-robotix's `QuaternionBasePlugin.h`): using `atan2` rather than `acos`\n * keeps the angle canonically within [0, pi] regardless of which of the\n * two antipodal unit quaternions (this or its negation) represents the\n * rotation, rather than acos's [0, 2*pi] range - important since composed\n * poses need to match the same canonical rotation vector the robot\n * controller itself would report for a pose.\n */\n toRotationVector(): number[] {\n const vecNorm = Math.sqrt(\n this.x * this.x + this.y * this.y + this.z * this.z,\n )\n if (vecNorm < EPSILON) {\n return [0, 0, 0]\n }\n const angle = 2 * Math.atan2(vecNorm, Math.abs(this.w))\n const scale = (this.w >= 0 ? angle : -angle) / vecNorm\n return [this.x * scale, this.y * scale, this.z * scale]\n }\n\n /** Hamilton product: `a.multiply(b)` applied to a vector rotates by `b` first, then by `a`. */\n multiply(other: QuaternionData): Quaternion {\n const { w: aw, x: ax, y: ay, z: az } = this\n const { w: bw, x: bx, y: by, z: bz } = other\n return new Quaternion(\n aw * bw - ax * bx - ay * by - az * bz,\n aw * bx + ax * bw + ay * bz - az * by,\n aw * by - ax * bz + ay * bw + az * bx,\n aw * bz + ax * by - ay * bx + az * bw,\n )\n }\n\n /** Conjugate: negates the vector part. Equal to `inverse()` for unit quaternions. */\n conjugate(): Quaternion {\n return new Quaternion(this.w, -this.x, -this.y, -this.z)\n }\n\n /** Inverse: `conjugate()` scaled by `1 / squaredNorm()`, matching `Eigen::Quaternion::inverse()`. Equal to `conjugate()` for unit quaternions. */\n inverse(): Quaternion {\n const squaredNorm =\n this.w * this.w + this.x * this.x + this.y * this.y + this.z * this.z\n // Degenerate (zero) quaternion has no inverse - flag it the same way Eigen does.\n if (squaredNorm < EPSILON) {\n return new Quaternion(0, 0, 0, 0)\n }\n const conjugate = this.conjugate()\n return new Quaternion(\n conjugate.w / squaredNorm,\n conjugate.x / squaredNorm,\n conjugate.y / squaredNorm,\n conjugate.z / squaredNorm,\n )\n }\n\n /**\n * The angle (in rad, always within [0, pi]) of the rotation that takes\n * `other` to `this`, matching Eigen's built-in `Quaternion::angularDistance()`.\n */\n angularDistance(other: QuaternionData): number {\n const relative = this.multiply({\n w: other.w,\n x: -other.x,\n y: -other.y,\n z: -other.z,\n })\n const vecNorm = Math.sqrt(\n relative.x * relative.x +\n relative.y * relative.y +\n relative.z * relative.z,\n )\n return 2 * Math.atan2(vecNorm, Math.abs(relative.w))\n }\n\n /** Rotate a 3D vector by this quaternion. */\n rotateVector(v: number[]): number[] {\n const vx = v[0] ?? 0\n const vy = v[1] ?? 0\n const vz = v[2] ?? 0\n const { w, x, y, z } = this\n\n // t = 2 * cross(q.xyz, v)\n const tx = 2 * (y * vz - z * vy)\n const ty = 2 * (z * vx - x * vz)\n const tz = 2 * (x * vy - y * vx)\n\n // v' = v + w*t + cross(q.xyz, t)\n return [\n vx + w * tx + (y * tz - z * ty),\n vy + w * ty + (z * tx - x * tz),\n vz + w * tz + (x * ty - y * tx),\n ]\n }\n\n toJSON(): QuaternionData {\n return { w: this.w, x: this.x, y: this.y, z: this.z }\n }\n}\n","import type { Pose as PoseData } from \"@wandelbots/nova-api/v2\"\nimport { Quaternion } from \"./Quaternion.ts\"\n\nconst ZERO_VECTOR = [0, 0, 0]\n\n// Matches wb-robotix's Constants.h TOLERANCE_MILLI, the default isApprox tolerance.\nconst TOLERANCE_MILLI = 1e-3\n\nfunction addVectors(a: number[], b: number[]): number[] {\n return [\n (a[0] ?? 0) + (b[0] ?? 0),\n (a[1] ?? 0) + (b[1] ?? 0),\n (a[2] ?? 0) + (b[2] ?? 0),\n ]\n}\n\nfunction negateVector(a: number[]): number[] {\n return [-(a[0] ?? 0), -(a[1] ?? 0), -(a[2] ?? 0)]\n}\n\nfunction isFiniteVector3(v: number[]): boolean {\n return v.length === 3 && v.every((n) => Number.isFinite(n))\n}\n\n/**\n * A `Pose` (position + axis-angle orientation) with methods for composing\n * and inverting transforms. Instances are plain-data compatible with the\n * wire-format `PoseData` type (own `position`/`orientation` properties only,\n * no enumerable methods), so they can be passed directly back into API calls\n * that expect a pose.\n */\nexport class Pose implements PoseData {\n readonly position: number[]\n readonly orientation: number[]\n\n constructor(\n position: number[] = ZERO_VECTOR,\n orientation: number[] = ZERO_VECTOR,\n ) {\n if (!isFiniteVector3(position)) {\n throw new Error(\n `Pose constructor: position must be an array of 3 finite numbers, got ${JSON.stringify(position)}`,\n )\n }\n if (!isFiniteVector3(orientation)) {\n throw new Error(\n `Pose constructor: orientation must be an array of 3 finite numbers, got ${JSON.stringify(orientation)}`,\n )\n }\n // Copy defensively so mutating the caller's arrays afterward can't\n // change this (supposedly immutable) instance's state.\n this.position = [...position]\n this.orientation = [...orientation]\n }\n\n static from(pose: PoseData): Pose {\n if (pose instanceof Pose) {\n return pose\n }\n return new Pose(\n pose.position ?? ZERO_VECTOR,\n pose.orientation ?? ZERO_VECTOR,\n )\n }\n\n static identity(): Pose {\n return new Pose()\n }\n\n /**\n * Compose this pose with `other`, treating `other` as being expressed in\n * this pose's coordinate frame. Equivalent to the homogeneous transform\n * product `this * other`: applying the result to a point is the same as\n * applying `other` first, then `this`.\n */\n multiply(other: PoseData): Pose {\n const q1 = Quaternion.fromRotationVector(this.orientation)\n const q2 = Quaternion.fromRotationVector(other.orientation ?? ZERO_VECTOR)\n\n const position = addVectors(\n this.position,\n q1.rotateVector(other.position ?? ZERO_VECTOR),\n )\n const orientation = q1.multiply(q2).toRotationVector()\n\n return new Pose(position, orientation)\n }\n\n /** The inverse transform, such that `pose.multiply(pose.inverse())` is the identity pose. */\n inverse(): Pose {\n const qInverse = Quaternion.fromRotationVector(this.orientation).inverse()\n\n const position = qInverse.rotateVector(negateVector(this.position))\n // Negating an axis-angle vector gives the inverse rotation directly.\n const orientation = negateVector(this.orientation)\n\n return new Pose(position, orientation)\n }\n\n /** Apply this pose's transform to a point, returning the transformed point. */\n transformPoint(point: number[]): number[] {\n const q = Quaternion.fromRotationVector(this.orientation)\n return addVectors(this.position, q.rotateVector(point))\n }\n\n /**\n * Compare to another pose using separate position/orientation tolerances,\n * matching wb-robotix's `Pose::isApprox()`: Euclidean distance for\n * position, and quaternion angular distance for orientation - not a naive\n * per-component diff, since two rotation vectors can represent nearly\n * identical rotations while differing componentwise near a\n * canonicalization boundary (e.g. close to the +/-pi wraparound).\n */\n isApprox(\n other: PoseData,\n deltaPosition = TOLERANCE_MILLI,\n deltaOrientation = TOLERANCE_MILLI,\n ): boolean {\n const otherPosition = other.position ?? ZERO_VECTOR\n const otherOrientation = other.orientation ?? ZERO_VECTOR\n\n const positionDistance = Math.sqrt(\n this.position.reduce(\n (sum, v, i) => sum + (v - (otherPosition[i] ?? 0)) ** 2,\n 0,\n ),\n )\n const orientationDistance = Quaternion.fromRotationVector(\n this.orientation,\n ).angularDistance(Quaternion.fromRotationVector(otherOrientation))\n\n return (\n positionDistance <= deltaPosition &&\n orientationDistance <= deltaOrientation\n )\n }\n\n /**\n * Position + orientation as a 6-element [x, y, z, roll, pitch, yaw] vector\n * (Euler angles in rad, XYZ Tait-Bryan / Rx*Ry*Rz convention), matching\n * wb-robotix's `Pose::toCartesian()`.\n */\n toCartesian(): number[] {\n const { w, x, y, z } = Quaternion.fromRotationVector(this.orientation)\n\n const roll = Math.atan2(2 * (w * x - y * z), 1 - 2 * (x * x + y * y))\n const pitch = Math.asin(Math.min(1, Math.max(-1, 2 * (x * z + w * y))))\n const yaw = Math.atan2(2 * (w * z - x * y), 1 - 2 * (y * y + z * z))\n\n return [...this.position, roll, pitch, yaw]\n }\n\n /** Human-readable `[x, y, z][rx, ry, rz]` representation, matching wb-robotix's `Pose::string()`. */\n toString(precision = 6): string {\n const fmt = (v: number) => Number(v.toPrecision(precision))\n return `[${this.position.map(fmt).join(\", \")}][${this.orientation.map(fmt).join(\", \")}]`\n }\n\n toJSON(): PoseData {\n return { position: this.position, orientation: this.orientation }\n }\n}\n","import type { Pose as PoseData } from \"@wandelbots/nova-api/v2\"\nimport type { Nova } from \"../../Nova.ts\"\nimport { Pose } from \"./Pose.ts\"\n\ntype PoseKeys = keyof PoseData\n\ntype IsExactlyPoseData<T> = [Exclude<keyof T, PoseKeys>] extends [never]\n ? [Exclude<PoseKeys, keyof T>] extends [never]\n ? true\n : false\n : false\n\n// Depth cap avoids \"type instantiation is excessively deep\" on nova-api's\n// large (and possibly cyclic, e.g. compound colliders) generated type graph.\ntype MaxDepth = [\n unknown,\n unknown,\n unknown,\n unknown,\n unknown,\n unknown,\n unknown,\n unknown,\n unknown,\n unknown,\n]\n\ntype AugmentPosesCore<\n T,\n Depth extends readonly unknown[],\n> = Depth[\"length\"] extends MaxDepth[\"length\"]\n ? T\n : IsExactlyPoseData<T> extends true\n ? Pose\n : T extends readonly (infer U)[]\n ? DeepPoseAugmented<U, [...Depth, unknown]>[]\n : T extends object\n ? { [K in keyof T]: DeepPoseAugmented<T[K], [...Depth, unknown]> }\n : T\n\n/**\n * Type-level counterpart to `augmentPoses`: recursively replaces any\n * `PoseData`-shaped field with `Pose` (distributing over unions, e.g. so\n * optional `Pose | undefined` fields keep the `| undefined`), so the static\n * type matches what's actually returned at runtime.\n */\nexport type DeepPoseAugmented<\n T,\n Depth extends readonly unknown[] = [],\n> = T extends unknown ? AugmentPosesCore<T, Depth> : never\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return (\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value) &&\n !(value instanceof Pose)\n )\n}\n\nfunction isNumberTriple(value: unknown): value is number[] {\n return (\n Array.isArray(value) &&\n value.length === 3 &&\n value.every((n) => typeof n === \"number\")\n )\n}\n\n/**\n * Structural check for the wire shape of `Pose` (`{ position, orientation }`,\n * each a 3-number array, and nothing else). This API has no other type with\n * this exact shape, so it reliably identifies `Pose` values without needing\n * per-endpoint knowledge of which fields are poses.\n */\nfunction isPoseShape(value: Record<string, unknown>): boolean {\n return (\n Object.keys(value).length === 2 &&\n isNumberTriple(value.position) &&\n isNumberTriple(value.orientation)\n )\n}\n\nfunction augmentPosesInPlace(value: unknown): void {\n if (Array.isArray(value)) {\n for (const item of value) {\n augmentPosesInPlace(item)\n }\n return\n }\n\n if (isPlainObject(value)) {\n if (isPoseShape(value)) {\n Object.setPrototypeOf(value, Pose.prototype)\n } else {\n for (const key of Object.keys(value)) {\n augmentPosesInPlace(value[key])\n }\n }\n }\n}\n\n/**\n * Recursively walks a parsed API response/request body, upgrading any\n * `Pose`-shaped objects in place to `Pose` instances so they gain math\n * methods (`multiply`, `inverse`, ...) while remaining JSON/wire-compatible.\n * Used by `withMath` for `nova.api.*` responses; exported so it can also\n * be applied manually to e.g. websocket messages.\n */\nexport function augmentPoses<T>(value: T): DeepPoseAugmented<T> {\n augmentPosesInPlace(value)\n return value as DeepPoseAugmented<T>\n}\n\ntype WithAugmentedPosesCore<\n T,\n Depth extends readonly unknown[],\n> = Depth[\"length\"] extends MaxDepth[\"length\"]\n ? T\n : T extends (...args: infer A) => Promise<infer R>\n ? (...args: A) => Promise<DeepPoseAugmented<R>>\n : T extends object\n ? { [K in keyof T]: WithAugmentedPosesCore<T[K], [...Depth, unknown]> }\n : T\n\n/**\n * Recursively maps every method on `Nova[\"api\"]` (and its nested API groups)\n * so calls are typed as returning `DeepPoseAugmented` results, matching what\n * `withMath` does at runtime.\n */\ntype WithAugmentedPoses<T> = WithAugmentedPosesCore<T, []>\n\nexport type NovaWithMath = Omit<Nova, \"api\"> & {\n readonly api: WithAugmentedPoses<Nova[\"api\"]>\n}\n\nfunction wrapWithPoseAugmentation<T extends object>(target: T): T {\n return new Proxy(target, {\n get(t, prop) {\n const value = Reflect.get(t, prop, t)\n if (typeof value === \"function\") {\n return (...args: unknown[]) => {\n const result = (value as (...a: unknown[]) => unknown).apply(t, args)\n return result instanceof Promise\n ? result.then((data) => augmentPoses(data))\n : result\n }\n }\n if (value !== null && typeof value === \"object\") {\n return wrapWithPoseAugmentation(value)\n }\n return value\n },\n }) as T\n}\n\n/**\n * Wraps a `Nova` instance so every `nova.api.*` call automatically upgrades\n * `Pose`-shaped fields in its response to `Pose` instances (with math\n * methods like `multiply`/`inverse`). Returns a new proxied view - does not\n * mutate the original `nova` instance.\n */\nexport function withMath(nova: Nova): NovaWithMath {\n const proxy = new Proxy(nova, {\n get(target, prop) {\n if (prop === \"api\") {\n return wrapWithPoseAugmentation(target.api)\n }\n // receiver is `target`, not the proxy, so `this` inside Nova's own\n // methods stays bound to the original (un-proxied) instance.\n return Reflect.get(target, prop, target)\n },\n })\n\n return proxy as unknown as NovaWithMath\n}\n"],"mappings":"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"}
@@ -3173,7 +3173,17 @@ type NatsPublishSubject = keyof NatsPublishPayloads;
3173
3173
  //#endregion
3174
3174
  //#region src/lib/experimental/nats/NovaNatsClient.d.ts
3175
3175
  type NovaNatsClientConfig = ConnectionOptions;
3176
- type NatsMessageHandler<K extends NatsSubscribeSubject> = (payload: NatsSubscribePayloads[K], msg: Msg) => void | Promise<void>;
3176
+ /**
3177
+ * A received message, annotated with the values of the subject template's
3178
+ * `{param}` placeholders as extracted from the message's concrete subject.
3179
+ * With a wildcard subscription (e.g. `{ cell: "*" }`), `subjectParams` is how
3180
+ * a handler knows which entity a message belongs to. Typed per subject via
3181
+ * the generated `NatsOperationParams`.
3182
+ */
3183
+ type NatsSubscribeMsg<K extends NatsSubscribeSubject> = Msg & {
3184
+ subjectParams: NatsOperationParams[K];
3185
+ };
3186
+ type NatsMessageHandler<K extends NatsSubscribeSubject> = (payload: NatsSubscribePayloads[K], msg: NatsSubscribeMsg<K>) => void | Promise<void>;
3177
3187
  type SubscribeArgs<K extends NatsSubscribeSubject> = keyof NatsOperationParams[K] extends never ? [handler: NatsMessageHandler<K>] : [params: NatsOperationParams[K], handler: NatsMessageHandler<K>];
3178
3188
  /**
3179
3189
  * Typed NATS client for the Wandelbots NOVA messaging API, generated from
@@ -3205,6 +3215,13 @@ declare class NovaNatsClient {
3205
3215
  * logged per-message, so one bad message doesn't stop later messages on
3206
3216
  * the same subscription from being handled.
3207
3217
  *
3218
+ * Each message is annotated with `msg.subjectParams` — the template's
3219
+ * `{param}` values extracted from the message's concrete subject — so a
3220
+ * wildcard subscriber knows which entity a message belongs to:
3221
+ *
3222
+ * nats.subscribe("nova.v2.cells.{cell}.status", { cell: "*" },
3223
+ * (services, msg) => console.log(msg.subjectParams.cell, services))
3224
+ *
3208
3225
  * Returns a function that unsubscribes when called.
3209
3226
  */
3210
3227
  subscribe<K extends NatsSubscribeSubject>(subject: K, ...args: SubscribeArgs<K>): Promise<() => void>;
@@ -3230,5 +3247,5 @@ declare class NovaNatsClient {
3230
3247
  publish<K extends NatsPublishSubject>(subject: K, params: NatsOperationParams[K], payload: NatsPublishPayloads[K]): Promise<void>;
3231
3248
  }
3232
3249
  //#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 };
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 };
3234
3251
  //# 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;;;;;;;;;;;;;;;;KCIvB;;;;;;;;KAQA;;;;;KAKA;;;;;;;;;KAeA;;;;;;;;;;;KAWA,yCACR,uBACA;;;;;;;;;KASQ;EACV;EACA;;;;;;;;KAQU;;;;;;;KAOA;;;;KASA;;;;;KAUA;;;;;KASA;;;;;KAKA;;;;;KAmBA,oBAAoB;;;;;;;;;;;KAWpB;;;;;;;;;;;;;;KAcA;;;;;;;;;;;;;;;;;;;KAmBA,YAAY;;;;;;;KAOZ,aAAa;;;;;;;;KAQb;;;;;KASA,UAAU,iBAAiB,iBAAiB;;;;;;;KAO5C,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA4CvB;;;;;;;;;;;;;;KAmBA;;;;;;;;;;;;KAsBA;;;;;;;;;;KA0BA;;;;;;;;;;;;KAYA;;;;;;KAMA;;;;;;KAMA;;;;;;KAMA;;;;;;;KAOA,oBAAoB;;;;;;;;KAQpB;;;;;;;;KAQA;;;;;KAKA;;;;;KAKA,2BAA2B;EACrC;EACA;;;;EAIA;KACG;;;;;EAKH;KACG;;;;;EAKH;;;;IAIE;;;;IAIA;;;;;;;KAOQ,6BAA6B;EACvC;EACA;;;;EAIA;KACG;;;;;EAKH;KACG;;;;;EAKH;;;;IAIE;;;;IAIA;;;;;;;;;KASQ;;;;;KAKA;EACV;EACA;;;;EAIA;KACG;;;;;EAKH;KACG;;EAEH,MAAM;IACJ;;;;;KAKQ;EACV;EACA;;;;EAIA;KACG;;;;;EAKH;KACG;;EAEH,MAAM;IACJ;;;;;KAKQ;EACV;EACA;;;;EAIA;KACG;;;;;EAKH;KACG;;EAEH,MAAM;IACJ;;;;;KAKQ;EACV;EACA;;;;EAIA;KACG;;;;;EAKH;KACG;;EAEH,MAAM;IACJ;;;;;KAKQ;EACV;EACA;;;;EAIA;KACG;;;;;EAKH;KACG;;EAEH,MAAM;IACJ;;;;;KAKQ;EACV;EACA;;;;EAIA;KACG;;;;;EAKH;KACG;;EAEH,MAAM;IACJ;;;;;KAKQ;EACV;EACA;;;;EAIA;KACG;;;;;EAKH;KACG;;EAEH,MAAM;IACJ;;;;;KAKQ;EACV;EACA;;;;EAIA;KACG;;;;;EAKH;KACG;;EAEH,MAAM;IACJ;;;;;KAKQ;EACV;EACA;;;;EAIA;KACG;;;;;EAKH;KACG;;EAEH,MAAM;IACJ;UAEa;EACf;EACA;;;;;EAKA;;;;EAIA;IACE;IACA;;;;;;;;;;;;;;UAca;;;;;;;EAOf;;;;;;;EAOA;;;;;;;;UAQe;EACf;EACA;EACA,oBAAoB;;;;;;;;;;;UAWL;EACf;EACA;EACA;;;;EAIA;IACE;IACA;;;;;;EAMF;;;;;;;;UAQe;EACf;EACA;;;;;;UAMe;;;;EAIf;EACA,oBAAoB;;;;;;;;;EASpB;;;;;;UAMe;;;;EAIf;;;;;;EAMA;;;;;EAKA;;;;;;;;;EASA;;;;;;;;;;UAUe;EACf;EACA,cAAc;;;;;;EAMd;;;;;;;EAOA;;;;;;;;;EASA;;;;;;;;;;;;;;;;;;EAkBA,sBAEK,2CAEC,wCACA,2CAGA,wCACA,wCACA,2CAGA,wCACA,wCACA,wCACA,2CAGA,wCACA,wCACA,wCACA,wCACA,2CAGA,wCACA,wCACA,wCACA,wCACA,wCACA,2CAGA,wCACA,wCACA,wCACA,wCACA,wCACA,wCACA,2CAGA,wCACA,wCACA,wCACA,wCACA,wCACA,wCACA,wCACA,2CAGA,wCACA,wCACA,wCACA,wCACA,wCACA,wCACA,wCACA,wCACA,2CAGA,wCACA,wCACA,wCACA,wCACA,wCACA,wCACA,wCACA,wCACA,wCACA;;;;;;;;UASS;EACf;EACA;;;;;;;;UAQe;;;;;;EAMf;EACA,eACI,gBACA,kBACA,iBACA,4BACA,oBACA;;;;;;;;;UASW;EACf;EACA;EACA;;;;;;;;;UASe;;;;EAIf;EACA,cAAc;;;;EAId;IACE;;;;;;;;;;;;UAYa;EACf;;;;;;;EAOA;;;;;;;;UAQe;;;;EAIf;;;;EAIA;;;;;;;;;UASe;;;;;;;;;;;EAWf;;;;EAIA;EACA,iBAAiB;;;;EAIjB;EACA,cAAc;EACd,UAAU;EACV,YAAY;;;;;;;;;;;;;EAaZ;;;;;;;;;EASA;;;;;;;;;;;UAWe;;;;;;EAMf;;;;;;EAMA;EACA,cAAc;EACd,cAAc;EACd,OAAO;GACN;;;;;;UAMc;;;;EAIf;;;;EAIA;;;;EAIA;EACA,OAAO;;;;EAIP;;;;EAIA;;;;EAIA;;;;EAIA;;;;;;UAMe;EACf;EACA,OAAO;EACP;IACE,UAAU;IACV,MAAM;IACN;;;;;;;;;;UAUa;;;;EAIf;;;;EAIA;;;;EAIA;;;;EAIA;;;;EAIA;;;;EAIA;KACG;;;;;EAKH;;;;EAIA;;;;;;;;UAQe;EACf;;;;EAIA;;;;;;;;;;;;;UAae;EACf;;;;EAIA;;;;EAIA;;;;EAIA;;;;EAIA;;;;;;;;UAQe;EACf;;;;EAIA;;;;EAIA;;;;;;;;UAQe;EACf;;;;;;;;;;;;UAYe;EACf;;;;EAIA;;;;EAIA;;;;;;;;;;UAUe;EACf;;;;EAIA;;;;EAIA;;;;;;;;;;;UAWe;EACf;;;;EAIA;;;;EAIA;;;;EAIA;;;;;;;;UAQe;EACf;;;;EAIA,UAAU;;;;;;;;;;;UAWK;EACf,WAAW;EACX,cAAc;;;;;;;;;;;;;;;;;UAiBC;EACf,OACI,SACA,MACA,YACA,QACA,WACA,UACA,qBACA;EACJ,OAAO;;;;EAIP;;;;;;;;UAQe;GACd,YAAY;;;;;;UAME;GACd,YAAY;;;;;;;;;;;;;UAaE;GACd,YAAY;;;;;;UAME;EACf,YAAY;EACZ,aAAa;EACb,OAAO;;;;;;;;;;;EAWP;;;;;;UAMe;GACd,YAAY;;;;;;UAME;GACd,YAAY;;;;;;UAME;EACf,OAAO;;;;;;EAMP;;;;;;;;UAQe;;;;EAIf;;;;;EAKA;EACA;;;;;;UAMe;;;;EAIf;;;;;;;;;EASA;EACA;;;;;;UAMe;;;;EAIf;;;;;EAKA;EACA;;;;;;UAMe;;;;EAIf;;;;;;;EAOA;;;;;;;;UAQe;EACf,WAAW;;;;EAIX;;;;;;EAMA;;;;;;;;;;UAUe;;;;EAIf;;;;;;;;;UASe;EACf;;;;;;;;;UASe;EACf;;;;;;;;;UASe;EACf;;;;;;;;;UASe;EACf;EACA;;;;;;;;;UASe;EACf;EACA;;;;;;;;;UASe;EACf;EACA;;;;;;;;;;;;;;;;;UAiBe;EACf,OACI,iBACA,sBACA,oBACA,8BACA,6BACA;;;;;;;;;;EAUJ;EACA;;;;;;;;;UASe;EACf;;;;EAIA;;;;;;;;;UASe;EACf;;;;;;;;;UASe;EACf;;;;;;;;;UASe;EACf;;;;;;;;;UASe;EACf;;;;;;;;;;;;;;;;UAgBe;;;;;EAKf;;;;;EAKA;EACA,OACI,oBACA,yBACA,kBACA,sBACA;;;;;EAKJ;;;;;;;;;;;UAWe;;;;;;EAMf;EACA,UAAU,iBAAiB;;;;;;;;UAQZ;;;;EAIf;;;;;;EAMA;;;;EAIA;;;;EAIA;EACA,gBAAgB;EAChB,qBAAqB;EACrB,eAAe;EACf,gBAAgB;EAChB,cAAc;;;;;;EAMd;EACA,WAAW;;;;;;;EAOX;;;;;;EAMA;;;;;;EAMA;EACA,UAAU;;;;;;;;EAQV;;;;;;UAMe;;;;EAIf;;;;;;;;UAQe;EACf,WAAW;EACX,cAAc;;;;;;;;UAQC;EACf,WAAW;EACX,cAAc;;;;;;;;UAQC;;;;;;EAMf;EACA,UAAU,iBAAiB;;;;;;;;UAQZ;;;;EAIf;;;;;;;;;;;EAgBA;;;;EAIA;;;;;;EAMA;;;;EAIA;EACA,gBAAgB;EAChB,cAAc;;;;;;;EAOd;EACA,eAAe;;;;;;UAMA;;;;EAIf;EACA,MAAM;;;;;;;;UAQS;EACf;EACA;;;;;;UAMe;EACf,WAAW;EACX;EACA;;;;;;;EAOA;EACA;;;;;;UAMe;EACf;EACA;;;;;;;EAOA;EACA;EACA;;;;;;;EAOA;;;;;;UAMe;EACf,SAAS;EACT,MAAM;EACN,QAAQ;EACR,SAAS;EACT,+BAA+B;;;;;;UAMhB;EACf,cAAc;EACd,gBAAgB;EAChB,mBAAmB;EACnB,mBAAmB;;;;EAInB;;;;;;UAMe;EACf;;;;EAIA;EACA,iBAAiB;EACjB,oBAAoB;;;;;;;;UAQL;;;;EAIf;;;;EAIA;;;;EAIA;;;;EAIA;;;;EAIA;EACA,OAAO;;;;;;;;;;;;;UAaQ;EACf,oBAAoB;EACpB,WAAW;EACX,OAAO;EACP,eAAe;;;;;;EAMf,wBAAwB;EACxB,wBAAwB;EACxB,kBAAkB;EAClB,WAAW;;;;EAIX;;;;EAIA,gBAAgB;EAChB,yBAAyB;EACzB,gBAAgB;;;;;EAKhB;;;;;;;;EAQA;;;;;;;;UAQe;EACf,WAAW;EACX,cAAc;;;;;;;UAOC;GACd,YAAY;;;;;;;UAOE;GACd,YAAY;;;;;;;UAOE;GACd,YAAY;;;;;;;UAOE;GACd,YAAY;;;;;;;;UAQE;EACf,WAAW;EACX,cAAc;;;;;;;;UAQC;EACf,WAAW;EACX,cAAc;;;;;;;;;;UAUC;;;;EAIf;;;;EAIA;;;;EAIA;;;;EAIA;;;;EAIA;;;;EAIA;;;;EAIA;;;;EAIA;;;;EAIA;KACG;;;;;;;UAOY;;;;;EAKf;;;;;EAKA;;;;;EAKA;;;;;EAKA;;;;;EAKA;;;;;EAKA;;;;;;;;;;UAUe;;;;EAIf;;;;EAIA;;;;EAIA;;;;EAIA;;;;EAIA;;;;EAIA;EACA,MAAM;;;;;;;;UAQS;;;;EAIf;EACA,iBAAiB;;;;;;;;UAQF;;;;EAIf;EACA,iBAAiB;;;;;;;;UAQF;EACf,cAAc;;;;EAId;EACA,iBAAiB;;;;;;UAMF;EACf;EACA;;;;;UC/uEe;;;;;;;;;;EAUf;;;;IAIE;;;;;;;;;;;EAWF;;;;IAIE;;;;;IAKA;;;;;;;;;;EAUF;;;;IAIE;;;;;;;;;EASF;;;;IAIE;;;;IAIA;;;;;;;;;;;EAWF;;;;IAIE;;;;;;;;;EASF;;;;IAIE;;;;;;;;;;;EAWF,yBAAyB;;;;;;;;;;EAUzB;;;;IAIE;;;;IAIA;;;;;;;;;;;EAWF;;;;IAIE;;;;;;;;;EASF;;;;IAIE;;;;;;;;;;EAUF;;;;IAIE;;;;;;;;;EASF;;;;IAIE;;;;IAIA;;;;;;;;;EASF;;;;IAIE;;;;IAIA;;;;;;;;;EASF;;;;IAIE;;;;IAIA;;;;;;;;;EASF;;;;IAIE;;;;IAIA;;;;IAIA;;;;;;;;;;;;;;;EAeF,wCAAwC;;;;;;;;;;;;;;;EAexC,0CAA0C;;;;;;;;;;;;;;EAc1C,gDAAgD;;;;;;;;;;EAUhD;;;;IAIE;;;;;;;;;;;EAWF;;;;IAIE;;;;;;;;;;;EAWF;;;;IAIE;;;;;;;;;;;EAWF;;;;IAIE;;;;IAIA;;;;;;;;;;;EAWF;;;;IAIE;;;;IAIA;;;;;;;;;;;EAWF;;;;IAIE;;;;IAIA;;;;;;;;;;;EAWF;;;;IAIE;;;;IAIA;;;;;;;;;;;EAWF;;;;IAIE;;;;IAIA;;;;;;;;;;;EAWF;;;;IAIE;;;;IAIA;;;;UAKa;;;;;;;;;;EAUf,wBAAwB;;;;;;;;;;EAUxB,mCAAmC;;;;;;;;;EASnC,iCAAiC;;;;;;;;EAQjC,iDAAiD;;;;;;;;;;EAUjD,+BAA+B;;;;;;;;EAQ/B,8BAA8B;;;;;;;;;;EAU9B,yBAAyB;;;;;;;;;;EAUzB,iDAAiD;;;;;;;;;;EAUjD,uCAAuC;;;;;;;;EAQvC,oCAAoC;;;;;;;;EAQpC,qDAAqD;;;;;;;;EAQrD,uDAAuD;;;;;;;;EAQvD,0FAA0F;;;;;;;;;;;;;;EAc1F,wCAAwC;;;;;;;;;;;;;;;EAexC,0CAA0C;;;;;;;;;;;;;;EAc1C,gDAAgD;;;;;;;;;;EAUhD,uCAAuC;;;;;;;;;;EAUvC,uCAAuC;;;;;;;;;;EAUvC,uCAAuC;;;;;;;;;;EAUvC,kDAAkD;;;;;;;;;;EAUlD,kDAAkD;;;;;;;;;;EAUlD,kDAAkD;;;;;;;;;;EAUlD,gEAAgE;;;;;;;;;;EAUhE,gEAAgE;;;;;;;;;;EAUhE,gEAAgE;;KAGtD,6BAA6B;;UAGxB;;;;;;;;;EASf,wCAAwC;;;;;;;;EAQxC,4DAA4D;;;UAI7C;;;;;;;;;EASf,wCAAwC;;;;;;;;EAQxC,4DAA4D;;KAGlD,2BAA2B;;UAGtB;;;;;;;;;;EAUf,wBAAwB;;;;;;;;;;EAUxB,mCAAmC;;;;;;;;;EASnC,iCAAiC;;;;;;;;EAQjC,iDAAiD;;;;;;;;;;EAUjD,+BAA+B;;;;;;;;EAQ/B,8BAA8B;;;;;;;;;;EAU9B,yBAAyB;;;;;;;;;;EAUzB,iDAAiD;;;;;;;;;;EAUjD,uCAAuC;;;;;;;;EAQvC,oCAAoC;;;;;;;;;EASpC,wCAAwC;;;;;;;;EAQxC,4DAA4D;;;;;;;;EAQ5D,qDAAqD;;;;;;;;EAQrD,uDAAuD;;;;;;;;EAQvD,0FAA0F;;;;;;;;;;;;;;EAc1F,wCAAwC;;;;;;;;;;;;;;;EAexC,0CAA0C;;;;;;;;;;;;;;EAc1C,gDAAgD;;;;;;;;;;EAUhD,uCAAuC;;;;;;;;;;EAUvC,uCAAuC;;;;;;;;;;EAUvC,uCAAuC;;;;;;;;;;EAUvC,kDAAkD;;;;;;;;;;EAUlD,kDAAkD;;;;;;;;;;EAUlD,kDAAkD;;;;;;;;;;EAUlD,gEAAgE;;;;;;;;;;EAUhE,gEAAgE;;;;;;;;;;EAUhE,gEAAgE;;KAGtD,2BAA2B;;;KCrgC3B,uBAAuB;KAE9B,mBAAmB,UAAU,yBAChC,SAAS,sBAAsB,IAC/B,KAAK,eACK;KAEP,cAAc,UAAU,8BACrB,oBAAoB,oBACrB,SAAS,mBAAmB,OAC5B,QAAQ,oBAAoB,IAAI,SAAS,mBAAmB;;;;;;;cAQtD;WACF,QAAQ;UACT;EAEI,YAAA,MAAM,MAAM,SAAQ;;;;;;EAgBhC,WAAW,QAAQ;;EAYb,SAAS;;;;;;;;;;;;;;;EA0BT,UAAU,UAAU,sBACxB,SAAS,MACN,MAAM,cAAc,KACtB;;;;;;;;;EAyCG,QAAQ,UAAU,oBACtB,SAAS,GACT,QAAQ,oBAAoB,IAC5B,SAAS,oBAAoB,IAC7B;IAAQ;MACP,QAAQ,kBAAkB;;;;;;;;;EAiBvB,QAAQ,UAAU,oBACtB,SAAS,GACT,QAAQ,oBAAoB,IAC5B,SAAS,oBAAoB,KAC5B"}
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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"}
@@ -105,6 +105,13 @@ var NovaNatsClient = class {
105
105
  * logged per-message, so one bad message doesn't stop later messages on
106
106
  * the same subscription from being handled.
107
107
  *
108
+ * Each message is annotated with `msg.subjectParams` — the template's
109
+ * `{param}` values extracted from the message's concrete subject — so a
110
+ * wildcard subscriber knows which entity a message belongs to:
111
+ *
112
+ * nats.subscribe("nova.v2.cells.{cell}.status", { cell: "*" },
113
+ * (services, msg) => console.log(msg.subjectParams.cell, services))
114
+ *
108
115
  * Returns a function that unsubscribes when called.
109
116
  */
110
117
  async subscribe(subject, ...args) {
@@ -112,9 +119,13 @@ var NovaNatsClient = class {
112
119
  const nc = await this.connect();
113
120
  const resolvedSubject = buildSubject(subject, params);
114
121
  const sub = nc.subscribe(resolvedSubject);
122
+ const paramPositions = [];
123
+ for (const [index, token] of subject.split(".").entries()) if (token.startsWith("{") && token.endsWith("}")) paramPositions.push([token.slice(1, -1), index]);
115
124
  (async () => {
116
125
  for await (const msg of sub) try {
117
- await handler(msg.json(), msg);
126
+ const subjectTokens = msg.subject.split(".");
127
+ const subjectParams = Object.fromEntries(paramPositions.map(([name, index]) => [name, subjectTokens[index] ?? ""]));
128
+ await handler(msg.json(), Object.assign(msg, { subjectParams }));
118
129
  } catch (err) {
119
130
  console.error(`Error handling NATS message on subject "${resolvedSubject}"`, err);
120
131
  }
@@ -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":"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"}
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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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@wandelbots/nova-js",
3
3
  "type": "module",
4
- "version": "4.3.0-pr.318.e48e5ca",
4
+ "version": "4.4.0-pr.320.79d16cc",
5
5
  "description": "Official JS client for the Wandelbots API",
6
6
  "sideEffects": false,
7
7
  "packageManager": "pnpm@11.9.0+sha512.bd682d5d03fe525ef7c9fd6780c6884d1e756ac4c9c9fe00c538782824310dcf90e3ddc4f53835f06dfaebd5085e41855e0bcbb3b60de2ac5bbab89e5036f03b",
@@ -1,18 +1,18 @@
1
1
  /**
2
- * Experimental pose math helpers (`Pose`, `augmentMath`) for working with
3
- * position + axis-angle orientation values from the NOVA API.
2
+ * Experimental pose math helpers (`Pose`, `Quaternion`, `withMath`) for
3
+ * working with position + axis-angle orientation values from the NOVA API.
4
4
  *
5
5
  * This API is experimental and may change without a major version bump.
6
6
  */
7
7
  export type { Pose as PoseData } from "@wandelbots/nova-api/v2"
8
+ export { Pose } from "../../lib/experimental/math/Pose.ts"
9
+ export { Quaternion } from "../../lib/experimental/math/Quaternion.ts"
10
+ export type { QuaternionData } from "../../lib/experimental/math/Quaternion.ts"
8
11
  export {
9
- augmentMath,
10
12
  augmentPoses,
11
- } from "../../lib/experimental/math/augmentMath.ts"
13
+ withMath,
14
+ } from "../../lib/experimental/math/withMath.ts"
12
15
  export type {
13
16
  DeepPoseAugmented,
14
17
  NovaWithMath,
15
- } from "../../lib/experimental/math/augmentMath.ts"
16
- export { Pose } from "../../lib/experimental/math/Pose.ts"
17
- export { Quaternion } from "../../lib/experimental/math/Quaternion.ts"
18
- export type { QuaternionData } from "../../lib/experimental/math/Quaternion.ts"
18
+ } from "../../lib/experimental/math/withMath.ts"
@@ -20,4 +20,7 @@ export type {
20
20
  // which type applies to which subject).
21
21
  export type * from "../../lib/experimental/nats/generated/types.ts"
22
22
  export { NovaNatsClient } from "../../lib/experimental/nats/NovaNatsClient.ts"
23
- export type { NovaNatsClientConfig } from "../../lib/experimental/nats/NovaNatsClient.ts"
23
+ export type {
24
+ NatsSubscribeMsg,
25
+ NovaNatsClientConfig,
26
+ } from "../../lib/experimental/nats/NovaNatsClient.ts"
@@ -85,9 +85,21 @@ export class Quaternion implements QuaternionData {
85
85
  return new Quaternion(this.w, -this.x, -this.y, -this.z)
86
86
  }
87
87
 
88
- /** Inverse rotation. Equivalent to `conjugate()` since a `Pose`'s orientation is always a unit quaternion. */
88
+ /** Inverse: `conjugate()` scaled by `1 / squaredNorm()`, matching `Eigen::Quaternion::inverse()`. Equal to `conjugate()` for unit quaternions. */
89
89
  inverse(): Quaternion {
90
- return this.conjugate()
90
+ const squaredNorm =
91
+ this.w * this.w + this.x * this.x + this.y * this.y + this.z * this.z
92
+ // Degenerate (zero) quaternion has no inverse - flag it the same way Eigen does.
93
+ if (squaredNorm < EPSILON) {
94
+ return new Quaternion(0, 0, 0, 0)
95
+ }
96
+ const conjugate = this.conjugate()
97
+ return new Quaternion(
98
+ conjugate.w / squaredNorm,
99
+ conjugate.x / squaredNorm,
100
+ conjugate.y / squaredNorm,
101
+ conjugate.z / squaredNorm,
102
+ )
91
103
  }
92
104
 
93
105
  /**
@@ -103,7 +103,7 @@ function augmentPosesInPlace(value: unknown): void {
103
103
  * Recursively walks a parsed API response/request body, upgrading any
104
104
  * `Pose`-shaped objects in place to `Pose` instances so they gain math
105
105
  * methods (`multiply`, `inverse`, ...) while remaining JSON/wire-compatible.
106
- * Used by `augmentMath` for `nova.api.*` responses; exported so it can also
106
+ * Used by `withMath` for `nova.api.*` responses; exported so it can also
107
107
  * be applied manually to e.g. websocket messages.
108
108
  */
109
109
  export function augmentPoses<T>(value: T): DeepPoseAugmented<T> {
@@ -125,7 +125,7 @@ type WithAugmentedPosesCore<
125
125
  /**
126
126
  * Recursively maps every method on `Nova["api"]` (and its nested API groups)
127
127
  * so calls are typed as returning `DeepPoseAugmented` results, matching what
128
- * `augmentMath` does at runtime.
128
+ * `withMath` does at runtime.
129
129
  */
130
130
  type WithAugmentedPoses<T> = WithAugmentedPosesCore<T, []>
131
131
 
@@ -159,7 +159,7 @@ function wrapWithPoseAugmentation<T extends object>(target: T): T {
159
159
  * methods like `multiply`/`inverse`). Returns a new proxied view - does not
160
160
  * mutate the original `nova` instance.
161
161
  */
162
- export function augmentMath(nova: Nova): NovaWithMath {
162
+ export function withMath(nova: Nova): NovaWithMath {
163
163
  const proxy = new Proxy(nova, {
164
164
  get(target, prop) {
165
165
  if (prop === "api") {
@@ -20,9 +20,20 @@ import type {
20
20
 
21
21
  export type NovaNatsClientConfig = ConnectionOptions
22
22
 
23
+ /**
24
+ * A received message, annotated with the values of the subject template's
25
+ * `{param}` placeholders as extracted from the message's concrete subject.
26
+ * With a wildcard subscription (e.g. `{ cell: "*" }`), `subjectParams` is how
27
+ * a handler knows which entity a message belongs to. Typed per subject via
28
+ * the generated `NatsOperationParams`.
29
+ */
30
+ export type NatsSubscribeMsg<K extends NatsSubscribeSubject> = Msg & {
31
+ subjectParams: NatsOperationParams[K]
32
+ }
33
+
23
34
  type NatsMessageHandler<K extends NatsSubscribeSubject> = (
24
35
  payload: NatsSubscribePayloads[K],
25
- msg: Msg,
36
+ msg: NatsSubscribeMsg<K>,
26
37
  ) => void | Promise<void>
27
38
 
28
39
  type SubscribeArgs<K extends NatsSubscribeSubject> =
@@ -92,6 +103,13 @@ export class NovaNatsClient {
92
103
  * logged per-message, so one bad message doesn't stop later messages on
93
104
  * the same subscription from being handled.
94
105
  *
106
+ * Each message is annotated with `msg.subjectParams` — the template's
107
+ * `{param}` values extracted from the message's concrete subject — so a
108
+ * wildcard subscriber knows which entity a message belongs to:
109
+ *
110
+ * nats.subscribe("nova.v2.cells.{cell}.status", { cell: "*" },
111
+ * (services, msg) => console.log(msg.subjectParams.cell, services))
112
+ *
95
113
  * Returns a function that unsubscribes when called.
96
114
  */
97
115
  async subscribe<K extends NatsSubscribeSubject>(
@@ -107,12 +125,33 @@ export class NovaNatsClient {
107
125
  const resolvedSubject = buildSubject(subject, params)
108
126
  const sub = nc.subscribe(resolvedSubject)
109
127
 
128
+ // The token positions of the template's {param} placeholders, computed
129
+ // once here so each message's subjectParams is a plain index pick: a
130
+ // delivered message always matches the subscribed pattern token for
131
+ // token, so its params sit at the same positions as in the template.
132
+ const paramPositions: [name: string, index: number][] = []
133
+ for (const [index, token] of subject.split(".").entries()) {
134
+ if (token.startsWith("{") && token.endsWith("}")) {
135
+ paramPositions.push([token.slice(1, -1), index])
136
+ }
137
+ }
138
+
110
139
  ;(async () => {
111
140
  for await (const msg of sub) {
112
141
  // Handled per-message: a bad payload or a throwing/rejecting handler
113
142
  // should not stop the subscription from processing later messages.
114
143
  try {
115
- await handler(msg.json<NatsSubscribePayloads[K]>(), msg)
144
+ const subjectTokens = msg.subject.split(".")
145
+ const subjectParams = Object.fromEntries(
146
+ paramPositions.map(([name, index]) => [
147
+ name,
148
+ subjectTokens[index] ?? "",
149
+ ]),
150
+ ) as NatsOperationParams[K]
151
+ await handler(
152
+ msg.json<NatsSubscribePayloads[K]>(),
153
+ Object.assign(msg, { subjectParams }),
154
+ )
116
155
  } catch (err) {
117
156
  console.error(
118
157
  `Error handling NATS message on subject "${resolvedSubject}"`,