taro-bluetooth-print 2.8.4 → 2.9.1

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 (86) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +53 -4
  3. package/dist/index.cjs.js +1 -1
  4. package/dist/index.es.js +1 -1
  5. package/dist/index.umd.js +1 -1
  6. package/dist/types/core/BluetoothPrinter.d.ts +21 -4
  7. package/dist/types/core/di/Container.d.ts +84 -0
  8. package/dist/types/core/di/Tokens.d.ts +29 -0
  9. package/dist/types/core/di/index.d.ts +3 -0
  10. package/dist/types/core/event/EventBus.d.ts +66 -0
  11. package/dist/types/core/event/index.d.ts +2 -0
  12. package/dist/types/core/index.d.ts +5 -4
  13. package/dist/types/core/plugin/PluginManager.d.ts +64 -0
  14. package/dist/types/core/plugin/index.d.ts +2 -0
  15. package/dist/types/device/MultiPrinterManager.d.ts +2 -0
  16. package/dist/types/errors/CommandBuildError.d.ts +40 -0
  17. package/dist/types/errors/ConnectionError.d.ts +45 -0
  18. package/dist/types/errors/PrintJobError.d.ts +42 -0
  19. package/dist/types/errors/index.d.ts +9 -0
  20. package/dist/types/factory/PrinterFactory.d.ts +108 -0
  21. package/dist/types/factory/di-factory.d.ts +52 -0
  22. package/dist/types/factory/index.d.ts +6 -0
  23. package/dist/types/index.d.ts +9 -1
  24. package/dist/types/providers/ServiceProvider.d.ts +56 -0
  25. package/dist/types/providers/index.d.ts +2 -0
  26. package/dist/types/services/interfaces/ICommandBuilder.d.ts +123 -0
  27. package/dist/types/services/interfaces/IConnectionManager.d.ts +49 -0
  28. package/dist/types/services/interfaces/IPrintJobManager.d.ts +67 -0
  29. package/dist/types/services/interfaces/index.d.ts +6 -233
  30. package/dist/types/template/TemplateEngine.d.ts +24 -68
  31. package/dist/types/template/engines/TemplateRenderer.d.ts +71 -0
  32. package/dist/types/template/parsers/TemplateParser.d.ts +23 -0
  33. package/dist/types/utils/index.d.ts +8 -0
  34. package/dist/types/utils/logger.d.ts +4 -3
  35. package/dist/types/utils/outputLimiter.d.ts +87 -0
  36. package/dist/types/utils/validation.d.ts +11 -309
  37. package/dist/types/utils/validators/array.d.ts +19 -0
  38. package/dist/types/utils/validators/buffer.d.ts +18 -0
  39. package/dist/types/utils/validators/chain.d.ts +31 -0
  40. package/dist/types/utils/validators/common.d.ts +22 -0
  41. package/dist/types/utils/validators/number.d.ts +20 -0
  42. package/dist/types/utils/validators/object.d.ts +24 -0
  43. package/dist/types/utils/validators/printer.d.ts +40 -0
  44. package/dist/types/utils/validators/types.d.ts +125 -0
  45. package/dist/types/utils/validators/uuid.d.ts +23 -0
  46. package/package.json +1 -1
  47. package/src/core/BluetoothPrinter.ts +48 -50
  48. package/src/core/di/Container.ts +330 -0
  49. package/src/core/di/Tokens.ts +45 -0
  50. package/src/core/di/index.ts +3 -0
  51. package/src/core/event/EventBus.ts +251 -0
  52. package/src/core/event/index.ts +2 -0
  53. package/src/core/index.ts +10 -4
  54. package/src/core/plugin/PluginManager.ts +161 -0
  55. package/src/core/plugin/index.ts +2 -0
  56. package/src/device/MultiPrinterManager.ts +15 -6
  57. package/src/errors/CommandBuildError.ts +72 -0
  58. package/src/errors/ConnectionError.ts +78 -0
  59. package/src/errors/PrintJobError.ts +75 -0
  60. package/src/errors/index.ts +10 -0
  61. package/src/factory/PrinterFactory.ts +139 -0
  62. package/src/factory/di-factory.ts +61 -0
  63. package/src/factory/index.ts +12 -0
  64. package/src/index.ts +61 -1
  65. package/src/providers/ServiceProvider.ts +213 -0
  66. package/src/providers/index.ts +2 -0
  67. package/src/services/interfaces/ICommandBuilder.ts +145 -0
  68. package/src/services/interfaces/IConnectionManager.ts +58 -0
  69. package/src/services/interfaces/IPrintJobManager.ts +83 -0
  70. package/src/services/interfaces/index.ts +5 -265
  71. package/src/template/TemplateEngine.ts +27 -792
  72. package/src/template/engines/TemplateRenderer.ts +762 -0
  73. package/src/template/parsers/TemplateParser.ts +94 -0
  74. package/src/utils/index.ts +9 -0
  75. package/src/utils/logger.ts +17 -4
  76. package/src/utils/outputLimiter.ts +227 -0
  77. package/src/utils/validation.ts +21 -1138
  78. package/src/utils/validators/array.ts +95 -0
  79. package/src/utils/validators/buffer.ts +81 -0
  80. package/src/utils/validators/chain.ts +181 -0
  81. package/src/utils/validators/common.ts +216 -0
  82. package/src/utils/validators/number.ts +101 -0
  83. package/src/utils/validators/object.ts +63 -0
  84. package/src/utils/validators/printer.ts +294 -0
  85. package/src/utils/validators/types.ts +105 -0
  86. package/src/utils/validators/uuid.ts +49 -0
