@wandelbots/nova-js 3.13.0-pr.307.ea0533d → 3.13.0

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.
Files changed (45) hide show
  1. package/dist/Nova--8gozkLR.d.mts +85 -0
  2. package/dist/Nova--8gozkLR.d.mts.map +1 -0
  3. package/dist/Nova-B1o5iTQ6.d.cts +85 -0
  4. package/dist/Nova-B1o5iTQ6.d.cts.map +1 -0
  5. package/dist/{context-Cu5mXcFZ.cjs → context-BZELA2eh.cjs} +3 -146
  6. package/dist/context-BZELA2eh.cjs.map +1 -0
  7. package/dist/{context-CmXqEEwW.mjs → context-ffr5Q2dc.mjs} +3 -86
  8. package/dist/context-ffr5Q2dc.mjs.map +1 -0
  9. package/dist/converters-DnG1fX23.mjs +87 -0
  10. package/dist/converters-DnG1fX23.mjs.map +1 -0
  11. package/dist/converters-EYS27XJE.cjs +146 -0
  12. package/dist/converters-EYS27XJE.cjs.map +1 -0
  13. package/dist/experimental/nats/index.cjs +96 -17
  14. package/dist/experimental/nats/index.cjs.map +1 -1
  15. package/dist/experimental/nats/index.d.cts +1079 -67
  16. package/dist/experimental/nats/index.d.cts.map +1 -1
  17. package/dist/experimental/nats/index.d.mts +1079 -67
  18. package/dist/experimental/nats/index.d.mts.map +1 -1
  19. package/dist/experimental/nats/index.mjs +96 -18
  20. package/dist/experimental/nats/index.mjs.map +1 -1
  21. package/dist/index.cjs +13 -12
  22. package/dist/index.mjs +3 -2
  23. package/dist/v1/index.cjs +3 -3
  24. package/dist/v1/index.cjs.map +1 -1
  25. package/dist/v1/index.mjs +2 -2
  26. package/dist/v2/index.cjs +4 -3
  27. package/dist/v2/index.cjs.map +1 -1
  28. package/dist/v2/index.d.cts +5 -83
  29. package/dist/v2/index.d.cts.map +1 -1
  30. package/dist/v2/index.d.mts +4 -82
  31. package/dist/v2/index.d.mts.map +1 -1
  32. package/dist/v2/index.mjs +2 -1
  33. package/dist/v2/index.mjs.map +1 -1
  34. package/dist/{wandelscriptUtils-CKhiZJsB.mjs → wandelscriptUtils-CpUXdLVc.mjs} +3 -2
  35. package/dist/{wandelscriptUtils-CKhiZJsB.mjs.map → wandelscriptUtils-CpUXdLVc.mjs.map} +1 -1
  36. package/dist/{wandelscriptUtils-BdqeVDCY.cjs → wandelscriptUtils-DcY1aLWu.cjs} +13 -12
  37. package/dist/{wandelscriptUtils-BdqeVDCY.cjs.map → wandelscriptUtils-DcY1aLWu.cjs.map} +1 -1
  38. package/package.json +1 -3
  39. package/src/experimental/nats/index.ts +7 -0
  40. package/src/lib/experimental/nats/NovaNatsClient.ts +86 -15
  41. package/src/lib/experimental/nats/buildNatsServerUrl.ts +15 -0
  42. package/src/lib/experimental/nats/buildSubject.ts +53 -5
  43. package/src/lib/experimental/nats/generated/operations.ts +918 -71
  44. package/dist/context-CmXqEEwW.mjs.map +0 -1
  45. package/dist/context-Cu5mXcFZ.cjs.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"converters-DnG1fX23.mjs","names":[],"sources":["../src/lib/converters.ts"],"sourcesContent":["export type URLParseOptions = {\n /**\n * Ignore any scheme in the input string and force this scheme instead.\n */\n scheme?: \"http\" | \"https\"\n /**\n * If the input string does not include a scheme, use this as the default\n * scheme.\n */\n defaultScheme?: \"http\" | \"https\"\n}\n\n/**\n * Parse a string as a URL, with options to enforce or default the scheme.\n */\nexport function parseUrl(url: string, options: URLParseOptions = {}): URL {\n const { scheme, defaultScheme } = options\n\n const schemeRegex = /^[a-zA-Z]+:\\/\\//\n\n if (scheme) {\n // Force the scheme by removing any existing scheme and prepending the desired one\n url = url.replace(schemeRegex, \"\")\n url = `${scheme}://${url}`\n } else if (defaultScheme && !schemeRegex.test(url)) {\n // No scheme is present, add the default one\n url = `${defaultScheme}://${url}`\n }\n\n return new URL(url)\n}\n\n/**\n * Attempt to parse a string as a URL; return undefined if we can't\n */\nexport function tryParseUrl(\n url: string,\n options: URLParseOptions = {},\n): URL | undefined {\n try {\n return parseUrl(url, options)\n } catch {\n return undefined\n }\n}\n\n/**\n * Permissively parse a NOVA instance URL from a config variable.\n * If scheme is not specified, defaults to https for *.wandelbots.io hosts,\n * and http otherwise.\n * Throws an error if a valid URL could not be determined.\n */\nexport function parseNovaInstanceUrl(url: string): URL {\n const testUrl = tryParseUrl(url, { defaultScheme: \"http\" })\n if (testUrl?.host.endsWith(\".wandelbots.io\")) {\n return parseUrl(url, { defaultScheme: \"https\" })\n } else {\n return parseUrl(url, { defaultScheme: \"http\" })\n }\n}\n\n/** Try to parse something as JSON; return undefined if we can't */\n// biome-ignore lint/suspicious/noExplicitAny: it's json\nexport function tryParseJson(json: unknown): any {\n try {\n return JSON.parse(json as string)\n } catch {\n return undefined\n }\n}\n\n/** Try to turn something into JSON; return undefined if we can't */\nexport function tryStringifyJson(json: unknown): string | undefined {\n try {\n return JSON.stringify(json)\n } catch {\n return undefined\n }\n}\n\n/**\n * Converts object parameters to query string.\n * e.g. { a: \"1\", b: \"2\" } => \"?a=1&b=2\"\n * {} => \"\"\n */\nexport function makeUrlQueryString(obj: Record<string, string>): string {\n const str = new URLSearchParams(obj).toString()\n return str ? `?${str}` : \"\"\n}\n\n/** Convert radians to degrees */\nexport function radiansToDegrees(radians: number): number {\n return radians * (180 / Math.PI)\n}\n\n/** Convert degrees to radians */\nexport function degreesToRadians(degrees: number): number {\n return degrees * (Math.PI / 180)\n}\n\n/**\n * Check for coordinate system id equivalence, accounting for the \"world\" default\n * on empty/undefined values.\n */\nexport function isSameCoordinateSystem(\n firstCoordSystem: string | undefined,\n secondCoordSystem: string | undefined,\n) {\n if (!firstCoordSystem) firstCoordSystem = \"world\"\n if (!secondCoordSystem) secondCoordSystem = \"world\"\n\n return firstCoordSystem === secondCoordSystem\n}\n\n/**\n * Helpful const for converting {x, y, z} to [x, y, z] and vice versa\n */\nexport const XYZ_TO_VECTOR = { x: 0, y: 1, z: 2 }\n"],"mappings":";;;;AAeA,SAAgB,SAAS,KAAa,UAA2B,CAAC,GAAQ;CACxE,MAAM,EAAE,QAAQ,kBAAkB;CAElC,MAAM,cAAc;CAEpB,IAAI,QAAQ;EAEV,MAAM,IAAI,QAAQ,aAAa,EAAE;EACjC,MAAM,GAAG,OAAO,KAAK;CACvB,OAAO,IAAI,iBAAiB,CAAC,YAAY,KAAK,GAAG,GAE/C,MAAM,GAAG,cAAc,KAAK;CAG9B,OAAO,IAAI,IAAI,GAAG;AACpB;;;;AAKA,SAAgB,YACd,KACA,UAA2B,CAAC,GACX;CACjB,IAAI;EACF,OAAO,SAAS,KAAK,OAAO;CAC9B,QAAQ;EACN;CACF;AACF;;;;;;;AAQA,SAAgB,qBAAqB,KAAkB;CAErD,IADgB,YAAY,KAAK,EAAE,eAAe,OAAO,CAC/C,GAAG,KAAK,SAAS,gBAAgB,GACzC,OAAO,SAAS,KAAK,EAAE,eAAe,QAAQ,CAAC;MAE/C,OAAO,SAAS,KAAK,EAAE,eAAe,OAAO,CAAC;AAElD;;AAIA,SAAgB,aAAa,MAAoB;CAC/C,IAAI;EACF,OAAO,KAAK,MAAM,IAAc;CAClC,QAAQ;EACN;CACF;AACF;;AAGA,SAAgB,iBAAiB,MAAmC;CAClE,IAAI;EACF,OAAO,KAAK,UAAU,IAAI;CAC5B,QAAQ;EACN;CACF;AACF;;;;;;AAOA,SAAgB,mBAAmB,KAAqC;CACtE,MAAM,MAAM,IAAI,gBAAgB,GAAG,EAAE,SAAS;CAC9C,OAAO,MAAM,IAAI,QAAQ;AAC3B;;AAGA,SAAgB,iBAAiB,SAAyB;CACxD,OAAO,WAAW,MAAM,KAAK;AAC/B;;AAGA,SAAgB,iBAAiB,SAAyB;CACxD,OAAO,WAAW,KAAK,KAAK;AAC9B;;;;;AAMA,SAAgB,uBACd,kBACA,mBACA;CACA,IAAI,CAAC,kBAAkB,mBAAmB;CAC1C,IAAI,CAAC,mBAAmB,oBAAoB;CAE5C,OAAO,qBAAqB;AAC9B;;;;AAKA,MAAa,gBAAgB;CAAE,GAAG;CAAG,GAAG;CAAG,GAAG;AAAE"}
