tci-client-node 0.1.1 → 0.2.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 (41) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +33 -13
  3. package/dist/audio/index.cjs +63 -20
  4. package/dist/audio/index.cjs.map +1 -1
  5. package/dist/audio/index.d.cts +18 -3
  6. package/dist/audio/index.d.ts +18 -3
  7. package/dist/audio/index.js +63 -20
  8. package/dist/audio/index.js.map +1 -1
  9. package/dist/dialect/index.cjs +331 -0
  10. package/dist/dialect/index.cjs.map +1 -0
  11. package/dist/dialect/index.d.cts +29 -0
  12. package/dist/dialect/index.d.ts +29 -0
  13. package/dist/dialect/index.js +292 -0
  14. package/dist/dialect/index.js.map +1 -0
  15. package/dist/{index-CK3XdXP3.d.cts → index-lbK6NGY4.d.cts} +1 -1
  16. package/dist/{index-Dfmrk2MR.d.ts → index-paj1AOJY.d.ts} +1 -1
  17. package/dist/index.cjs +890 -224
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.d.cts +54 -12
  20. package/dist/index.d.ts +54 -12
  21. package/dist/index.js +876 -224
  22. package/dist/index.js.map +1 -1
  23. package/dist/protocol/index.cjs.map +1 -1
  24. package/dist/protocol/index.d.cts +1 -1
  25. package/dist/protocol/index.d.ts +1 -1
  26. package/dist/protocol/index.js.map +1 -1
  27. package/dist/testing/index.cjs +77 -25
  28. package/dist/testing/index.cjs.map +1 -1
  29. package/dist/testing/index.d.cts +2 -0
  30. package/dist/testing/index.d.ts +2 -0
  31. package/dist/testing/index.js +77 -25
  32. package/dist/testing/index.js.map +1 -1
  33. package/dist/transport/index.cjs +175 -0
  34. package/dist/transport/index.cjs.map +1 -0
  35. package/dist/transport/index.d.cts +37 -0
  36. package/dist/transport/index.d.ts +37 -0
  37. package/dist/transport/index.js +138 -0
  38. package/dist/transport/index.js.map +1 -0
  39. package/dist/types-9seY9Th-.d.ts +63 -0
  40. package/dist/types-xRotf9gW.d.cts +63 -0
  41. package/package.json +15 -1