@@ -0,0 +1,45 @@
1
+ /**
2
+ * 服务标识符(Tokens)
3
+ * 用于依赖注入的常量定义
4
+ */
5
+
6
+ // 适配器相关
7
+ export const ADAPTER_TOKEN = Symbol.for('Adapter');
8
+ export const ADAPTER_FACTORY_TOKEN = Symbol.for('AdapterFactory');
9
+
10
+ // 驱动相关
11
+ export const DRIVER_TOKEN = Symbol.for('Driver');
12
+ export const DRIVER_FACTORY_TOKEN = Symbol.for('DriverFactory');
13
+
14
+ // 服务相关
15
+ export const DEVICE_MANAGER_TOKEN = Symbol.for('DeviceManager');
16
+ export const CONNECTION_MANAGER_TOKEN = Symbol.for('ConnectionManager');
17
+ export const PRINT_JOB_MANAGER_TOKEN = Symbol.for('PrintJobManager');
18
+ export const PRINT_QUEUE_TOKEN = Symbol.for('PrintQueue');
19
+ export const OFFLINE_CACHE_TOKEN = Symbol.for('OfflineCache');
20
+ export const CONFIG_MANAGER_TOKEN = Symbol.for('ConfigManager');
21
+
22
+ // 新增服务令牌
23
+ export const COMMAND_BUILDER_TOKEN = Symbol.for('CommandBuilder');
24
+ export const PRINTER_STATUS_TOKEN = Symbol.for('PrinterStatus');
25
+ export const PRINT_HISTORY_TOKEN = Symbol.for('PrintHistory');
26
+ export const PRINT_STATISTICS_TOKEN = Symbol.for('PrintStatistics');
27
+ export const CLOUD_PRINT_MANAGER_TOKEN = Symbol.for('CloudPrintManager');
28
+ export const SCHEDULED_RETRY_MANAGER_TOKEN = Symbol.for('ScheduledRetryManager');
29
+ export const BATCH_PRINT_MANAGER_TOKEN = Symbol.for('BatchPrintManager');
30
+
31
+ // 工具服务
32
+ export const LOGGER_TOKEN = Symbol.for('Logger');
33
+ export const ENCODING_SERVICE_TOKEN = Symbol.for('EncodingService');
34
+ export const IMAGE_PROCESSING_TOKEN = Symbol.for('ImageProcessing');
35
+ export const BARCODE_GENERATOR_TOKEN = Symbol.for('BarcodeGenerator');
36
+ export const TEMPLATE_ENGINE_TOKEN = Symbol.for('TemplateEngine');
37
+
38
+ // 事件系统
39
+ export const EVENT_BUS_TOKEN = Symbol.for('EventBus');
40
+
41
+ // 插件系统
42
+ export const PLUGIN_MANAGER_TOKEN = Symbol.for('PluginManager');
43
+
44
+ // 性能监控
45
+ export const PERFORMANCE_MONITOR_TOKEN = Symbol.for('PerformanceMonitor');
@@ -0,0 +1,3 @@
1
+ export { Container, rootContainer, injectable, inject } from './Container';
2
+ export type { ServiceProviderConfig, RegistrationOptions } from './Container';
3
+ export * from './Tokens';
@@ -0,0 +1,251 @@
1
+ /**
2
+ * 事件总线 - 提供强大的事件驱动能力
3
+ * 支持事件订阅、发布、一次性监听、异步处理等
4
+ */
5
+
6
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
+ export type EventHandler<T = any> = (payload: T) => void | Promise<void>;
8
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
9
+ export type EventFilter<T = any> = (payload: T) => boolean;
10
+
11
+ export interface EventSubscription {
12
+ unsubscribe(): void;
13
+ }
14
+
15
+ export interface EventOptions {
16
+ /** 优先级,数字越小优先级越高 */
17
+ priority?: number;
18
+ /** 过滤器 */
19
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
+ filter?: EventFilter<any>;
21
+ /** 是否只监听一次 */
22
+ once?: boolean;
23
+ /** 超时时间(毫秒) */
24
+ timeout?: number;
25
+ }
26
+
27
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
+ interface EventListener<T = any> {
29
+ handler: EventHandler<T>;
30
+ options: Required<Pick<EventOptions, 'priority'>> & Omit<EventOptions, 'priority'>;
31
+ id: string;
32
+ }
33
+
34
+ export class EventBus {
35
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
+ private listeners = new Map<string | symbol, EventListener<any>[]>();
37
+ private listenerIdCounter = 0;
38
+
39
+ /**
40
+ * 订阅事件
41
+ */
42
+ on<T>(
43
+ event: string | symbol,
44
+ handler: EventHandler<T>,
45
+ options: EventOptions = {}
46
+ ): EventSubscription {
47
+ const id = this.generateListenerId();
48
+ const listener: EventListener<T> = {
49
+ handler,
50
+ options: {
51
+ priority: options.priority ?? 100,
52
+ filter: options.filter,
53
+ once: options.once ?? false,
54
+ timeout: options.timeout,
55
+ },
56
+ id,
57
+ };
58
+
59
+ const eventKey = String(event);
60
+ if (!this.listeners.has(eventKey)) {
61
+ this.listeners.set(eventKey, []);
62
+ }
63
+
64
+ const listeners = this.listeners.get(eventKey)!;
65
+ listeners.push(listener);
66
+
67
+ // 按优先级排序
68
+ listeners.sort((a, b) => a.options.priority - b.options.priority);
69
+
70
+ return {
71
+ unsubscribe: () => {
72
+ this.off(event, handler);
73
+ },
74
+ };
75
+ }
76
+
77
+ /**
78
+ * 只监听一次
79
+ */
80
+ once<T>(
81
+ event: string | symbol,
82
+ handler: EventHandler<T>,
83
+ options: Omit<EventOptions, 'once'> = {}
84
+ ): EventSubscription {
85
+ return this.on(event, handler, { ...options, once: true });
86
+ }
87
+
88
+ /**
89
+ * 取消订阅
90
+ */
91
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
92
+ off<T>(event: string | symbol, handler: EventHandler<T>): void {
93
+ const eventKey = String(event);
94
+ const listeners = this.listeners.get(eventKey);
95
+ if (!listeners) return;
96
+
97
+ const index = listeners.findIndex(l => l.handler === handler);
98
+ if (index !== -1) {
99
+ listeners.splice(index, 1);
100
+ }
101
+
102
+ if (listeners.length === 0) {
103
+ this.listeners.delete(eventKey);
104
+ }
105
+ }
106
+
107
+ /**
108
+ * 发布事件
109
+ */
110
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
111
+ async emit<T>(event: string | symbol, payload: T): Promise<void> {
112
+ const eventKey = String(event);
113
+ const listeners = this.listeners.get(eventKey);
114
+ if (!listeners || listeners.length === 0) return;
115
+
116
+ const toRemove: number[] = [];
117
+
118
+ for (let i = 0; i < listeners.length; i++) {
119
+ const listener = listeners[i];
120
+ if (!listener) continue;
121
+
122
+ // 应用过滤器
123
+ if (listener.options.filter && !listener.options.filter(payload)) {
124
+ continue;
125
+ }
126
+
127
+ try {
128
+ if (listener.options.timeout) {
129
+ await this.executeWithTimeout(listener.handler, payload, listener.options.timeout);
130
+ } else {
131
+ await listener.handler(payload);
132
+ }
133
+ } catch (error) {
134
+ console.error(`Error in event handler for "${eventKey}":`, error);
135
+ }
136
+
137
+ // 标记一次性监听器
138
+ if (listener.options.once) {
139
+ toRemove.push(i);
140
+ }
141
+ }
142
+
143
+ // 移除一次性监听器
144
+ for (let i = toRemove.length - 1; i >= 0; i--) {
145
+ const index = toRemove[i];
146
+ if (index !== undefined) {
147
+ listeners.splice(index, 1);
148
+ }
149
+ }
150
+
151
+ if (listeners.length === 0) {
152
+ this.listeners.delete(eventKey);
153
+ }
154
+ }
155
+
156
+ /**
157
+ * 同步发布事件(不等待处理完成)
158
+ */
159
+ emitSync<T>(event: string | symbol, payload: T): void {
160
+ this.emit(event, payload).catch(error => {
161
+ console.error(`Error in async event handler for "${String(event)}":`, error);
162
+ });
163
+ }
164
+
165
+ /**
166
+ * 等待特定事件
167
+ */
168
+ waitFor<T>(
169
+ event: string | symbol,
170
+ timeout?: number,
171
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
172
+ _filter?: EventFilter<T>
173
+ ): Promise<T> {
174
+ return new Promise((resolve, reject) => {
175
+ const timer = timeout
176
+ ? setTimeout(() => {
177
+ subscription.unsubscribe();
178
+ reject(new Error(`Timeout waiting for event "${String(event)}"`));
179
+ }, timeout)
180
+ : null;
181
+
182
+ const subscription = this.once<T>(event, payload => {
183
+ if (timer) clearTimeout(timer);
184
+ resolve(payload);
185
+ });
186
+ });
187
+ }
188
+
189
+ /**
190
+ * 检查是否有监听器
191
+ */
192
+ hasListeners(event: string | symbol): boolean {
193
+ const listeners = this.listeners.get(String(event));
194
+ return !!listeners && listeners.length > 0;
195
+ }
196
+
197
+ /**
198
+ * 获取监听器数量
199
+ */
200
+ listenerCount(event: string | symbol): number {
201
+ const listeners = this.listeners.get(String(event));
202
+ return listeners?.length ?? 0;
203
+ }
204
+
205
+ /**
206
+ * 移除所有监听器
207
+ */
208
+ removeAllListeners(event?: string | symbol): void {
209
+ if (event) {
210
+ this.listeners.delete(String(event));
211
+ } else {
212
+ this.listeners.clear();
213
+ }
214
+ }
215
+
216
+ /**
217
+ * 获取所有事件名称
218
+ */
219
+ eventNames(): (string | symbol)[] {
220
+ return Array.from(this.listeners.keys());
221
+ }
222
+
223
+ private generateListenerId(): string {
224
+ return `listener_${++this.listenerIdCounter}`;
225
+ }
226
+
227
+ private executeWithTimeout<T>(
228
+ handler: EventHandler<T>,
229
+ payload: T,
230
+ timeout: number
231
+ ): Promise<void> {
232
+ return new Promise((resolve, reject) => {
233
+ const timer = setTimeout(() => {
234
+ reject(new Error(`Event handler timed out after ${timeout}ms`));
235
+ }, timeout);
236
+
237
+ Promise.resolve(handler(payload))
238
+ .then(() => {
239
+ clearTimeout(timer);
240
+ resolve();
241
+ })
242
+ .catch(error => {
243
+ clearTimeout(timer);
244
+ reject(error instanceof Error ? error : new Error(String(error)));
245
+ });
246
+ });
247
+ }
248
+ }
249
+
250
+ // 全局事件总线实例
251
+ export const globalEventBus = new EventBus();
@@ -0,0 +1,2 @@
1
+ export { EventBus, globalEventBus } from './EventBus';
2
+ export type { EventHandler, EventFilter, EventSubscription, EventOptions } from './EventBus';
package/src/core/index.ts CHANGED
@@ -1,7 +1,13 @@
1
1
  /**
2
- * Core Module - 核心模块
2
+ * 核心架构模块
3
+ * 提供依赖注入、事件总线、插件管理等核心能力
3
4
  */
