@springfield/ham-radio-utils 2.0.1 → 2.1.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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## [2.1.0](https://gitlab.com/springfield-ham-radio/app/ham-radio-utils/compare/v2.0.2...v2.1.0) (2025-06-21)
2
+
3
+
4
+ ### Features
5
+
6
+ * add UI logger for command-level logging in UI environments ([c9f7545](https://gitlab.com/springfield-ham-radio/app/ham-radio-utils/commit/c9f754580119f4c8bfd1a42ebca089692fd4a62e))
7
+
8
+ ## [2.0.2](https://gitlab.com/springfield-ham-radio/app/ham-radio-utils/compare/v2.0.1...v2.0.2) (2025-06-20)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **deps:** update dependency loglayer to ^6.4.3 ([0e41dfa](https://gitlab.com/springfield-ham-radio/app/ham-radio-utils/commit/0e41dfa764d9b3b176efcea00a586c3e2cfef336))
14
+
1
15
  ## [2.0.1](https://gitlab.com/springfield-ham-radio/app/ham-radio-utils/compare/v2.0.0...v2.0.1) (2025-06-14)
2
16
 
3
17
 
package/dist/index.d.ts CHANGED
@@ -8,4 +8,6 @@ export * from './utils/memory-channel-utils.js';
8
8
  export * from './utils/memory-data-utils.js';
9
9
  export * from './utils/number-to-bcd.js';
10
10
  export * from './utils/to-hex-words.js';
11
+ export * from './utils/ui-logger.js';
12
+ export * from './utils/ui-logger-factory.js';
11
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC;AAE7C,cAAc,uCAAuC,CAAC;AACtD,cAAc,kDAAkD,CAAC;AAEjE,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,iCAAiC,CAAC;AAChD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC;AAE7C,cAAc,uCAAuC,CAAC;AACtD,cAAc,kDAAkD,CAAC;AAEjE,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,iCAAiC,CAAC;AAChD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC"}
package/dist/index.js CHANGED
@@ -8,4 +8,6 @@ export * from './utils/memory-channel-utils.js';
8
8
  export * from './utils/memory-data-utils.js';
9
9
  export * from './utils/number-to-bcd.js';
10
10
  export * from './utils/to-hex-words.js';
11
+ export * from './utils/ui-logger.js';
12
+ export * from './utils/ui-logger-factory.js';
11
13
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC;AAE7C,cAAc,uCAAuC,CAAC;AACtD,cAAc,kDAAkD,CAAC;AAEjE,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,iCAAiC,CAAC;AAChD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC","sourcesContent":["export * from './memory/segmented-memory.js';\n\nexport * from './test-utils/radio-channel-factory.js';\nexport * from './test-utils/radio-programmed-channel-factory.js';\n\nexport * from './utils/bcd-to-number.js';\nexport * from './utils/band-plan.js';\nexport * from './utils/frequency-display.js';\nexport * from './utils/memory-channel-utils.js';\nexport * from './utils/memory-data-utils.js';\nexport * from './utils/number-to-bcd.js';\nexport * from './utils/to-hex-words.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC;AAE7C,cAAc,uCAAuC,CAAC;AACtD,cAAc,kDAAkD,CAAC;AAEjE,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,iCAAiC,CAAC;AAChD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC","sourcesContent":["export * from './memory/segmented-memory.js';\n\nexport * from './test-utils/radio-channel-factory.js';\nexport * from './test-utils/radio-programmed-channel-factory.js';\n\nexport * from './utils/bcd-to-number.js';\nexport * from './utils/band-plan.js';\nexport * from './utils/frequency-display.js';\nexport * from './utils/memory-channel-utils.js';\nexport * from './utils/memory-data-utils.js';\nexport * from './utils/number-to-bcd.js';\nexport * from './utils/to-hex-words.js';\nexport * from './utils/ui-logger.js';\nexport * from './utils/ui-logger-factory.js';\n"]}
@@ -0,0 +1,8 @@
1
+ import { UILogger } from './ui-logger.js';
2
+ export interface UILoggerConfig {
3
+ enableConsoleDebug?: boolean;
4
+ customTransport?: any;
5
+ }
6
+ export declare function createUILogger(config?: UILoggerConfig): UILogger;
7
+ export declare function createUILoggerWithCallback(callback: (logEntry: any) => void, config?: UILoggerConfig): UILogger;
8
+ //# sourceMappingURL=ui-logger-factory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui-logger-factory.d.ts","sourceRoot":"","sources":["../../src/utils/ui-logger-factory.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AA2B1C,MAAM,WAAW,cAAc;IAC7B,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,eAAe,CAAC,EAAE,GAAG,CAAC;CACvB;AAED,wBAAgB,cAAc,CAAC,MAAM,GAAE,cAAmB,GAAG,QAAQ,CAYpE;AAED,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,IAAI,EAAE,MAAM,GAAE,cAAmB,GAAG,QAAQ,CAQnH"}
@@ -0,0 +1,48 @@
1
+ import { LogLayer } from 'loglayer';
2
+ import { BaseTransport } from '@loglayer/transport';
3
+ import { UILogger } from './ui-logger.js';
4
+ /**
5
+ * Custom LogLayer transport for capturing UI log entries.
6
+ */
7
+ class UILogTransport extends BaseTransport {
8
+ #callback;
9
+ constructor(callback) {
10
+ // Pass a dummy logger to BaseTransport
11
+ super({ logger: { log: () => { } }, id: 'ui-logger-transport', enabled: true });
12
+ this.#callback = callback;
13
+ }
14
+ shipToLogger(params) {
15
+ const logEntry = {
16
+ timestamp: new Date().toISOString(),
17
+ level: params.logLevel,
18
+ message: params.messages.join(' '),
19
+ data: params.data,
20
+ hasData: params.hasData,
21
+ };
22
+ this.#callback(logEntry);
23
+ return [];
24
+ }
25
+ }
26
+ export function createUILogger(config = {}) {
27
+ // By default, just print to console as JSON
28
+ const defaultCallback = (logEntry) => {
29
+ console.log(JSON.stringify(logEntry));
30
+ };
31
+ const transport = config.customTransport || new UILogTransport(defaultCallback);
32
+ const logger = new LogLayer({
33
+ transport,
34
+ consoleDebug: config.enableConsoleDebug ?? false,
35
+ });
36
+ logger.setLevel('debug');
37
+ return new UILogger(logger);
38
+ }
39
+ export function createUILoggerWithCallback(callback, config = {}) {
40
+ const transport = new UILogTransport(callback);
41
+ const logger = new LogLayer({
42
+ transport,
43
+ consoleDebug: config.enableConsoleDebug ?? false,
44
+ });
45
+ logger.setLevel('debug');
46
+ return new UILogger(logger);
47
+ }
48
+ //# sourceMappingURL=ui-logger-factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui-logger-factory.js","sourceRoot":"","sources":["../../src/utils/ui-logger-factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C;;GAEG;AACH,MAAM,cAAe,SAAQ,aAAkB;IAC7C,SAAS,CAA0B;IAEnC,YAAY,QAAiC;QAC3C,uCAAuC;QACvC,KAAK,CAAC,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE,EAAE,EAAE,EAAE,qBAAqB,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/E,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;IAC5B,CAAC;IAED,YAAY,CAAC,MAAW;QACtB,MAAM,QAAQ,GAAG;YACf,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,KAAK,EAAE,MAAM,CAAC,QAAQ;YACtB,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC;YAClC,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,OAAO,EAAE,MAAM,CAAC,OAAO;SACxB,CAAC;QACF,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QACzB,OAAO,EAAE,CAAC;IACZ,CAAC;CACF;AAOD,MAAM,UAAU,cAAc,CAAC,SAAyB,EAAE;IACxD,4CAA4C;IAC5C,MAAM,eAAe,GAAG,CAAC,QAAa,EAAE,EAAE;QACxC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC;IACxC,CAAC,CAAC;IACF,MAAM,SAAS,GAAG,MAAM,CAAC,eAAe,IAAI,IAAI,cAAc,CAAC,eAAe,CAAC,CAAC;IAChF,MAAM,MAAM,GAAG,IAAI,QAAQ,CAAC;QAC1B,SAAS;QACT,YAAY,EAAE,MAAM,CAAC,kBAAkB,IAAI,KAAK;KACjD,CAAC,CAAC;IACH,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IACzB,OAAO,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,QAAiC,EAAE,SAAyB,EAAE;IACvG,MAAM,SAAS,GAAG,IAAI,cAAc,CAAC,QAAQ,CAAC,CAAC;IAC/C,MAAM,MAAM,GAAG,IAAI,QAAQ,CAAC;QAC1B,SAAS;QACT,YAAY,EAAE,MAAM,CAAC,kBAAkB,IAAI,KAAK;KACjD,CAAC,CAAC;IACH,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IACzB,OAAO,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC;AAC9B,CAAC","sourcesContent":["import { LogLayer } from 'loglayer';\nimport { BaseTransport } from '@loglayer/transport';\nimport { UILogger } from './ui-logger.js';\n\n/**\n * Custom LogLayer transport for capturing UI log entries.\n */\nclass UILogTransport extends BaseTransport<any> {\n #callback: (logEntry: any) => void;\n\n constructor(callback: (logEntry: any) => void) {\n // Pass a dummy logger to BaseTransport\n super({ logger: { log: () => {} }, id: 'ui-logger-transport', enabled: true });\n this.#callback = callback;\n }\n\n shipToLogger(params: any) {\n const logEntry = {\n timestamp: new Date().toISOString(),\n level: params.logLevel,\n message: params.messages.join(' '),\n data: params.data,\n hasData: params.hasData,\n };\n this.#callback(logEntry);\n return [];\n }\n}\n\nexport interface UILoggerConfig {\n enableConsoleDebug?: boolean;\n customTransport?: any;\n}\n\nexport function createUILogger(config: UILoggerConfig = {}): UILogger {\n // By default, just print to console as JSON\n const defaultCallback = (logEntry: any) => {\n console.log(JSON.stringify(logEntry));\n };\n const transport = config.customTransport || new UILogTransport(defaultCallback);\n const logger = new LogLayer({\n transport,\n consoleDebug: config.enableConsoleDebug ?? false,\n });\n logger.setLevel('debug');\n return new UILogger(logger);\n}\n\nexport function createUILoggerWithCallback(callback: (logEntry: any) => void, config: UILoggerConfig = {}): UILogger {\n const transport = new UILogTransport(callback);\n const logger = new LogLayer({\n transport,\n consoleDebug: config.enableConsoleDebug ?? false,\n });\n logger.setLevel('debug');\n return new UILogger(logger);\n}\n"]}
@@ -0,0 +1,43 @@
1
+ import type { ILogLayer } from 'loglayer';
2
+ /**
3
+ * UI Logger: Command-Level Logging for UI Display
4
+ *
5
+ * This module provides a specialized logger for capturing command-level information
6
+ * that can be easily parsed and displayed in a UI. It captures details about
7
+ * commands, data sent, data expected, and data received in a structured format.
8
+ *
9
+ * Purpose:
10
+ * - Captures command-level information for UI display
11
+ * - Provides structured JSON logging for easy parsing
12
+ * - Tracks command execution details including timing
13
+ * - Supports protocol debugging in UI environments
14
+ * - Maintains separation from debug logging
15
+ *
16
+ * Design Rationale:
17
+ * - Single log entry per command provides clean UI display
18
+ * - JSON structure enables easy parsing and filtering
19
+ * - Command-level granularity is appropriate for UI debugging
20
+ * - Structured data supports rich UI representations
21
+ * - Separate from debug logging avoids UI noise
22
+ */
23
+ /**
24
+ * Generic protocol step interface for UI logging
25
+ */
26
+ export interface UIProtocolStep {
27
+ [key: string]: any;
28
+ }
29
+ /**
30
+ * UI Logger for capturing command-level information for UI display
31
+ */
32
+ export declare class UILogger {
33
+ private logger;
34
+ private commandStartTimes;
35
+ constructor(logger: ILogLayer);
36
+ startCommand(stepIndex: number, totalSteps: number, operation: string, step: UIProtocolStep): void;
37
+ logCommandSuccess(stepIndex: number, totalSteps: number, operation: string, step: UIProtocolStep, context: any): void;
38
+ logCommandFailure(stepIndex: number, totalSteps: number, operation: string, step: UIProtocolStep, error: Error, context: any): void;
39
+ private getCommandType;
40
+ private getStepDescription;
41
+ private getExpectedData;
42
+ }
43
+ //# sourceMappingURL=ui-logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui-logger.d.ts","sourceRoot":"","sources":["../../src/utils/ui-logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAE1C;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED;;GAEG;AACH,qBAAa,QAAQ;IACnB,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,iBAAiB,CAA6B;gBAE1C,MAAM,EAAE,SAAS;IAI7B,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,GAAG,IAAI;IAqBlG,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,GAAG,GAAG,IAAI;IA4CrH,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,GAAG,IAAI;IAkCnI,OAAO,CAAC,cAAc;IAUtB,OAAO,CAAC,kBAAkB;IAS1B,OAAO,CAAC,eAAe;CAaxB"}
@@ -0,0 +1,139 @@
1
+ /**
2
+ * UI Logger for capturing command-level information for UI display
3
+ */
4
+ export class UILogger {
5
+ logger;
6
+ commandStartTimes = new Map();
7
+ constructor(logger) {
8
+ this.logger = logger;
9
+ }
10
+ startCommand(stepIndex, totalSteps, operation, step) {
11
+ const commandId = `${operation}-${stepIndex}`;
12
+ const startTime = Date.now();
13
+ this.commandStartTimes.set(commandId, startTime);
14
+ const commandType = this.getCommandType(step);
15
+ const description = this.getStepDescription(step);
16
+ this.logger
17
+ .withMetadata({
18
+ commandId,
19
+ commandType,
20
+ description,
21
+ stepIndex,
22
+ totalSteps,
23
+ operation,
24
+ startTime,
25
+ })
26
+ .info('Command started');
27
+ }
28
+ logCommandSuccess(stepIndex, totalSteps, operation, step, context) {
29
+ const commandId = `${operation}-${stepIndex}`;
30
+ const startTime = this.commandStartTimes.get(commandId) || Date.now();
31
+ const endTime = Date.now();
32
+ const duration = endTime - startTime;
33
+ const commandType = this.getCommandType(step);
34
+ const description = this.getStepDescription(step);
35
+ // Extract sent and received data from context
36
+ const dataSent = context.variables?.get('lastSentData');
37
+ const dataReceived = context.variables?.get('lastReceivedData');
38
+ const dataExpected = this.getExpectedData(step);
39
+ // For readSegment, also extract chunk logs if present
40
+ let dataChunks = undefined;
41
+ if (commandType === 'readSegment') {
42
+ dataChunks = context.variables?.get('lastReadSegmentChunks');
43
+ // For readSegment, we don't need the flattened dataSent/dataReceived since we have dataChunks
44
+ }
45
+ // Convert dataSent and dataReceived to byte array format if they exist
46
+ const dataSentArray = dataSent ? Array.from(dataSent) : undefined;
47
+ const dataReceivedArray = dataReceived ? Array.from(dataReceived) : undefined;
48
+ this.logger
49
+ .withMetadata({
50
+ commandType,
51
+ description,
52
+ dataSent: dataSentArray,
53
+ dataExpected,
54
+ dataReceived: dataReceivedArray,
55
+ dataChunks,
56
+ success: true,
57
+ startTime,
58
+ endTime,
59
+ duration,
60
+ stepIndex,
61
+ totalSteps,
62
+ operation,
63
+ })
64
+ .info('Command completed successfully');
65
+ }
66
+ logCommandFailure(stepIndex, totalSteps, operation, step, error, context) {
67
+ const commandId = `${operation}-${stepIndex}`;
68
+ const startTime = this.commandStartTimes.get(commandId) || Date.now();
69
+ const endTime = Date.now();
70
+ const duration = endTime - startTime;
71
+ const commandType = this.getCommandType(step);
72
+ const description = this.getStepDescription(step);
73
+ // Extract sent data from context
74
+ const dataSent = context.variables?.get('lastSentData');
75
+ const dataExpected = this.getExpectedData(step);
76
+ // Convert dataSent to byte array format if it exists
77
+ const dataSentArray = dataSent ? Array.from(dataSent) : undefined;
78
+ this.logger
79
+ .withMetadata({
80
+ commandType,
81
+ description,
82
+ dataSent: dataSentArray,
83
+ dataExpected,
84
+ error: error.message,
85
+ success: false,
86
+ startTime,
87
+ endTime,
88
+ duration,
89
+ stepIndex,
90
+ totalSteps,
91
+ operation,
92
+ })
93
+ .info('Command failed');
94
+ }
95
+ getCommandType(step) {
96
+ if ('sendReceive' in step)
97
+ return 'sendReceive';
98
+ if ('send' in step)
99
+ return 'send';
100
+ if ('receive' in step)
101
+ return 'receive';
102
+ if ('readSegment' in step)
103
+ return 'readSegment';
104
+ if ('writeSegment' in step)
105
+ return 'writeSegment';
106
+ if ('setVariable' in step)
107
+ return 'setVariable';
108
+ return 'unknown';
109
+ }
110
+ getStepDescription(step) {
111
+ if ('sendReceive' in step && step.sendReceive.description)
112
+ return step.sendReceive.description;
113
+ if ('send' in step && step.send.description)
114
+ return step.send.description;
115
+ if ('receive' in step && step.receive.description)
116
+ return step.receive.description;
117
+ if ('readSegment' in step && step.readSegment.description)
118
+ return step.readSegment.description;
119
+ if ('writeSegment' in step && step.writeSegment.description)
120
+ return step.writeSegment.description;
121
+ return 'No description';
122
+ }
123
+ getExpectedData(step) {
124
+ if ('sendReceive' in step && step.sendReceive.receive)
125
+ return step.sendReceive.receive;
126
+ if ('receive' in step)
127
+ return step.receive;
128
+ if ('readSegment' in step) {
129
+ // For readSegment, return both start and end chunk receive patterns
130
+ return {
131
+ startChunk: step.readSegment.startChunk.receive,
132
+ endChunk: step.readSegment.endChunk.receive,
133
+ type: 'readSegment',
134
+ };
135
+ }
136
+ return undefined;
137
+ }
138
+ }
139
+ //# sourceMappingURL=ui-logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui-logger.js","sourceRoot":"","sources":["../../src/utils/ui-logger.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ILogLayer } from 'loglayer';\n\n/**\n * UI Logger: Command-Level Logging for UI Display\n *\n * This module provides a specialized logger for capturing command-level information\n * that can be easily parsed and displayed in a UI. It captures details about\n * commands, data sent, data expected, and data received in a structured format.\n *\n * Purpose:\n * - Captures command-level information for UI display\n * - Provides structured JSON logging for easy parsing\n * - Tracks command execution details including timing\n * - Supports protocol debugging in UI environments\n * - Maintains separation from debug logging\n *\n * Design Rationale:\n * - Single log entry per command provides clean UI display\n * - JSON structure enables easy parsing and filtering\n * - Command-level granularity is appropriate for UI debugging\n * - Structured data supports rich UI representations\n * - Separate from debug logging avoids UI noise\n */\n\n/**\n * Generic protocol step interface for UI logging\n */\nexport interface UIProtocolStep {\n [key: string]: any;\n}\n\n/**\n * UI Logger for capturing command-level information for UI display\n */\nexport class UILogger {\n private logger: ILogLayer;\n private commandStartTimes = new Map<string, number>();\n\n constructor(logger: ILogLayer) {\n this.logger = logger;\n }\n\n startCommand(stepIndex: number, totalSteps: number, operation: string, step: UIProtocolStep): void {\n const commandId = `${operation}-${stepIndex}`;\n const startTime = Date.now();\n this.commandStartTimes.set(commandId, startTime);\n\n const commandType = this.getCommandType(step);\n const description = this.getStepDescription(step);\n\n this.logger\n .withMetadata({\n commandId,\n commandType,\n description,\n stepIndex,\n totalSteps,\n operation,\n startTime,\n })\n .info('Command started');\n }\n\n logCommandSuccess(stepIndex: number, totalSteps: number, operation: string, step: UIProtocolStep, context: any): void {\n const commandId = `${operation}-${stepIndex}`;\n const startTime = this.commandStartTimes.get(commandId) || Date.now();\n const endTime = Date.now();\n const duration = endTime - startTime;\n\n const commandType = this.getCommandType(step);\n const description = this.getStepDescription(step);\n\n // Extract sent and received data from context\n const dataSent = context.variables?.get('lastSentData');\n const dataReceived = context.variables?.get('lastReceivedData');\n const dataExpected = this.getExpectedData(step);\n\n // For readSegment, also extract chunk logs if present\n let dataChunks = undefined;\n if (commandType === 'readSegment') {\n dataChunks = context.variables?.get('lastReadSegmentChunks');\n // For readSegment, we don't need the flattened dataSent/dataReceived since we have dataChunks\n }\n\n // Convert dataSent and dataReceived to byte array format if they exist\n const dataSentArray = dataSent ? Array.from(dataSent) : undefined;\n const dataReceivedArray = dataReceived ? Array.from(dataReceived) : undefined;\n\n this.logger\n .withMetadata({\n commandType,\n description,\n dataSent: dataSentArray,\n dataExpected,\n dataReceived: dataReceivedArray,\n dataChunks,\n success: true,\n startTime,\n endTime,\n duration,\n stepIndex,\n totalSteps,\n operation,\n })\n .info('Command completed successfully');\n }\n\n logCommandFailure(stepIndex: number, totalSteps: number, operation: string, step: UIProtocolStep, error: Error, context: any): void {\n const commandId = `${operation}-${stepIndex}`;\n const startTime = this.commandStartTimes.get(commandId) || Date.now();\n const endTime = Date.now();\n const duration = endTime - startTime;\n\n const commandType = this.getCommandType(step);\n const description = this.getStepDescription(step);\n\n // Extract sent data from context\n const dataSent = context.variables?.get('lastSentData');\n const dataExpected = this.getExpectedData(step);\n\n // Convert dataSent to byte array format if it exists\n const dataSentArray = dataSent ? Array.from(dataSent) : undefined;\n\n this.logger\n .withMetadata({\n commandType,\n description,\n dataSent: dataSentArray,\n dataExpected,\n error: error.message,\n success: false,\n startTime,\n endTime,\n duration,\n stepIndex,\n totalSteps,\n operation,\n })\n .info('Command failed');\n }\n\n private getCommandType(step: UIProtocolStep): string {\n if ('sendReceive' in step) return 'sendReceive';\n if ('send' in step) return 'send';\n if ('receive' in step) return 'receive';\n if ('readSegment' in step) return 'readSegment';\n if ('writeSegment' in step) return 'writeSegment';\n if ('setVariable' in step) return 'setVariable';\n return 'unknown';\n }\n\n private getStepDescription(step: UIProtocolStep): string {\n if ('sendReceive' in step && step.sendReceive.description) return step.sendReceive.description;\n if ('send' in step && step.send.description) return step.send.description;\n if ('receive' in step && step.receive.description) return step.receive.description;\n if ('readSegment' in step && step.readSegment.description) return step.readSegment.description;\n if ('writeSegment' in step && step.writeSegment.description) return step.writeSegment.description;\n return 'No description';\n }\n\n private getExpectedData(step: UIProtocolStep): any {\n if ('sendReceive' in step && step.sendReceive.receive) return step.sendReceive.receive;\n if ('receive' in step) return step.receive;\n if ('readSegment' in step) {\n // For readSegment, return both start and end chunk receive patterns\n return {\n startChunk: step.readSegment.startChunk.receive,\n endChunk: step.readSegment.endChunk.receive,\n type: 'readSegment',\n };\n }\n return undefined;\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@springfield/ham-radio-utils",
3
- "version": "2.0.1",
3
+ "version": "2.1.0",
4
4
  "author": "Bryan Hunt",
5
5
  "packageManager": "yarn@4.9.2",
6
6
  "readme": "README.md",
@@ -52,7 +52,7 @@
52
52
  "dependencies": {
53
53
  "@springfield/ham-radio-api": "^14.0.0",
54
54
  "fishery": "^2.3.1",
55
- "loglayer": "^6.4.2",
55
+ "loglayer": "^6.4.3",
56
56
  "sprintf-js": "^1.1.3"
57
57
  },
58
58
  "devDependencies": {
@@ -60,7 +60,7 @@
60
60
  "@commitlint/config-conventional": "19.8.1",
61
61
  "@semantic-release/changelog": "6.0.3",
62
62
  "@semantic-release/git": "10.0.1",
63
- "@semantic-release/gitlab": "13.2.5",
63
+ "@semantic-release/gitlab": "13.2.6",
64
64
  "@types/sprintf-js": "1.1.4",
65
65
  "husky": "9.1.7",
66
66
  "oxlint": "1.1.0",
package/src/index.ts CHANGED
@@ -10,3 +10,5 @@ export * from './utils/memory-channel-utils.js';
10
10
  export * from './utils/memory-data-utils.js';
11
11
  export * from './utils/number-to-bcd.js';
12
12
  export * from './utils/to-hex-words.js';
13
+ export * from './utils/ui-logger.js';
14
+ export * from './utils/ui-logger-factory.js';
@@ -0,0 +1,57 @@
1
+ import { LogLayer } from 'loglayer';
2
+ import { BaseTransport } from '@loglayer/transport';
3
+ import { UILogger } from './ui-logger.js';
4
+
5
+ /**
6
+ * Custom LogLayer transport for capturing UI log entries.
7
+ */
8
+ class UILogTransport extends BaseTransport<any> {
9
+ #callback: (logEntry: any) => void;
10
+
11
+ constructor(callback: (logEntry: any) => void) {
12
+ // Pass a dummy logger to BaseTransport
13
+ super({ logger: { log: () => {} }, id: 'ui-logger-transport', enabled: true });
14
+ this.#callback = callback;
15
+ }
16
+
17
+ shipToLogger(params: any) {
18
+ const logEntry = {
19
+ timestamp: new Date().toISOString(),
20
+ level: params.logLevel,
21
+ message: params.messages.join(' '),
22
+ data: params.data,
23
+ hasData: params.hasData,
24
+ };
25
+ this.#callback(logEntry);
26
+ return [];
27
+ }
28
+ }
29
+
30
+ export interface UILoggerConfig {
31
+ enableConsoleDebug?: boolean;
32
+ customTransport?: any;
33
+ }
34
+
35
+ export function createUILogger(config: UILoggerConfig = {}): UILogger {
36
+ // By default, just print to console as JSON
37
+ const defaultCallback = (logEntry: any) => {
38
+ console.log(JSON.stringify(logEntry));
39
+ };
40
+ const transport = config.customTransport || new UILogTransport(defaultCallback);
41
+ const logger = new LogLayer({
42
+ transport,
43
+ consoleDebug: config.enableConsoleDebug ?? false,
44
+ });
45
+ logger.setLevel('debug');
46
+ return new UILogger(logger);
47
+ }
48
+
49
+ export function createUILoggerWithCallback(callback: (logEntry: any) => void, config: UILoggerConfig = {}): UILogger {
50
+ const transport = new UILogTransport(callback);
51
+ const logger = new LogLayer({
52
+ transport,
53
+ consoleDebug: config.enableConsoleDebug ?? false,
54
+ });
55
+ logger.setLevel('debug');
56
+ return new UILogger(logger);
57
+ }
@@ -0,0 +1,174 @@
1
+ import type { ILogLayer } from 'loglayer';
2
+
3
+ /**
4
+ * UI Logger: Command-Level Logging for UI Display
5
+ *
6
+ * This module provides a specialized logger for capturing command-level information
7
+ * that can be easily parsed and displayed in a UI. It captures details about
8
+ * commands, data sent, data expected, and data received in a structured format.
9
+ *
10
+ * Purpose:
11
+ * - Captures command-level information for UI display
12
+ * - Provides structured JSON logging for easy parsing
13
+ * - Tracks command execution details including timing
14
+ * - Supports protocol debugging in UI environments
15
+ * - Maintains separation from debug logging
16
+ *
17
+ * Design Rationale:
18
+ * - Single log entry per command provides clean UI display
19
+ * - JSON structure enables easy parsing and filtering
20
+ * - Command-level granularity is appropriate for UI debugging
21
+ * - Structured data supports rich UI representations
22
+ * - Separate from debug logging avoids UI noise
23
+ */
24
+
25
+ /**
26
+ * Generic protocol step interface for UI logging
27
+ */
28
+ export interface UIProtocolStep {
29
+ [key: string]: any;
30
+ }
31
+
32
+ /**
33
+ * UI Logger for capturing command-level information for UI display
34
+ */
35
+ export class UILogger {
36
+ private logger: ILogLayer;
37
+ private commandStartTimes = new Map<string, number>();
38
+
39
+ constructor(logger: ILogLayer) {
40
+ this.logger = logger;
41
+ }
42
+
43
+ startCommand(stepIndex: number, totalSteps: number, operation: string, step: UIProtocolStep): void {
44
+ const commandId = `${operation}-${stepIndex}`;
45
+ const startTime = Date.now();
46
+ this.commandStartTimes.set(commandId, startTime);
47
+
48
+ const commandType = this.getCommandType(step);
49
+ const description = this.getStepDescription(step);
50
+
51
+ this.logger
52
+ .withMetadata({
53
+ commandId,
54
+ commandType,
55
+ description,
56
+ stepIndex,
57
+ totalSteps,
58
+ operation,
59
+ startTime,
60
+ })
61
+ .info('Command started');
62
+ }
63
+
64
+ logCommandSuccess(stepIndex: number, totalSteps: number, operation: string, step: UIProtocolStep, context: any): void {
65
+ const commandId = `${operation}-${stepIndex}`;
66
+ const startTime = this.commandStartTimes.get(commandId) || Date.now();
67
+ const endTime = Date.now();
68
+ const duration = endTime - startTime;
69
+
70
+ const commandType = this.getCommandType(step);
71
+ const description = this.getStepDescription(step);
72
+
73
+ // Extract sent and received data from context
74
+ const dataSent = context.variables?.get('lastSentData');
75
+ const dataReceived = context.variables?.get('lastReceivedData');
76
+ const dataExpected = this.getExpectedData(step);
77
+
78
+ // For readSegment, also extract chunk logs if present
79
+ let dataChunks = undefined;
80
+ if (commandType === 'readSegment') {
81
+ dataChunks = context.variables?.get('lastReadSegmentChunks');
82
+ // For readSegment, we don't need the flattened dataSent/dataReceived since we have dataChunks
83
+ }
84
+
85
+ // Convert dataSent and dataReceived to byte array format if they exist
86
+ const dataSentArray = dataSent ? Array.from(dataSent) : undefined;
87
+ const dataReceivedArray = dataReceived ? Array.from(dataReceived) : undefined;
88
+
89
+ this.logger
90
+ .withMetadata({
91
+ commandType,
92
+ description,
93
+ dataSent: dataSentArray,
94
+ dataExpected,
95
+ dataReceived: dataReceivedArray,
96
+ dataChunks,
97
+ success: true,
98
+ startTime,
99
+ endTime,
100
+ duration,
101
+ stepIndex,
102
+ totalSteps,
103
+ operation,
104
+ })
105
+ .info('Command completed successfully');
106
+ }
107
+
108
+ logCommandFailure(stepIndex: number, totalSteps: number, operation: string, step: UIProtocolStep, error: Error, context: any): void {
109
+ const commandId = `${operation}-${stepIndex}`;
110
+ const startTime = this.commandStartTimes.get(commandId) || Date.now();
111
+ const endTime = Date.now();
112
+ const duration = endTime - startTime;
113
+
114
+ const commandType = this.getCommandType(step);
115
+ const description = this.getStepDescription(step);
116
+
117
+ // Extract sent data from context
118
+ const dataSent = context.variables?.get('lastSentData');
119
+ const dataExpected = this.getExpectedData(step);
120
+
121
+ // Convert dataSent to byte array format if it exists
122
+ const dataSentArray = dataSent ? Array.from(dataSent) : undefined;
123
+
124
+ this.logger
125
+ .withMetadata({
126
+ commandType,
127
+ description,
128
+ dataSent: dataSentArray,
129
+ dataExpected,
130
+ error: error.message,
131
+ success: false,
132
+ startTime,
133
+ endTime,
134
+ duration,
135
+ stepIndex,
136
+ totalSteps,
137
+ operation,
138
+ })
139
+ .info('Command failed');
140
+ }
141
+
142
+ private getCommandType(step: UIProtocolStep): string {
143
+ if ('sendReceive' in step) return 'sendReceive';
144
+ if ('send' in step) return 'send';
145
+ if ('receive' in step) return 'receive';
146
+ if ('readSegment' in step) return 'readSegment';
147
+ if ('writeSegment' in step) return 'writeSegment';
148
+ if ('setVariable' in step) return 'setVariable';
149
+ return 'unknown';
150
+ }
151
+
152
+ private getStepDescription(step: UIProtocolStep): string {
153
+ if ('sendReceive' in step && step.sendReceive.description) return step.sendReceive.description;
154
+ if ('send' in step && step.send.description) return step.send.description;
155
+ if ('receive' in step && step.receive.description) return step.receive.description;
156
+ if ('readSegment' in step && step.readSegment.description) return step.readSegment.description;
157
+ if ('writeSegment' in step && step.writeSegment.description) return step.writeSegment.description;
158
+ return 'No description';
159
+ }
160
+
161
+ private getExpectedData(step: UIProtocolStep): any {
162
+ if ('sendReceive' in step && step.sendReceive.receive) return step.sendReceive.receive;
163
+ if ('receive' in step) return step.receive;
164
+ if ('readSegment' in step) {
165
+ // For readSegment, return both start and end chunk receive patterns
166
+ return {
167
+ startChunk: step.readSegment.startChunk.receive,
168
+ endChunk: step.readSegment.endChunk.receive,
169
+ type: 'readSegment',
170
+ };
171
+ }
172
+ return undefined;
173
+ }
174
+ }