@@ -0,0 +1,37 @@
1
+ import { EventEmitter } from 'eventemitter3';
2
+ import WebSocket from 'ws';
3
+
4
+ interface TciTransportEvents {
5
+ connected: () => void;
6
+ disconnected: (reason?: unknown) => void;
7
+ text: (raw: string) => void;
8
+ binary: (raw: Buffer) => void;
9
+ error: (error: Error) => void;
10
+ }
11
+ interface TciTransport extends EventEmitter<TciTransportEvents> {
12
+ readonly url: string;
13
+ connect(timeoutMs: number): Promise<void>;
14
+ disconnect(code?: number, reason?: string): Promise<void>;
15
+ isConnected(): boolean;
16
+ sendText(raw: string): Promise<void>;
17
+ sendBinary(raw: Buffer): Promise<void>;
18
+ terminate(): void;
19
+ }
20
+ type TciTransportFactory = (url: string) => TciTransport;
21
+
22
+ declare class WebSocketTciTransport extends EventEmitter<TciTransportEvents> implements TciTransport {
23
+ readonly url: string;
24
+ private readonly WebSocketImpl;
25
+ private socket?;
26
+ constructor(url: string, WebSocketImpl?: typeof WebSocket);
27
+ connect(timeoutMs: number): Promise<void>;
28
+ disconnect(code?: number, reason?: string): Promise<void>;
29
+ isConnected(): boolean;
30
+ sendText(raw: string): Promise<void>;
31
+ sendBinary(raw: Buffer): Promise<void>;
32
+ terminate(): void;
33
+ private attach;
34
+ private send;
35
+ }
36
+
37
+ export { type TciTransport, type TciTransportEvents, type TciTransportFactory, WebSocketTciTransport };
@@ -0,0 +1,138 @@
1
+ // src/transport/WebSocketTransport.ts
2
+ import { EventEmitter } from "eventemitter3";
3
+ import WebSocket from "ws";
4
+
5
+ // src/errors.ts
6
+ var TciError = class extends Error {
7
+ code;
8
+ details;
9
+ constructor(code, message, details) {
10
+ super(message);
11
+ this.name = "TciError";
12
+ this.code = code;
13
+ this.details = details;
14
+ }
15
+ };
16
+ function toTciError(error, fallbackCode = "protocol-error") {
17
+ if (error instanceof TciError) {
18
+ return error;
19
+ }
20
+ if (error instanceof Error) {
21
+ return new TciError(fallbackCode, error.message, error);
22
+ }
23
+ return new TciError(fallbackCode, String(error), error);
24
+ }
25
+
26
+ // src/transport/WebSocketTransport.ts
27
+ var WebSocketTciTransport = class extends EventEmitter {
28
+ constructor(url, WebSocketImpl = WebSocket) {
29
+ super();
30
+ this.url = url;
31
+ this.WebSocketImpl = WebSocketImpl;
32
+ }
33
+ url;
34
+ WebSocketImpl;
35
+ socket;
36
+ async connect(timeoutMs) {
37
+ if (this.socket?.readyState === WebSocket.OPEN) return;
38
+ const socket = new this.WebSocketImpl(this.url);
39
+ this.socket = socket;
40
+ await new Promise((resolve, reject) => {
41
+ const timer = setTimeout(() => {
42
+ cleanup();
43
+ this.terminate();
44
+ reject(new TciError("connect-timeout", `Timed out connecting to ${this.url}`));
45
+ }, timeoutMs);
46
+ const cleanup = () => {
47
+ clearTimeout(timer);
48
+ socket.off("open", onOpen);
49
+ socket.off("close", onCloseBeforeOpen);
50
+ socket.off("error", onErrorBeforeOpen);
51
+ };
52
+ const onOpen = () => {
53
+ cleanup();
54
+ this.attach(socket);
55
+ this.emit("connected");
56
+ resolve();
57
+ };
58
+ const onCloseBeforeOpen = () => {
59
+ cleanup();
60
+ reject(new TciError("disconnected", `Disconnected while connecting to ${this.url}`));
61
+ };
62
+ const onErrorBeforeOpen = (error) => {
63
+ cleanup();
64
+ reject(toTciError(error, "disconnected"));
65
+ };
66
+ socket.once("open", onOpen);
67
+ socket.once("close", onCloseBeforeOpen);
68
+ socket.once("error", onErrorBeforeOpen);
69
+ });
70
+ }
71
+ async disconnect(code = 1e3, reason = "client disconnect") {
72
+ const socket = this.socket;
73
+ if (!socket || socket.readyState === WebSocket.CLOSED) return;
74
+ await new Promise((resolve) => {
75
+ const timer = setTimeout(resolve, 1e3);
76
+ timer.unref?.();
77
+ socket.once("close", () => {
78
+ clearTimeout(timer);
79
+ resolve();
80
+ });
81
+ socket.once("error", () => {
82
+ clearTimeout(timer);
83
+ resolve();
84
+ });
85
+ socket.close(code, reason);
86
+ });
87
+ }
88
+ isConnected() {
89
+ return this.socket?.readyState === WebSocket.OPEN;
90
+ }
91
+ async sendText(raw) {
92
+ await this.send(raw);
93
+ }
94
+ async sendBinary(raw) {
95
+ await this.send(raw, true);
96
+ }
97
+ terminate() {
98
+ try {
99
+ this.socket?.terminate();
100
+ } catch {
101
+ }
102
+ }
103
+ attach(socket) {
104
+ socket.on("message", (data, isBinary) => {
105
+ try {
106
+ const buffer = dataToBuffer(data);
107
+ if (isBinary) this.emit("binary", buffer);
108
+ else this.emit("text", buffer.toString("utf8"));
109
+ } catch (error) {
110
+ this.emit("error", error instanceof Error ? error : new Error(String(error)));
111
+ }
112
+ });
113
+ socket.on("close", (code, reason) => {
114
+ if (this.socket === socket) this.socket = void 0;
115
+ this.emit("disconnected", { code, reason: reason.toString("utf8") });
116
+ });
117
+ socket.on("error", (error) => this.emit("error", error));
118
+ }
119
+ async send(data, binary = false) {
120
+ const socket = this.socket;
121
+ if (!socket || socket.readyState !== WebSocket.OPEN) {
122
+ throw new TciError("not-connected", "TCI socket is not connected");
123
+ }
124
+ await new Promise((resolve, reject) => {
125
+ socket.send(data, { binary }, (error) => error ? reject(error) : resolve());
126
+ });
127
+ }
128
+ };
129
+ function dataToBuffer(data) {
130
+ if (Buffer.isBuffer(data)) return data;
131
+ if (data instanceof ArrayBuffer) return Buffer.from(data);
132
+ if (Array.isArray(data)) return Buffer.concat(data.map((item) => dataToBuffer(item)));
133
+ throw new TciError("protocol-error", "Unsupported WebSocket data type");
134
+ }
135
+ export {
136
+ WebSocketTciTransport
137
+ };
138
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/transport/WebSocketTransport.ts","../../src/errors.ts"],"sourcesContent":["import { EventEmitter } from 'eventemitter3';\nimport WebSocket from 'ws';\nimport { TciError, toTciError } from '../errors.js';\nimport type { TciTransport, TciTransportEvents } from './types.js';\n\nexport class WebSocketTciTransport extends EventEmitter<TciTransportEvents> implements TciTransport {\n private socket?: WebSocket;\n\n constructor(readonly url: string, private readonly WebSocketImpl: typeof WebSocket = WebSocket) {\n super();\n }\n\n async connect(timeoutMs: number): Promise<void> {\n if (this.socket?.readyState === WebSocket.OPEN) return;\n const socket = new this.WebSocketImpl(this.url);\n this.socket = socket;\n await new Promise<void>((resolve, reject) => {\n const timer = setTimeout(() => {\n cleanup();\n this.terminate();\n reject(new TciError('connect-timeout', `Timed out connecting to ${this.url}`));\n }, timeoutMs);\n const cleanup = () => {\n clearTimeout(timer);\n socket.off('open', onOpen);\n socket.off('close', onCloseBeforeOpen);\n socket.off('error', onErrorBeforeOpen);\n };\n const onOpen = () => {\n cleanup();\n this.attach(socket);\n this.emit('connected');\n resolve();\n };\n const onCloseBeforeOpen = () => {\n cleanup();\n reject(new TciError('disconnected', `Disconnected while connecting to ${this.url}`));\n };\n const onErrorBeforeOpen = (error: Error) => {\n cleanup();\n reject(toTciError(error, 'disconnected'));\n };\n socket.once('open', onOpen);\n socket.once('close', onCloseBeforeOpen);\n socket.once('error', onErrorBeforeOpen);\n });\n }\n\n async disconnect(code = 1000, reason = 'client disconnect'): Promise<void> {\n const socket = this.socket;\n if (!socket || socket.readyState === WebSocket.CLOSED) return;\n await new Promise<void>((resolve) => {\n const timer = setTimeout(resolve, 1_000);\n timer.unref?.();\n socket.once('close', () => { clearTimeout(timer); resolve(); });\n socket.once('error', () => { clearTimeout(timer); resolve(); });\n socket.close(code, reason);\n });\n }\n\n isConnected(): boolean { return this.socket?.readyState === WebSocket.OPEN; }\n\n async sendText(raw: string): Promise<void> { await this.send(raw); }\n async sendBinary(raw: Buffer): Promise<void> { await this.send(raw, true); }\n\n terminate(): void {\n try { this.socket?.terminate(); } catch { /* ignore termination races */ }\n }\n\n private attach(socket: WebSocket): void {\n socket.on('message', (data, isBinary) => {\n try {\n const buffer = dataToBuffer(data);\n if (isBinary) this.emit('binary', buffer);\n else this.emit('text', buffer.toString('utf8'));\n } catch (error) {\n this.emit('error', error instanceof Error ? error : new Error(String(error)));\n }\n });\n socket.on('close', (code, reason) => {\n if (this.socket === socket) this.socket = undefined;\n this.emit('disconnected', { code, reason: reason.toString('utf8') });\n });\n socket.on('error', (error) => this.emit('error', error));\n }\n\n private async send(data: string | Buffer, binary = false): Promise<void> {\n const socket = this.socket;\n if (!socket || socket.readyState !== WebSocket.OPEN) {\n throw new TciError('not-connected', 'TCI socket is not connected');\n }\n await new Promise<void>((resolve, reject) => {\n socket.send(data, { binary }, (error) => error ? reject(error) : resolve());\n });\n }\n}\n\nfunction dataToBuffer(data: WebSocket.RawData): Buffer {\n if (Buffer.isBuffer(data)) return data;\n if (data instanceof ArrayBuffer) return Buffer.from(data);\n if (Array.isArray(data)) return Buffer.concat(data.map((item) => dataToBuffer(item)));\n throw new TciError('protocol-error', 'Unsupported WebSocket data type');\n}\n","export type TciErrorCode =\n | 'connect-timeout'\n | 'handshake-timeout'\n | 'invalid-handshake'\n | 'unknown-dialect'\n | 'command-timeout'\n | 'not-connected'\n | 'disconnected'\n | 'protocol-error'\n | 'invalid-frame'\n | 'cancelled';\n\nexport class TciError extends Error {\n readonly code: TciErrorCode;\n readonly details?: unknown;\n\n constructor(code: TciErrorCode, message: string, details?: unknown) {\n super(message);\n this.name = 'TciError';\n this.code = code;\n this.details = details;\n }\n}\n\nexport function toTciError(error: unknown, fallbackCode: TciErrorCode = 'protocol-error'): TciError {\n if (error instanceof TciError) {\n return error;\n }\n if (error instanceof Error) {\n return new TciError(fallbackCode, error.message, error);\n }\n return new TciError(fallbackCode, String(error), error);\n}\n"],"mappings":";AAAA,SAAS,oBAAoB;AAC7B,OAAO,eAAe;;;ACWf,IAAM,WAAN,cAAuB,MAAM;AAAA,EACzB;AAAA,EACA;AAAA,EAET,YAAY,MAAoB,SAAiB,SAAmB;AAClE,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,SAAK,UAAU;AAAA,EACjB;AACF;AAEO,SAAS,WAAW,OAAgB,eAA6B,kBAA4B;AAClG,MAAI,iBAAiB,UAAU;AAC7B,WAAO;AAAA,EACT;AACA,MAAI,iBAAiB,OAAO;AAC1B,WAAO,IAAI,SAAS,cAAc,MAAM,SAAS,KAAK;AAAA,EACxD;AACA,SAAO,IAAI,SAAS,cAAc,OAAO,KAAK,GAAG,KAAK;AACxD;;;AD3BO,IAAM,wBAAN,cAAoC,aAAyD;AAAA,EAGlG,YAAqB,KAA8B,gBAAkC,WAAW;AAC9F,UAAM;AADa;AAA8B;AAAA,EAEnD;AAAA,EAFqB;AAAA,EAA8B;AAAA,EAF3C;AAAA,EAMR,MAAM,QAAQ,WAAkC;AAC9C,QAAI,KAAK,QAAQ,eAAe,UAAU,KAAM;AAChD,UAAM,SAAS,IAAI,KAAK,cAAc,KAAK,GAAG;AAC9C,SAAK,SAAS;AACd,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,YAAM,QAAQ,WAAW,MAAM;AAC7B,gBAAQ;AACR,aAAK,UAAU;AACf,eAAO,IAAI,SAAS,mBAAmB,2BAA2B,KAAK,GAAG,EAAE,CAAC;AAAA,MAC/E,GAAG,SAAS;AACZ,YAAM,UAAU,MAAM;AACpB,qBAAa,KAAK;AAClB,eAAO,IAAI,QAAQ,MAAM;AACzB,eAAO,IAAI,SAAS,iBAAiB;AACrC,eAAO,IAAI,SAAS,iBAAiB;AAAA,MACvC;AACA,YAAM,SAAS,MAAM;AACnB,gBAAQ;AACR,aAAK,OAAO,MAAM;AAClB,aAAK,KAAK,WAAW;AACrB,gBAAQ;AAAA,MACV;AACA,YAAM,oBAAoB,MAAM;AAC9B,gBAAQ;AACR,eAAO,IAAI,SAAS,gBAAgB,oCAAoC,KAAK,GAAG,EAAE,CAAC;AAAA,MACrF;AACA,YAAM,oBAAoB,CAAC,UAAiB;AAC1C,gBAAQ;AACR,eAAO,WAAW,OAAO,cAAc,CAAC;AAAA,MAC1C;AACA,aAAO,KAAK,QAAQ,MAAM;AAC1B,aAAO,KAAK,SAAS,iBAAiB;AACtC,aAAO,KAAK,SAAS,iBAAiB;AAAA,IACxC,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,WAAW,OAAO,KAAM,SAAS,qBAAoC;AACzE,UAAM,SAAS,KAAK;AACpB,QAAI,CAAC,UAAU,OAAO,eAAe,UAAU,OAAQ;AACvD,UAAM,IAAI,QAAc,CAAC,YAAY;AACnC,YAAM,QAAQ,WAAW,SAAS,GAAK;AACvC,YAAM,QAAQ;AACd,aAAO,KAAK,SAAS,MAAM;AAAE,qBAAa,KAAK;AAAG,gBAAQ;AAAA,MAAG,CAAC;AAC9D,aAAO,KAAK,SAAS,MAAM;AAAE,qBAAa,KAAK;AAAG,gBAAQ;AAAA,MAAG,CAAC;AAC9D,aAAO,MAAM,MAAM,MAAM;AAAA,IAC3B,CAAC;AAAA,EACH;AAAA,EAEA,cAAuB;AAAE,WAAO,KAAK,QAAQ,eAAe,UAAU;AAAA,EAAM;AAAA,EAE5E,MAAM,SAAS,KAA4B;AAAE,UAAM,KAAK,KAAK,GAAG;AAAA,EAAG;AAAA,EACnE,MAAM,WAAW,KAA4B;AAAE,UAAM,KAAK,KAAK,KAAK,IAAI;AAAA,EAAG;AAAA,EAE3E,YAAkB;AAChB,QAAI;AAAE,WAAK,QAAQ,UAAU;AAAA,IAAG,QAAQ;AAAA,IAAiC;AAAA,EAC3E;AAAA,EAEQ,OAAO,QAAyB;AACtC,WAAO,GAAG,WAAW,CAAC,MAAM,aAAa;AACvC,UAAI;AACF,cAAM,SAAS,aAAa,IAAI;AAChC,YAAI,SAAU,MAAK,KAAK,UAAU,MAAM;AAAA,YACnC,MAAK,KAAK,QAAQ,OAAO,SAAS,MAAM,CAAC;AAAA,MAChD,SAAS,OAAO;AACd,aAAK,KAAK,SAAS,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC;AAAA,MAC9E;AAAA,IACF,CAAC;AACD,WAAO,GAAG,SAAS,CAAC,MAAM,WAAW;AACnC,UAAI,KAAK,WAAW,OAAQ,MAAK,SAAS;AAC1C,WAAK,KAAK,gBAAgB,EAAE,MAAM,QAAQ,OAAO,SAAS,MAAM,EAAE,CAAC;AAAA,IACrE,CAAC;AACD,WAAO,GAAG,SAAS,CAAC,UAAU,KAAK,KAAK,SAAS,KAAK,CAAC;AAAA,EACzD;AAAA,EAEA,MAAc,KAAK,MAAuB,SAAS,OAAsB;AACvE,UAAM,SAAS,KAAK;AACpB,QAAI,CAAC,UAAU,OAAO,eAAe,UAAU,MAAM;AACnD,YAAM,IAAI,SAAS,iBAAiB,6BAA6B;AAAA,IACnE;AACA,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,aAAO,KAAK,MAAM,EAAE,OAAO,GAAG,CAAC,UAAU,QAAQ,OAAO,KAAK,IAAI,QAAQ,CAAC;AAAA,IAC5E,CAAC;AAAA,EACH;AACF;AAEA,SAAS,aAAa,MAAiC;AACrD,MAAI,OAAO,SAAS,IAAI,EAAG,QAAO;AAClC,MAAI,gBAAgB,YAAa,QAAO,OAAO,KAAK,IAAI;AACxD,MAAI,MAAM,QAAQ,IAAI,EAAG,QAAO,OAAO,OAAO,KAAK,IAAI,CAAC,SAAS,aAAa,IAAI,CAAC,CAAC;AACpF,QAAM,IAAI,SAAS,kBAAkB,iCAAiC;AACxE;","names":[]}
@@ -0,0 +1,63 @@
1
+ import { T as TciCommand } from './text-BwCWY1k1.js';
2
+
3
+ type BuiltInTciDialectId = 'expertsdr-1.4' | 'expertsdr-1.5-1.8' | 'expertsdr-1.9-2.0' | 'aethersdr-1.5' | 'thetis-2.0' | 'generic-observed';
4
+ type TciDialectId = BuiltInTciDialectId | (string & {
5
+ readonly __tciDialectId?: never;
6
+ });
7
+ type TciDialectSelection = 'auto' | TciDialectId | TciDialect;
8
+ type TciStreamLengthSemantics = 'scalar' | 'per-channel' | 'auto';
9
+ interface TciProtocolIdentity {
10
+ programName?: string;
11
+ protocolVersion?: string;
12
+ rawProtocolArgs: string[];
13
+ device?: string;
14
+ }
15
+ interface TciDialectDetectionContext {
16
+ identity: TciProtocolIdentity;
17
+ commands: readonly TciCommand[];
18
+ commandNames: ReadonlySet<string>;
19
+ }
20
+ interface TciDialectScore {
21
+ score: number;
22
+ evidence: string[];
23
+ warnings?: string[];
24
+ }
25
+ interface TciDriveState {
26
+ trx: number;
27
+ value: number;
28
+ }
29
+ interface TciDialect {
30
+ readonly id: TciDialectId;
31
+ readonly label: string;
32
+ readonly streamLengthSemantics: TciStreamLengthSemantics;
33
+ readonly supportsStreamChannels: boolean;
34
+ readonly supportsTxAudioSource: boolean;
35
+ detect(context: TciDialectDetectionContext): TciDialectScore;
36
+ resolve?(context: TciDialectDetectionContext): TciDialect;
37
+ buildDriveSetArgs(trx: number, value: number): readonly unknown[];
38
+ buildDriveReadArgs(trx: number): readonly unknown[];
39
+ parseDrive(args: readonly string[], defaultTrx: number): TciDriveState | undefined;
40
+ buildTuneDriveSetArgs(trx: number, value: number): readonly unknown[];
41
+ buildTuneDriveReadArgs(trx: number): readonly unknown[];
42
+ parseTuneDrive(args: readonly string[], defaultTrx: number): TciDriveState | undefined;
43
+ }
44
+ interface TciDialectDetection {
45
+ dialect: TciDialect;
46
+ confidence: 'manual' | 'high' | 'medium' | 'low';
47
+ evidence: string[];
48
+ warnings: string[];
49
+ }
50
+ interface TciHandshakeResult {
51
+ identity: TciProtocolIdentity;
52
+ dialect: TciDialectDetection;
53
+ ready: true;
54
+ commandNames: string[];
55
+ }
56
+ interface TciWriteResult<T> {
57
+ requested: T;
58
+ applied: T;
59
+ outcome: 'applied' | 'clamped';
60
+ acknowledgement: 'state' | 'reply' | 'readback';
61
+ }
62
+
63
+ export type { BuiltInTciDialectId as B, TciDialectId as T, TciHandshakeResult as a, TciDialectSelection as b, TciWriteResult as c, TciDialect as d, TciDialectDetection as e, TciDialectDetectionContext as f, TciDialectScore as g, TciDriveState as h, TciProtocolIdentity as i, TciStreamLengthSemantics as j };
@@ -0,0 +1,63 @@
1
+ import { T as TciCommand } from './text-BwCWY1k1.cjs';
2
+
3
+ type BuiltInTciDialectId = 'expertsdr-1.4' | 'expertsdr-1.5-1.8' | 'expertsdr-1.9-2.0' | 'aethersdr-1.5' | 'thetis-2.0' | 'generic-observed';
4
+ type TciDialectId = BuiltInTciDialectId | (string & {
5
+ readonly __tciDialectId?: never;
6
+ });
7
+ type TciDialectSelection = 'auto' | TciDialectId | TciDialect;
8
+ type TciStreamLengthSemantics = 'scalar' | 'per-channel' | 'auto';
9
+ interface TciProtocolIdentity {
10
+ programName?: string;
11
+ protocolVersion?: string;
12
+ rawProtocolArgs: string[];
13
+ device?: string;
14
+ }
15
+ interface TciDialectDetectionContext {
16
+ identity: TciProtocolIdentity;
17
+ commands: readonly TciCommand[];
18
+ commandNames: ReadonlySet<string>;
19
+ }
20
+ interface TciDialectScore {
21
+ score: number;
22
+ evidence: string[];
23
+ warnings?: string[];
24
+ }
25
+ interface TciDriveState {
26
+ trx: number;
27
+ value: number;
28
+ }
29
+ interface TciDialect {
30
+ readonly id: TciDialectId;
31
+ readonly label: string;
32
+ readonly streamLengthSemantics: TciStreamLengthSemantics;
33
+ readonly supportsStreamChannels: boolean;
34
+ readonly supportsTxAudioSource: boolean;
35
+ detect(context: TciDialectDetectionContext): TciDialectScore;
36
+ resolve?(context: TciDialectDetectionContext): TciDialect;
37
+ buildDriveSetArgs(trx: number, value: number): readonly unknown[];
38
+ buildDriveReadArgs(trx: number): readonly unknown[];
39
+ parseDrive(args: readonly string[], defaultTrx: number): TciDriveState | undefined;
40
+ buildTuneDriveSetArgs(trx: number, value: number): readonly unknown[];
41
+ buildTuneDriveReadArgs(trx: number): readonly unknown[];
42
+ parseTuneDrive(args: readonly string[], defaultTrx: number): TciDriveState | undefined;
43
+ }
44
+ interface TciDialectDetection {
45
+ dialect: TciDialect;
46
+ confidence: 'manual' | 'high' | 'medium' | 'low';
47
+ evidence: string[];
48
+ warnings: string[];
49
+ }
50
+ interface TciHandshakeResult {
51
+ identity: TciProtocolIdentity;
52
+ dialect: TciDialectDetection;
53
+ ready: true;
54
+ commandNames: string[];
55
+ }
56
+ interface TciWriteResult<T> {
57
+ requested: T;
58
+ applied: T;
59
+ outcome: 'applied' | 'clamped';
60
+ acknowledgement: 'state' | 'reply' | 'readback';
61
+ }
62
+
63
+ export type { BuiltInTciDialectId as B, TciDialectId as T, TciHandshakeResult as a, TciDialectSelection as b, TciWriteResult as c, TciDialect as d, TciDialectDetection as e, TciDialectDetectionContext as f, TciDialectScore as g, TciDriveState as h, TciProtocolIdentity as i, TciStreamLengthSemantics as j };
package/package.json CHANGED
@@ -1,10 +1,13 @@
1
1
  {
2
2
  "name": "tci-client-node",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Pure TypeScript TCI (Transceiver Control Interface) client for SunSDR/ExpertSDR.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "boybook",
8
+ "engines": {
9
+ "node": ">=20"
10
+ },
8
11
  "repository": {
9
12
  "type": "git",
10
13
  "url": "git+https://github.com/boybook/tci-client-node.git"
@@ -26,6 +29,7 @@
26
29
  "files": [
27
30
  "dist",
28
31
  "README.md",
32
+ "CHANGELOG.md",
29
33
  "LICENSE"
30
34
  ],
31
35
  "main": "./dist/index.cjs",
@@ -63,6 +67,16 @@
63
67
  "import": "./dist/audio/index.js",
64
68
  "require": "./dist/audio/index.cjs"
65
69
  },
70
+ "./dialect": {
71
+ "types": "./dist/dialect/index.d.ts",
72
+ "import": "./dist/dialect/index.js",
73
+ "require": "./dist/dialect/index.cjs"
74
+ },
75
+ "./transport": {
76
+ "types": "./dist/transport/index.d.ts",
77
+ "import": "./dist/transport/index.js",
78
+ "require": "./dist/transport/index.cjs"
79
+ },
66
80
  "./testing": {
67
81
  "types": "./dist/testing/index.d.ts",
68
82
  "import": "./dist/testing/index.js",