4
5
 
5
- export { BluetoothPrinter } from './BluetoothPrinter';
6
- export type { PrinterEvents } from './BluetoothPrinter';
7
- export { EventEmitter } from './EventEmitter';
6
+ // 依赖注入
7
+ export * from './di';
8
+
9
+ // 事件总线
10
+ export * from './event';
11
+
12
+ // 插件管理
13
+ export * from './plugin';
@@ -0,0 +1,161 @@
1
+ import type { EventBus } from '../event/EventBus';
2
+ import type { Container } from '../di/Container';
3
+
4
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
5
+ export interface PluginContext {
6
+ /** 事件总线 */
7
+ eventBus: EventBus;
8
+ /** DI容器 */
9
+ container: Container;
10
+ /** 配置 */
11
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
12
+ config: Record<string, unknown>;
13
+ }
14
+
15
+ export interface Plugin {
16
+ /** 插件名称 */
17
+ name: string;
18
+ /** 插件版本 */
19
+ version: string;
20
+ /** 插件依赖 */
21
+ dependencies?: string[];
22
+ /** 安装插件 */
23
+ install(context: PluginContext): void | Promise<void>;
24
+ /** 卸载插件 */
25
+ uninstall?(context: PluginContext): void | Promise<void>;
26
+ }
27
+
28
+ export interface PluginRegistration {
29
+ plugin: Plugin;
30
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
31
+ config: Record<string, unknown>;
32
+ installed: boolean;
33
+ }
34
+
35
+ export class PluginManager {
36
+ private plugins = new Map<string, PluginRegistration>();
37
+ private context: PluginContext;
38
+
39
+ constructor(context: PluginContext) {
40
+ this.context = context;
41
+ }
42
+
43
+ /**
44
+ * 注册插件
45
+ */
46
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
47
+ register(plugin: Plugin, config: Record<string, unknown> = {}): void {
48
+ if (this.plugins.has(plugin.name)) {
49
+ throw new Error(`Plugin "${plugin.name}" is already registered`);
50
+ }
51
+
52
+ this.plugins.set(plugin.name, {
53
+ plugin,
54
+ config,
55
+ installed: false,
56
+ });
57
+ }
58
+
59
+ /**
60
+ * 安装插件
61
+ */
62
+ async install(pluginName: string): Promise<void> {
63
+ const registration = this.plugins.get(pluginName);
64
+ if (!registration) {
65
+ throw new Error(`Plugin "${pluginName}" not found`);
66
+ }
67
+
68
+ if (registration.installed) {
69
+ return;
70
+ }
71
+
72
+ // 检查依赖
73
+ if (registration.plugin.dependencies) {
74
+ for (const dep of registration.plugin.dependencies) {
75
+ if (!this.plugins.has(dep)) {
76
+ throw new Error(`Plugin "${pluginName}" depends on "${dep}" which is not registered`);
77
+ }
78
+ await this.install(dep);
79
+ }
80
+ }
81
+
82
+ // 安装插件
83
+ const context: PluginContext = {
84
+ ...this.context,
85
+ config: { ...this.context.config, ...registration.config },
86
+ };
87
+
88
+ await registration.plugin.install(context);
89
+ registration.installed = true;
90
+
91
+ // 发布插件安装事件
92
+ await this.context.eventBus.emit('plugin:installed', {
93
+ name: pluginName,
94
+ version: registration.plugin.version,
95
+ });
96
+ }
97
+
98
+ /**
99
+ * 卸载插件
100
+ */
101
+ async uninstall(pluginName: string): Promise<void> {
102
+ const registration = this.plugins.get(pluginName);
103
+ if (!registration || !registration.installed) {
104
+ return;
105
+ }
106
+
107
+ // 检查是否有其他插件依赖此插件
108
+ for (const [name, reg] of this.plugins) {
109
+ if (reg.installed && reg.plugin.dependencies?.includes(pluginName)) {
110
+ throw new Error(`Cannot uninstall "${pluginName}" because "${name}" depends on it`);
111
+ }
112
+ }
113
+
114
+ if (registration.plugin.uninstall) {
115
+ await registration.plugin.uninstall(this.context);
116
+ }
117
+
118
+ registration.installed = false;
119
+
120
+ await this.context.eventBus.emit('plugin:uninstalled', { name: pluginName });
121
+ }
122
+
123
+ /**
124
+ * 批量安装插件
125
+ */
126
+ async installAll(): Promise<void> {
127
+ for (const [name] of this.plugins) {
128
+ await this.install(name);
129
+ }
130
+ }
131
+
132
+ /**
133
+ * 获取插件
134
+ */
135
+ getPlugin(name: string): Plugin | undefined {
136
+ return this.plugins.get(name)?.plugin;
137
+ }
138
+
139
+ /**
140
+ * 检查插件是否已安装
141
+ */
142
+ isInstalled(name: string): boolean {
143
+ return this.plugins.get(name)?.installed ?? false;
144
+ }
145
+
146
+ /**
147
+ * 获取所有已安装的插件
148
+ */
149
+ getInstalledPlugins(): Plugin[] {
150
+ return Array.from(this.plugins.values())
151
+ .filter(r => r.installed)
152
+ .map(r => r.plugin);
153
+ }
154
+
155
+ /**
156
+ * 获取所有已注册的插件
157
+ */
158
+ getRegisteredPlugins(): Plugin[] {
159
+ return Array.from(this.plugins.values()).map(r => r.plugin);
160
+ }
161
+ }
@@ -0,0 +1,2 @@
1
+ export { PluginManager } from './PluginManager';
2
+ export type { Plugin, PluginContext, PluginRegistration } from './PluginManager';
@@ -44,6 +44,8 @@ export interface PrinterConnection {
44
44
  connectedAt: number;
45
45
  /** Last activity timestamp */
46
46
  lastActivity?: number;
47
+ /** Error handler reference (for cleanup) */
48
+ errorHandler?: (error: Error) => void;
47
49
  }
