@wandelbots/nova-js 3.11.4 → 3.13.0-pr.307.07b1ea2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,115 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _nats_io_nats_core = require("@nats-io/nats-core");
3
+ //#region src/lib/experimental/nats/buildSubject.ts
4
+ /**
5
+ * Builds a NATS subject from an AsyncAPI-style channel address template
6
+ * (e.g. `"{instance}.v2.cells.{cell}"`) by substituting each `{param}`
7
+ * placeholder with the corresponding value from `params`.
8
+ */
9
+ function isValidSubjectChar(char) {
10
+ const code = char.charCodeAt(0);
11
+ return code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122 || char === "-" || char === "_";
12
+ }
13
+ function isValidSubjectValue(value) {
14
+ if (value.length === 0) return false;
15
+ for (const char of value) if (!isValidSubjectChar(char)) return false;
16
+ return true;
17
+ }
18
+ function buildSubject(template, params) {
19
+ let result = "";
20
+ let cursor = 0;
21
+ while (cursor < template.length) {
22
+ const openIndex = template.indexOf("{", cursor);
23
+ if (openIndex === -1) {
24
+ result += template.slice(cursor);
25
+ break;
26
+ }
27
+ const closeIndex = template.indexOf("}", openIndex + 1);
28
+ if (closeIndex === -1) {
29
+ result += template.slice(cursor);
30
+ break;
31
+ }
32
+ result += template.slice(cursor, openIndex);
33
+ const paramName = template.slice(openIndex + 1, closeIndex);
34
+ const value = params[paramName];
35
+ if (value === void 0) throw new Error(`Missing value for subject parameter "${paramName}" in template "${template}"`);
36
+ if (!isValidSubjectValue(value)) throw new Error(`Invalid value for subject parameter "${paramName}": "${value}" (must be non-empty and contain only letters, digits, "-", and "_")`);
37
+ result += value;
38
+ cursor = closeIndex + 1;
39
+ }
40
+ return result;
41
+ }
42
+ //#endregion
43
+ //#region src/lib/experimental/nats/NovaNatsClient.ts
44
+ /**
45
+ * Typed NATS client for the Wandelbots NOVA messaging API, generated from
46
+ * src/asyncapi.yaml (see scripts/generate-nats-client.ts).
47
+ *
48
+ * Connects over WebSocket via `@nats-io/nats-core`'s `wsconnect`.
49
+ */
50
+ var NovaNatsClient = class {
51
+ config;
52
+ connectionPromise = null;
53
+ constructor(config) {
54
+ this.config = config;
55
+ }
56
+ /**
57
+ * Connects to NATS if not already connected or connecting, and returns the
58
+ * connection. Safe to call concurrently: all callers share the same
59
+ * in-flight connection attempt instead of each starting their own.
60
+ */
61
+ connect() {
62
+ if (!this.connectionPromise) this.connectionPromise = (0, _nats_io_nats_core.wsconnect)(this.config).catch((err) => {
63
+ this.connectionPromise = null;
64
+ throw err;
65
+ });
66
+ return this.connectionPromise;
67
+ }
68
+ /** Closes the underlying NATS connection, if open or connecting. */
69
+ async close() {
70
+ const connectionPromise = this.connectionPromise;
71
+ this.connectionPromise = null;
72
+ if (!connectionPromise) return;
73
+ try {
74
+ await (await connectionPromise).close();
75
+ } catch {}
76
+ }
77
+ /**
78
+ * Subscribes to a NATS subject published by the server, invoking `handler`
79
+ * with the JSON-decoded payload of every message received.
80
+ *
81
+ * `subject` is the subject template as it appears on the wire, e.g.
82
+ * `"nova.v2.cells.{cell}"`, with `{param}` placeholders filled in from
83
+ * `params`.
84
+ *
85
+ * Returns a function that unsubscribes when called.
86
+ */
87
+ async subscribe(subject, params, handler) {
88
+ const nc = await this.connect();
89
+ const resolvedSubject = buildSubject(subject, params);
90
+ const sub = nc.subscribe(resolvedSubject);
91
+ (async () => {
92
+ for await (const msg of sub) handler(msg.json(), msg);
93
+ })().catch((err) => {
94
+ console.error(`Error handling NATS subscription for "${resolvedSubject}"`, err);
95
+ });
96
+ return () => sub.unsubscribe();
97
+ }
98
+ /**
99
+ * Sends a request payload for a NATS subject the server receives, and
100
+ * waits for the JSON-decoded reply.
101
+ *
102
+ * `subject` is the subject template as it appears on the wire, e.g.
103
+ * `"nova.v2.cells.{cell}.bus-ios.ios.set"`, with `{param}` placeholders
104
+ * filled in from `params`.
105
+ */
106
+ async request(subject, params, payload, opts = {}) {
107
+ const nc = await this.connect();
108
+ const resolvedSubject = buildSubject(subject, params);
109
+ return (await nc.request(resolvedSubject, JSON.stringify(payload), { timeout: opts.timeout ?? 5e3 })).json();
110
+ }
111
+ };
112
+ //#endregion
113
+ exports.NovaNatsClient = NovaNatsClient;
114
+
115
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +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 */\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 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 { 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 private connectionPromise: Promise<NatsConnection> | null = null\n\n constructor(config: NovaNatsClientConfig) {\n this.config = config\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 * 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":";;;;;;;;AAMA,SAAS,mBAAmB,MAAuB;CACjD,MAAM,OAAO,KAAK,WAAW,CAAC;CAC9B,OACG,QAAQ,MAAM,QAAQ,MACtB,QAAQ,MAAM,QAAQ,MACtB,QAAQ,MAAM,QAAQ,OACvB,SAAS,OACT,SAAS;AAEb;AAEA,SAAS,oBAAoB,OAAwB;CACnD,IAAI,MAAM,WAAW,GAAG,OAAO;CAC/B,KAAK,MAAM,QAAQ,OACjB,IAAI,CAAC,mBAAmB,IAAI,GAAG,OAAO;CAExC,OAAO;AACT;AAEA,SAAgB,aACd,UACA,QACQ;CAIR,IAAI,SAAS;CACb,IAAI,SAAS;CAEb,OAAO,SAAS,SAAS,QAAQ;EAC/B,MAAM,YAAY,SAAS,QAAQ,KAAK,MAAM;EAC9C,IAAI,cAAc,IAAI;GACpB,UAAU,SAAS,MAAM,MAAM;GAC/B;EACF;EAEA,MAAM,aAAa,SAAS,QAAQ,KAAK,YAAY,CAAC;EACtD,IAAI,eAAe,IAAI;GACrB,UAAU,SAAS,MAAM,MAAM;GAC/B;EACF;EAEA,UAAU,SAAS,MAAM,QAAQ,SAAS;EAC1C,MAAM,YAAY,SAAS,MAAM,YAAY,GAAG,UAAU;EAC1D,MAAM,QAAQ,OAAO;EACrB,IAAI,UAAU,KAAA,GACZ,MAAM,IAAI,MACR,wCAAwC,UAAU,iBAAiB,SAAS,EAC9E;EAEF,IAAI,CAAC,oBAAoB,KAAK,GAC5B,MAAM,IAAI,MACR,wCAAwC,UAAU,MAAM,MAAM,qEAChE;EAEF,UAAU;EAEV,SAAS,aAAa;CACxB;CAEA,OAAO;AACT;;;;;;;;;AC3CA,IAAa,iBAAb,MAA4B;CAC1B;CACA,oBAA4D;CAE5D,YAAY,QAA8B;EACxC,KAAK,SAAS;CAChB;;;;;;CAOA,UAAmC;EACjC,IAAI,CAAC,KAAK,mBACR,KAAK,qBAAA,GAAA,mBAAA,WAA8B,KAAK,MAAM,EAAE,OAAO,QAAiB;GAEtE,KAAK,oBAAoB;GACzB,MAAM;EACR,CAAC;EAEH,OAAO,KAAK;CACd;;CAGA,MAAM,QAAuB;EAC3B,MAAM,oBAAoB,KAAK;EAC/B,KAAK,oBAAoB;EACzB,IAAI,CAAC,mBAAmB;EACxB,IAAI;GAEF,OAAM,MADW,mBACR,MAAM;EACjB,QAAQ,CAER;CACF;;;;;;;;;;;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"}