@@ -0,0 +1,146 @@
1
+ //#region src/lib/converters.ts
2
+ /**
3
+ * Parse a string as a URL, with options to enforce or default the scheme.
4
+ */
5
+ function parseUrl(url, options = {}) {
6
+ const { scheme, defaultScheme } = options;
7
+ const schemeRegex = /^[a-zA-Z]+:\/\//;
8
+ if (scheme) {
9
+ url = url.replace(schemeRegex, "");
10
+ url = `${scheme}://${url}`;
11
+ } else if (defaultScheme && !schemeRegex.test(url)) url = `${defaultScheme}://${url}`;
12
+ return new URL(url);
13
+ }
14
+ /**
15
+ * Attempt to parse a string as a URL; return undefined if we can't
16
+ */
17
+ function tryParseUrl(url, options = {}) {
18
+ try {
19
+ return parseUrl(url, options);
20
+ } catch {
21
+ return;
22
+ }
23
+ }
24
+ /**
25
+ * Permissively parse a NOVA instance URL from a config variable.
26
+ * If scheme is not specified, defaults to https for *.wandelbots.io hosts,
27
+ * and http otherwise.
28
+ * Throws an error if a valid URL could not be determined.
29
+ */
30
+ function parseNovaInstanceUrl(url) {
31
+ if (tryParseUrl(url, { defaultScheme: "http" })?.host.endsWith(".wandelbots.io")) return parseUrl(url, { defaultScheme: "https" });
32
+ else return parseUrl(url, { defaultScheme: "http" });
33
+ }
34
+ /** Try to parse something as JSON; return undefined if we can't */
35
+ function tryParseJson(json) {
36
+ try {
37
+ return JSON.parse(json);
38
+ } catch {
39
+ return;
40
+ }
41
+ }
42
+ /** Try to turn something into JSON; return undefined if we can't */
43
+ function tryStringifyJson(json) {
44
+ try {
45
+ return JSON.stringify(json);
46
+ } catch {
47
+ return;
48
+ }
49
+ }
50
+ /**
51
+ * Converts object parameters to query string.
52
+ * e.g. { a: "1", b: "2" } => "?a=1&b=2"
53
+ * {} => ""
54
+ */
55
+ function makeUrlQueryString(obj) {
56
+ const str = new URLSearchParams(obj).toString();
57
+ return str ? `?${str}` : "";
58
+ }
59
+ /** Convert radians to degrees */
60
+ function radiansToDegrees(radians) {
61
+ return radians * (180 / Math.PI);
62
+ }
63
+ /** Convert degrees to radians */
64
+ function degreesToRadians(degrees) {
65
+ return degrees * (Math.PI / 180);
66
+ }
67
+ /**
68
+ * Check for coordinate system id equivalence, accounting for the "world" default
69
+ * on empty/undefined values.
70
+ */
71
+ function isSameCoordinateSystem(firstCoordSystem, secondCoordSystem) {
72
+ if (!firstCoordSystem) firstCoordSystem = "world";
73
+ if (!secondCoordSystem) secondCoordSystem = "world";
74
+ return firstCoordSystem === secondCoordSystem;
75
+ }
76
+ /**
77
+ * Helpful const for converting {x, y, z} to [x, y, z] and vice versa
78
+ */
79
+ const XYZ_TO_VECTOR = {
80
+ x: 0,
81
+ y: 1,
82
+ z: 2
83
+ };
84
+ //#endregion
85
+ Object.defineProperty(exports, "XYZ_TO_VECTOR", {
86
+ enumerable: true,
87
+ get: function() {
88
+ return XYZ_TO_VECTOR;
89
+ }
90
+ });
91
+ Object.defineProperty(exports, "degreesToRadians", {
92
+ enumerable: true,
93
+ get: function() {
94
+ return degreesToRadians;
95
+ }
96
+ });
97
+ Object.defineProperty(exports, "isSameCoordinateSystem", {
98
+ enumerable: true,
99
+ get: function() {
100
+ return isSameCoordinateSystem;
101
+ }
102
+ });
103
+ Object.defineProperty(exports, "makeUrlQueryString", {
104
+ enumerable: true,
105
+ get: function() {
106
+ return makeUrlQueryString;
107
+ }
108
+ });
109
+ Object.defineProperty(exports, "parseNovaInstanceUrl", {
110
+ enumerable: true,
111
+ get: function() {
112
+ return parseNovaInstanceUrl;
113
+ }
114
+ });
115
+ Object.defineProperty(exports, "parseUrl", {
116
+ enumerable: true,
117
+ get: function() {
118
+ return parseUrl;
119
+ }
120
+ });
121
+ Object.defineProperty(exports, "radiansToDegrees", {
122
+ enumerable: true,
123
+ get: function() {
124
+ return radiansToDegrees;
125
+ }
126
+ });
127
+ Object.defineProperty(exports, "tryParseJson", {
128
+ enumerable: true,
129
+ get: function() {
130
+ return tryParseJson;
131
+ }
132
+ });
133
+ Object.defineProperty(exports, "tryParseUrl", {
134
+ enumerable: true,
135
+ get: function() {
136
+ return tryParseUrl;
137
+ }
138
+ });
139
+ Object.defineProperty(exports, "tryStringifyJson", {
140
+ enumerable: true,
141
+ get: function() {
142
+ return tryStringifyJson;
143
+ }
144
+ });
145
+
146
+ //# sourceMappingURL=converters-EYS27XJE.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"converters-EYS27XJE.cjs","names":[],"sources":["../src/lib/converters.ts"],"sourcesContent":["export type URLParseOptions = {\n /**\n * Ignore any scheme in the input string and force this scheme instead.\n */\n scheme?: \"http\" | \"https\"\n /**\n * If the input string does not include a scheme, use this as the default\n * scheme.\n */\n defaultScheme?: \"http\" | \"https\"\n}\n\n/**\n * Parse a string as a URL, with options to enforce or default the scheme.\n */\nexport function parseUrl(url: string, options: URLParseOptions = {}): URL {\n const { scheme, defaultScheme } = options\n\n const schemeRegex = /^[a-zA-Z]+:\\/\\//\n\n if (scheme) {\n // Force the scheme by removing any existing scheme and prepending the desired one\n url = url.replace(schemeRegex, \"\")\n url = `${scheme}://${url}`\n } else if (defaultScheme && !schemeRegex.test(url)) {\n // No scheme is present, add the default one\n url = `${defaultScheme}://${url}`\n }\n\n return new URL(url)\n}\n\n/**\n * Attempt to parse a string as a URL; return undefined if we can't\n */\nexport function tryParseUrl(\n url: string,\n options: URLParseOptions = {},\n): URL | undefined {\n try {\n return parseUrl(url, options)\n } catch {\n return undefined\n }\n}\n\n/**\n * Permissively parse a NOVA instance URL from a config variable.\n * If scheme is not specified, defaults to https for *.wandelbots.io hosts,\n * and http otherwise.\n * Throws an error if a valid URL could not be determined.\n */\nexport function parseNovaInstanceUrl(url: string): URL {\n const testUrl = tryParseUrl(url, { defaultScheme: \"http\" })\n if (testUrl?.host.endsWith(\".wandelbots.io\")) {\n return parseUrl(url, { defaultScheme: \"https\" })\n } else {\n return parseUrl(url, { defaultScheme: \"http\" })\n }\n}\n\n/** Try to parse something as JSON; return undefined if we can't */\n// biome-ignore lint/suspicious/noExplicitAny: it's json\nexport function tryParseJson(json: unknown): any {\n try {\n return JSON.parse(json as string)\n } catch {\n return undefined\n }\n}\n\n/** Try to turn something into JSON; return undefined if we can't */\nexport function tryStringifyJson(json: unknown): string | undefined {\n try {\n return JSON.stringify(json)\n } catch {\n return undefined\n }\n}\n\n/**\n * Converts object parameters to query string.\n * e.g. { a: \"1\", b: \"2\" } => \"?a=1&b=2\"\n * {} => \"\"\n */\nexport function makeUrlQueryString(obj: Record<string, string>): string {\n const str = new URLSearchParams(obj).toString()\n return str ? `?${str}` : \"\"\n}\n\n/** Convert radians to degrees */\nexport function radiansToDegrees(radians: number): number {\n return radians * (180 / Math.PI)\n}\n\n/** Convert degrees to radians */\nexport function degreesToRadians(degrees: number): number {\n return degrees * (Math.PI / 180)\n}\n\n/**\n * Check for coordinate system id equivalence, accounting for the \"world\" default\n * on empty/undefined values.\n */\nexport function isSameCoordinateSystem(\n firstCoordSystem: string | undefined,\n secondCoordSystem: string | undefined,\n) {\n if (!firstCoordSystem) firstCoordSystem = \"world\"\n if (!secondCoordSystem) secondCoordSystem = \"world\"\n\n return firstCoordSystem === secondCoordSystem\n}\n\n/**\n * Helpful const for converting {x, y, z} to [x, y, z] and vice versa\n */\nexport const XYZ_TO_VECTOR = { x: 0, y: 1, z: 2 }\n"],"mappings":";;;;AAeA,SAAgB,SAAS,KAAa,UAA2B,CAAC,GAAQ;CACxE,MAAM,EAAE,QAAQ,kBAAkB;CAElC,MAAM,cAAc;CAEpB,IAAI,QAAQ;EAEV,MAAM,IAAI,QAAQ,aAAa,EAAE;EACjC,MAAM,GAAG,OAAO,KAAK;CACvB,OAAO,IAAI,iBAAiB,CAAC,YAAY,KAAK,GAAG,GAE/C,MAAM,GAAG,cAAc,KAAK;CAG9B,OAAO,IAAI,IAAI,GAAG;AACpB;;;;AAKA,SAAgB,YACd,KACA,UAA2B,CAAC,GACX;CACjB,IAAI;EACF,OAAO,SAAS,KAAK,OAAO;CAC9B,QAAQ;EACN;CACF;AACF;;;;;;;AAQA,SAAgB,qBAAqB,KAAkB;CAErD,IADgB,YAAY,KAAK,EAAE,eAAe,OAAO,CAC/C,GAAG,KAAK,SAAS,gBAAgB,GACzC,OAAO,SAAS,KAAK,EAAE,eAAe,QAAQ,CAAC;MAE/C,OAAO,SAAS,KAAK,EAAE,eAAe,OAAO,CAAC;AAElD;;AAIA,SAAgB,aAAa,MAAoB;CAC/C,IAAI;EACF,OAAO,KAAK,MAAM,IAAc;CAClC,QAAQ;EACN;CACF;AACF;;AAGA,SAAgB,iBAAiB,MAAmC;CAClE,IAAI;EACF,OAAO,KAAK,UAAU,IAAI;CAC5B,QAAQ;EACN;CACF;AACF;;;;;;AAOA,SAAgB,mBAAmB,KAAqC;CACtE,MAAM,MAAM,IAAI,gBAAgB,GAAG,EAAE,SAAS;CAC9C,OAAO,MAAM,IAAI,QAAQ;AAC3B;;AAGA,SAAgB,iBAAiB,SAAyB;CACxD,OAAO,WAAW,MAAM,KAAK;AAC/B;;AAGA,SAAgB,iBAAiB,SAAyB;CACxD,OAAO,WAAW,KAAK,KAAK;AAC9B;;;;;AAMA,SAAgB,uBACd,kBACA,mBACA;CACA,IAAI,CAAC,kBAAkB,mBAAmB;CAC1C,IAAI,CAAC,mBAAmB,oBAAoB;CAE5C,OAAO,qBAAqB;AAC9B;;;;AAKA,MAAa,gBAAgB;CAAE,GAAG;CAAG,GAAG;CAAG,GAAG;AAAE"}
@@ -1,18 +1,59 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_converters = require("../../converters-EYS27XJE.cjs");
2
3
  let _nats_io_nats_core = require("@nats-io/nats-core");