48
50
 
49
51
  /**
@@ -200,14 +202,15 @@ export class MultiPrinterManager {
200
202
  try {
201
203
  const printer = new BluetoothPrinter();
202
204
 
203
- // Set up error handler
204
- printer.on('error', error => {
205
- this.emit('printer-error', { printerId, error });
206
- });
207
-
208
- // Connect
205
+ // Connect first so we know it works before storing
209
206
  await printer.connect(actualDeviceId);
210
207
 
208
+ // Set up error handler and store reference for cleanup
209
+ const errorHandler = (error: Error) => {
210
+ this.emit('printer-error', { printerId, error });
211
+ };
212
+ printer.on('error', errorHandler);
213
+
211
214
  const connection: PrinterConnection = {
212
215
  printerId,
213
216
  deviceId: actualDeviceId,
@@ -215,6 +218,7 @@ export class MultiPrinterManager {
215
218
  printer,
216
219
  connectedAt: Date.now(),
217
220
  lastActivity: Date.now(),
221
+ errorHandler,
218
222
  };
219
223
 
220
224
  this.printers.set(printerId, connection);
@@ -242,6 +246,11 @@ export class MultiPrinterManager {
242
246
 
243
247
  this.logger.info(`Disconnecting printer "${printerId}"`);
244
248
 
249
+ // Remove error handler to prevent memory leak
250
+ if (connection.errorHandler) {
251
+ connection.printer.off('error', connection.errorHandler);
252
+ }
253
+
245
254
  try {
246
255
  await connection.printer.disconnect();
247
256
  } catch (error) {
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Command Build Error
3
+ *
4
+ * Specialized error for command building failures
5
+ */
6
+
7
+ import { BluetoothPrintError, ErrorCode } from './BluetoothError';
8
+
9
+ /**
10
+ * Command building error codes
11
+ */
12
+ export enum CommandBuildErrorCode {
13
+ /** Invalid configuration */
14
+ INVALID_CONFIG = 'INVALID_CONFIGURATION',
15
+ /** Encoding not supported */
16
+ ENCODING_NOT_SUPPORTED = 'ENCODING_NOT_SUPPORTED',
17
+ /** Invalid image data */
18
+ INVALID_IMAGE = 'INVALID_IMAGE_DATA',
19
+ /** Invalid QR code data */
20
+ INVALID_QR = 'INVALID_QR_DATA',
21
+ /** Driver error */
22
+ DRIVER_ERROR = 'DRIVER_ERROR',
23
+ }
24
+
25
+ /**
26
+ * CommandBuildError - Specialized error for command building failures
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * throw new CommandBuildError(
31
+ * CommandBuildErrorCode.ENCODING_NOT_SUPPORTED,
32
+ * 'Encoding "EUC-JP" is not supported',
33
+ * originalError
34
+ * );
35
+ * ```
36
+ */
37
+ export class CommandBuildError extends BluetoothPrintError {
38
+ constructor(
39
+ public readonly buildErrorCode: CommandBuildErrorCode,
40
+ message: string,
41
+ originalError?: Error
42
+ ) {
43
+ const baseCode = CommandBuildError._toBaseCode(buildErrorCode);
44
+ super(baseCode, message, originalError);
45
+ this.name = 'CommandBuildError';
46
+
47
+ if (Error.captureStackTrace) {
48
+ Error.captureStackTrace(this, CommandBuildError);
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Converts CommandBuildErrorCode to base ErrorCode
54
+ */
55
+ private static _toBaseCode(code: CommandBuildErrorCode): ErrorCode {
56
+ const mapping: Partial<Record<CommandBuildErrorCode, ErrorCode>> = {
57
+ [CommandBuildErrorCode.INVALID_CONFIG]: ErrorCode.INVALID_CONFIGURATION,
58
+ [CommandBuildErrorCode.ENCODING_NOT_SUPPORTED]: ErrorCode.ENCODING_NOT_SUPPORTED,
59
+ [CommandBuildErrorCode.INVALID_IMAGE]: ErrorCode.INVALID_IMAGE_DATA,
60
+ [CommandBuildErrorCode.INVALID_QR]: ErrorCode.INVALID_QR_DATA,
61
+ [CommandBuildErrorCode.DRIVER_ERROR]: ErrorCode.PRINT_JOB_FAILED,
62
+ };
63
+ return mapping[code] ?? ErrorCode.INVALID_CONFIGURATION;
64
+ }
65
+
66
+ /**
67
+ * Checks if an error is a command build error
68
+ */
69
+ static isCommandBuildError(error: unknown): error is CommandBuildError {
70
+ return error instanceof CommandBuildError;
71
+ }
72
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Connection Error
3
+ *
4
+ * Specialized error for connection-related failures
5
+ */
6
+
7
+ import { BluetoothPrintError, ErrorCode } from './BluetoothError';
8
+
9
+ /**
10
+ * Connection-related error codes
11
+ */
12
+ export enum ConnectionErrorCode {
13
+ /** Failed to establish connection */
14
+ FAILED = 'CONNECTION_FAILED',
15
+ /** Connection attempt timed out */
16
+ TIMEOUT = 'CONNECTION_TIMEOUT',
17
+ /** Device not found during discovery */
18
+ NOT_FOUND = 'DEVICE_NOT_FOUND',
19
+ /** Device disconnected unexpectedly */
20
+ DISCONNECTED = 'DEVICE_DISCONNECTED',
21
+ /** Bluetooth service not found on device */
22
+ SERVICE_NOT_FOUND = 'SERVICE_NOT_FOUND',
23
+ /** Bluetooth characteristic not found */
24
+ CHARACTERISTIC_NOT_FOUND = 'CHARACTERISTIC_NOT_FOUND',
25
+ /** Service discovery failed */
26
+ DISCOVERY_FAILED = 'SERVICE_DISCOVERY_FAILED',
27
+ /** Platform doesn't support Bluetooth */
28
+ PLATFORM_UNSUPPORTED = 'PLATFORM_NOT_SUPPORTED',
29
+ }
30
+
31
+ /**
32
+ * ConnectionError - Specialized error for connection-related failures
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * throw new ConnectionError(
37
+ * ConnectionErrorCode.TIMEOUT,
38
+ * 'Connection timed out after 30s',
39
+ * originalError
40
+ * );
41
+ * ```
42
+ */
43
+ export class ConnectionError extends BluetoothPrintError {
44
+ constructor(code: ConnectionErrorCode, message: string, originalError?: Error) {
45
+ // Map our codes to the base ErrorCode enum
46
+ const baseCode = ConnectionError._toBaseCode(code);
47
+ super(baseCode, message, originalError);
48
+ this.name = 'ConnectionError';
49
+
50
+ if (Error.captureStackTrace) {
51
+ Error.captureStackTrace(this, ConnectionError);
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Converts ConnectionErrorCode to base ErrorCode
57
+ */
58
+ private static _toBaseCode(code: ConnectionErrorCode): ErrorCode {
59
+ const mapping: Partial<Record<ConnectionErrorCode, ErrorCode>> = {
60
+ [ConnectionErrorCode.FAILED]: ErrorCode.CONNECTION_FAILED,
61
+ [ConnectionErrorCode.TIMEOUT]: ErrorCode.CONNECTION_TIMEOUT,
62
+ [ConnectionErrorCode.NOT_FOUND]: ErrorCode.DEVICE_NOT_FOUND,
63
+ [ConnectionErrorCode.DISCONNECTED]: ErrorCode.DEVICE_DISCONNECTED,
64
+ [ConnectionErrorCode.SERVICE_NOT_FOUND]: ErrorCode.SERVICE_NOT_FOUND,
65
+ [ConnectionErrorCode.CHARACTERISTIC_NOT_FOUND]: ErrorCode.CHARACTERISTIC_NOT_FOUND,
66
+ [ConnectionErrorCode.DISCOVERY_FAILED]: ErrorCode.SERVICE_DISCOVERY_FAILED,
67
+ [ConnectionErrorCode.PLATFORM_UNSUPPORTED]: ErrorCode.PLATFORM_NOT_SUPPORTED,
68
+ };
69
+ return mapping[code] ?? ErrorCode.CONNECTION_FAILED;
70
+ }
71
+
72
+ /**
73
+ * Checks if an error is a connection-related error
74
+ */
75
+ static isConnectionError(error: unknown): error is ConnectionError {
76
+ return error instanceof ConnectionError;
77
+ }
78
+ }