4
+ //#region src/lib/experimental/nats/buildNatsServerUrl.ts
5
+ /**
6
+ * Builds the WebSocket URL for a NOVA instance's NATS gateway from its
7
+ * instance URL, e.g. `https://foo.instance.wandelbots.io` becomes
8
+ * `wss://foo.instance.wandelbots.io/api/nats`.
9
+ *
10
+ * Pass the result as `servers` in the `NovaNatsClientConfig` passed to
11
+ * `NovaNatsClient`.
12
+ */
13
+ function buildNatsServerUrl(instanceUrl) {
14
+ const url = require_converters.parseNovaInstanceUrl(instanceUrl);
15
+ return `${url.protocol === "https:" ? "wss:" : "ws:"}//${url.host}/api/nats`;
16
+ }
17
+ //#endregion
3
18
  //#region src/lib/experimental/nats/buildSubject.ts
4
19
  /**
5
20
  * Builds a NATS subject from an AsyncAPI-style channel address template
6
21
  * (e.g. `"{instance}.v2.cells.{cell}"`) by substituting each `{param}`
7
22
  * placeholder with the corresponding value from `params`.
8
23
  */
24
+ function isValidSubjectChar(char) {
25
+ const code = char.charCodeAt(0);
26
+ return code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122 || char === "-" || char === "_";
27
+ }
28
+ function isValidSubjectValue(value) {
29
+ if (value === "*") return true;
30
+ if (value.length === 0) return false;
31
+ for (const char of value) if (!isValidSubjectChar(char)) return false;
32
+ return true;
33
+ }
9
34
  function buildSubject(template, params) {
10
- return template.replace(/\{([^}]+)\}/g, (match, paramName) => {
35
+ let result = "";
36
+ let cursor = 0;
37
+ while (cursor < template.length) {
38
+ const openIndex = template.indexOf("{", cursor);
39
+ if (openIndex === -1) {
40
+ result += template.slice(cursor);
41
+ break;
42
+ }
43
+ const closeIndex = template.indexOf("}", openIndex + 1);
44
+ if (closeIndex === -1) {
45
+ result += template.slice(cursor);
46
+ break;
47
+ }
48
+ result += template.slice(cursor, openIndex);
49
+ const paramName = template.slice(openIndex + 1, closeIndex);
11
50
  const value = params[paramName];
12
51
  if (value === void 0) throw new Error(`Missing value for subject parameter "${paramName}" in template "${template}"`);
13
- if (value === "" || /[\s.>*]/.test(value)) throw new Error(`Invalid value for subject parameter "${paramName}": "${value}" (must be non-empty and must not contain whitespace or the NATS special characters ".", ">", "*")`);
14
- return value;
15
- });
52
+ if (!isValidSubjectValue(value)) throw new Error(`Invalid value for subject parameter "${paramName}": "${value}" (must be non-empty and contain only letters, digits, "-", and "_")`);
53
+ result += value;
54
+ cursor = closeIndex + 1;
55
+ }
56
+ return result;
16
57
  }
17
58
  //#endregion
18
59
  //#region src/lib/experimental/nats/NovaNatsClient.ts
@@ -24,19 +65,34 @@ function buildSubject(template, params) {
24
65
  */
25
66
  var NovaNatsClient = class {
26
67
  config;
27
- connection = null;
28
- constructor(config) {
29
- this.config = config;
68
+ connectionPromise = null;
69
+ constructor(nova, config = {}) {
70
+ this.config = {
71
+ servers: buildNatsServerUrl(nova.instanceUrl.href),
72
+ ...nova.accessToken ? { token: nova.accessToken } : {},
73
+ ...config
74
+ };
30
75
  }
31
- /** Connects to NATS if not already connected, and returns the connection. */
32
- async connect() {
33
- if (!this.connection) this.connection = await (0, _nats_io_nats_core.wsconnect)(this.config);
34
- return this.connection;
76
+ /**
77
+ * Connects to NATS if not already connected or connecting, and returns the
78
+ * connection. Safe to call concurrently: all callers share the same
79
+ * in-flight connection attempt instead of each starting their own.
80
+ */
81
+ connect() {
82
+ if (!this.connectionPromise) this.connectionPromise = (0, _nats_io_nats_core.wsconnect)(this.config).catch((err) => {
83
+ this.connectionPromise = null;
84
+ throw err;
85
+ });
86
+ return this.connectionPromise;
35
87
  }
36
- /** Closes the underlying NATS connection, if open. */
88
+ /** Closes the underlying NATS connection, if open or connecting. */
37
89
  async close() {
38
- await this.connection?.close();
39
- this.connection = null;
90
+ const connectionPromise = this.connectionPromise;
91
+ this.connectionPromise = null;
92
+ if (!connectionPromise) return;
93
+ try {
94
+ await (await connectionPromise).close();
95
+ } catch {}
40
96
  }
41
97
  /**
42
98
  * Subscribes to a NATS subject published by the server, invoking `handler`
@@ -46,16 +102,25 @@ var NovaNatsClient = class {
46
102
  * `"nova.v2.cells.{cell}"`, with `{param}` placeholders filled in from
47
103
  * `params`.
48
104
  *
105
+ * Errors decoding a message or thrown/rejected by `handler` are caught and
106
+ * logged per-message, so one bad message doesn't stop later messages on
107
+ * the same subscription from being handled.
108
+ *
49
109
  * Returns a function that unsubscribes when called.
50
110
  */
51
- async subscribe(subject, params, handler) {
111
+ async subscribe(subject, ...args) {
112
+ const [params, handler] = args.length === 1 ? [{}, args[0]] : [args[0], args[1]];
52
113
  const nc = await this.connect();
53
114
  const resolvedSubject = buildSubject(subject, params);
54
115
  const sub = nc.subscribe(resolvedSubject);
55
116
  (async () => {
56
- for await (const msg of sub) handler(msg.json(), msg);
117
+ for await (const msg of sub) try {
118
+ await handler(msg.json(), msg);
119
+ } catch (err) {
120
+ console.error(`Error handling NATS message on subject "${resolvedSubject}"`, err);
121
+ }
57
122
  })().catch((err) => {
58
- console.error(`Error handling NATS subscription for "${resolvedSubject}"`, err);
123
+ console.error(`NATS subscription iterator failed for "${resolvedSubject}"`, err);
59
124
  });
60
125
  return () => sub.unsubscribe();
61
126
  }
@@ -72,8 +137,22 @@ var NovaNatsClient = class {
72
137
  const resolvedSubject = buildSubject(subject, params);
73
138
  return (await nc.request(resolvedSubject, JSON.stringify(payload), { timeout: opts.timeout ?? 5e3 })).json();
74
139
  }
140
+ /**
141
+ * Publishes a JSON payload to any NATS subject defined in the spec,
142
+ * without waiting for a reply.
143
+ *
144
+ * `subject` is the subject template as it appears on the wire, e.g.
145
+ * `"nova.v2.cells.{cell}.bus-ios.ios.set"`, with `{param}` placeholders
146
+ * filled in from `params`.
147
+ */
148
+ async publish(subject, params, payload) {
149
+ const nc = await this.connect();
150
+ const resolvedSubject = buildSubject(subject, params);
151
+ nc.publish(resolvedSubject, JSON.stringify(payload));
152
+ }
75
153
  };
76
154
  //#endregion
77
155
  exports.NovaNatsClient = NovaNatsClient;
156
+ exports.buildNatsServerUrl = buildNatsServerUrl;
78
157
 
79
158
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../../../src/lib/experimental/nats/buildSubject.ts","../../../src/lib/experimental/nats/NovaNatsClient.ts"],"sourcesContent":["/**\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 */\nexport function buildSubject(\n template: string,\n params: Record<string, string>,\n): string {\n return template.replace(/\\{([^}]+)\\}/g, (match, paramName: string) => {\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 (value === \"\" || /[\\s.>*]/.test(value)) {\n throw new Error(\n `Invalid value for subject parameter \"${paramName}\": \"${value}\" (must be non-empty and must not contain whitespace or the NATS special characters \".\", \">\", \"*\")`,\n )\n }\n return value\n })\n}\n","import {\n type ConnectionOptions,\n type Msg,\n type NatsConnection,\n wsconnect,\n} from \"@nats-io/nats-core\"\nimport { buildSubject } from \"./buildSubject.ts\"\nimport type {\n NatsOperationParams,\n NatsReplyPayloads,\n NatsRequestPayloads,\n NatsRequestSubject,\n NatsSubscribePayloads,\n NatsSubscribeSubject,\n} from \"./generated/operations.ts\"\n\nexport type NovaNatsClientConfig = ConnectionOptions\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 connection: NatsConnection | null = null\n\n constructor(config: NovaNatsClientConfig) {\n this.config = config\n }\n\n /** Connects to NATS if not already connected, and returns the connection. */\n async connect(): Promise<NatsConnection> {\n if (!this.connection) {\n this.connection = await wsconnect(this.config)\n }\n return this.connection\n }\n\n /** Closes the underlying NATS connection, if open. */\n async close(): Promise<void> {\n await this.connection?.close()\n this.connection = null\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 * Returns a function that unsubscribes when called.\n */\n async subscribe<K extends NatsSubscribeSubject>(\n subject: K,\n params: NatsOperationParams[K],\n handler: (payload: NatsSubscribePayloads[K], msg: Msg) => void,\n ): Promise<() => void> {\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 handler(msg.json<NatsSubscribePayloads[K]>(), msg)\n }\n })().catch((err: unknown) => {\n console.error(\n `Error handling NATS subscription 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"],"mappings":";;;;;;;;AAKA,SAAgB,aACd,UACA,QACQ;CACR,OAAO,SAAS,QAAQ,iBAAiB,OAAO,cAAsB;EACpE,MAAM,QAAQ,OAAO;EACrB,IAAI,UAAU,KAAA,GACZ,MAAM,IAAI,MACR,wCAAwC,UAAU,iBAAiB,SAAS,EAC9E;EAEF,IAAI,UAAU,MAAM,UAAU,KAAK,KAAK,GACtC,MAAM,IAAI,MACR,wCAAwC,UAAU,MAAM,MAAM,mGAChE;EAEF,OAAO;CACT,CAAC;AACH;;;;;;;;;ACCA,IAAa,iBAAb,MAA4B;CAC1B;CACA,aAAoC;CAEpC,YAAY,QAA8B;EACxC,KAAK,SAAS;CAChB;;CAGA,MAAM,UAAmC;EACvC,IAAI,CAAC,KAAK,YACR,KAAK,aAAa,OAAA,GAAA,mBAAA,WAAgB,KAAK,MAAM;EAE/C,OAAO,KAAK;CACd;;CAGA,MAAM,QAAuB;EAC3B,MAAM,KAAK,YAAY,MAAM;EAC7B,KAAK,aAAa;CACpB;;;;;;;;;;;CAYA,MAAM,UACJ,SACA,QACA,SACqB;EACrB,MAAM,KAAK,MAAM,KAAK,QAAQ;EAC9B,MAAM,kBAAkB,aAAa,SAAS,MAAM;EACpD,MAAM,MAAM,GAAG,UAAU,eAAe;EAEvC,CAAC,YAAY;GACZ,WAAW,MAAM,OAAO,KACtB,QAAQ,IAAI,KAA+B,GAAG,GAAG;EAErD,GAAG,EAAE,OAAO,QAAiB;GAC3B,QAAQ,MACN,yCAAyC,gBAAgB,IACzD,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,GACU,KAA2B;CACxC;AACF"}
1
+ {"version":3,"file":"index.cjs","names":["parseNovaInstanceUrl"],"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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"}