taro-bluetooth-print 2.9.0 → 2.9.2

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 (65) hide show
  1. package/CHANGELOG.md +16 -1
  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/di/Container.d.ts +84 -0
  7. package/dist/types/core/di/Tokens.d.ts +29 -0
  8. package/dist/types/core/di/index.d.ts +3 -0
  9. package/dist/types/core/event/EventBus.d.ts +66 -0
  10. package/dist/types/core/event/index.d.ts +2 -0
  11. package/dist/types/core/index.d.ts +5 -4
  12. package/dist/types/core/plugin/PluginManager.d.ts +64 -0
  13. package/dist/types/core/plugin/index.d.ts +2 -0
  14. package/dist/types/device/MultiPrinterManager.d.ts +2 -0
  15. package/dist/types/factory/di-factory.d.ts +52 -0
  16. package/dist/types/index.d.ts +5 -1
  17. package/dist/types/providers/ServiceProvider.d.ts +56 -0
  18. package/dist/types/providers/index.d.ts +2 -0
  19. package/dist/types/template/TemplateEngine.d.ts +24 -68
  20. package/dist/types/template/engines/TemplateRenderer.d.ts +71 -0
  21. package/dist/types/template/parsers/TemplateParser.d.ts +23 -0
  22. package/dist/types/utils/index.d.ts +8 -0
  23. package/dist/types/utils/logger.d.ts +4 -3
  24. package/dist/types/utils/outputLimiter.d.ts +87 -0
  25. package/dist/types/utils/validation.d.ts +11 -309
  26. package/dist/types/utils/validators/array.d.ts +19 -0
  27. package/dist/types/utils/validators/buffer.d.ts +18 -0
  28. package/dist/types/utils/validators/chain.d.ts +31 -0
  29. package/dist/types/utils/validators/common.d.ts +22 -0
  30. package/dist/types/utils/validators/number.d.ts +20 -0
  31. package/dist/types/utils/validators/object.d.ts +24 -0
  32. package/dist/types/utils/validators/printer.d.ts +40 -0
  33. package/dist/types/utils/validators/types.d.ts +125 -0
  34. package/dist/types/utils/validators/uuid.d.ts +23 -0
  35. package/package.json +1 -1
  36. package/src/core/BluetoothPrinter.ts +2 -1
  37. package/src/core/di/Container.ts +332 -0
  38. package/src/core/di/Tokens.ts +45 -0
  39. package/src/core/di/index.ts +3 -0
  40. package/src/core/event/EventBus.ts +251 -0
  41. package/src/core/event/index.ts +2 -0
  42. package/src/core/index.ts +10 -4
  43. package/src/core/plugin/PluginManager.ts +161 -0
  44. package/src/core/plugin/index.ts +2 -0
  45. package/src/device/MultiPrinterManager.ts +15 -6
  46. package/src/factory/di-factory.ts +61 -0
  47. package/src/index.ts +50 -1
  48. package/src/providers/ServiceProvider.ts +213 -0
  49. package/src/providers/index.ts +2 -0
  50. package/src/template/TemplateEngine.ts +27 -792
  51. package/src/template/engines/TemplateRenderer.ts +762 -0
  52. package/src/template/parsers/TemplateParser.ts +94 -0
  53. package/src/utils/index.ts +9 -0
  54. package/src/utils/logger.ts +17 -4
  55. package/src/utils/outputLimiter.ts +227 -0
  56. package/src/utils/validation.ts +21 -1138
  57. package/src/utils/validators/array.ts +95 -0
  58. package/src/utils/validators/buffer.ts +81 -0
  59. package/src/utils/validators/chain.ts +181 -0
  60. package/src/utils/validators/common.ts +216 -0
  61. package/src/utils/validators/number.ts +101 -0
  62. package/src/utils/validators/object.ts +63 -0
  63. package/src/utils/validators/printer.ts +294 -0
  64. package/src/utils/validators/types.ts +105 -0
  65. package/src/utils/validators/uuid.ts +49 -0
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Template Parser
3
+ *
4
+ * Handles template validation and data extraction.
5
+ * Provides variable substitution and nested value access.
6
+ */
7
+
8
+ import type { TemplateDefinition, ValidationResult } from '../TemplateEngine';
9
+
10
+ /**
11
+ * Template Parser class
12
+ * Handles template validation and data extraction
13
+ */
14
+ export class TemplateParser {
15
+ /**
16
+ * Validate template data
17
+ */
18
+ validate(template: TemplateDefinition, data: Record<string, unknown>): ValidationResult {
19
+ const errors: ValidationResult['errors'] = [];
20
+
21
+ for (const element of template.elements) {
22
+ if (element.type === 'variable') {
23
+ const value = this.getNestedValue(data, element.name);
24
+ if (value === undefined) {
25
+ errors.push({
26
+ field: element.name,
27
+ message: `Missing required variable: ${element.name}`,
28
+ code: 'MISSING_VARIABLE',
29
+ });
30
+ }
31
+ } else if (element.type === 'loop') {
32
+ const items = this.getNestedValue(data, element.items);
33
+ if (!Array.isArray(items)) {
34
+ errors.push({
35
+ field: element.items,
36
+ message: `Loop variable '${element.items}' must be an array`,
37
+ code: 'INVALID_LOOP_VARIABLE',
38
+ });
39
+ }
40
+ } else if (element.type === 'condition') {
41
+ // Condition validation is optional - conditions can reference missing data
42
+ }
43
+ }
44
+
45
+ return { valid: errors.length === 0, errors };
46
+ }
47
+
48
+ /**
49
+ * Substitute variables in a string
50
+ */
51
+ substituteVariables(template: string, data: Record<string, unknown>): string {
52
+ return template.replace(/\{\{(\w+(?:\.\w+)*)\}\}/g, (_, key: string) => {
53
+ const value = this.getNestedValue(data, key);
54
+ if (value === undefined) return '';
55
+ if (typeof value === 'object') return JSON.stringify(value);
56
+ // eslint-disable-next-line @typescript-eslint/no-base-to-string
57
+ return String(value);
58
+ });
59
+ }
60
+
61
+ /**
62
+ * Get nested value from object
63
+ */
64
+ getNestedValue(obj: Record<string, unknown>, path: string): unknown {
65
+ const keys = path.split('.');
66
+ let current: unknown = obj;
67
+
68
+ for (const key of keys) {
69
+ if (current === null || current === undefined) {
70
+ return undefined;
71
+ }
72
+ if (typeof current === 'object') {
73
+ current = (current as Record<string, unknown>)[key];
74
+ } else {
75
+ return undefined;
76
+ }
77
+ }
78
+
79
+ return current;
80
+ }
81
+
82
+ /**
83
+ * Format a value with optional format string
84
+ */
85
+ formatValue(value: unknown, format?: string): string {
86
+ if (format === 'currency' && typeof value === 'number') {
87
+ return `¥${value.toFixed(2)}`;
88
+ }
89
+ if (format === 'date' && value instanceof Date) {
90
+ return value.toLocaleDateString('zh-CN');
91
+ }
92
+ return String(value);
93
+ }
94
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * 工具函数集合
3
+ */
4
+
5
+ export * from './encoding';
6
+ export * from './image';
7
+ export * from './logger';
8
+ export * from './uuid';
9
+ export * from './outputLimiter';
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Logging utilities for debugging and monitoring
3
3
  */
4
+ import { truncateForLog, generateSummary } from './outputLimiter';
4
5
 
5
6
  /**
6
7
  * Log entry structure
@@ -46,6 +47,10 @@ export interface LoggerConfig {
46
47
  prefix: string;
47
48
  /** Custom log handler function */
48
49
  handler?: LogHandler;
50
+ /** 单条日志最大长度,防止输出被截断 */
51
+ maxOutputLength?: number;
52
+ /** 是否使用数据摘要模式(减少输出量) */
53
+ useSummaryMode?: boolean;
49
54
  }
50
55
 
51
56
  /**
@@ -74,6 +79,8 @@ export class Logger {
74
79
  private static config: LoggerConfig = {
75
80
  level: LogLevel.WARN,
76
81
  prefix: '[TaroBTPrint]',
82
+ maxOutputLength: 5000,
83
+ useSummaryMode: false,
77
84
  };
78
85
 
79
86
  /**
@@ -136,10 +143,16 @@ export class Logger {
136
143
 
137
144
  const prefix = this.formatPrefix(level, scope);
138
145
  const formatted = this.formatMessage(level, message, scope);
146
+
147
+ // 处理参数,防止输出过长
148
+ const processedArgs = this.config.useSummaryMode
149
+ ? args.map(arg => generateSummary(arg))
150
+ : args.map(arg => truncateForLog(arg, this.config.maxOutputLength));
151
+
139
152
  const entry: LogEntry = {
140
153
  level,
141
154
  message,
142
- args,
155
+ args: processedArgs,
143
156
  timestamp: new Date(),
144
157
  scope,
145
158
  formatted,
@@ -154,15 +167,15 @@ export class Logger {
154
167
  case LogLevel.DEBUG:
155
168
  case LogLevel.INFO:
156
169
  // eslint-disable-next-line no-console
157
- console.log(prefix, message, ...args);
170
+ console.log(prefix, message, ...processedArgs);
158
171
  break;
159
172
  case LogLevel.WARN:
160
173
  // eslint-disable-next-line no-console
161
- console.warn(prefix, message, ...args);
174
+ console.warn(prefix, message, ...processedArgs);
162
175
  break;
163
176
  case LogLevel.ERROR:
164
177
  // eslint-disable-next-line no-console
165
- console.error(prefix, message, ...args);
178
+ console.error(prefix, message, ...processedArgs);
166
179
  break;
167
180
  }
168
181
  }
@@ -0,0 +1,227 @@
1
+ /**
2
+ * 输出限制工具 - 防止响应被截断
3
+ * 用于处理大量数据输出时的截断问题
4
+ */
5
+
6
+ /**
7
+ * 截断配置选项
8
+ */
9
+ export interface TruncateOptions {
10
+ /** 最大长度限制 */
11
+ maxLength?: number;
12
+ /** 截断后添加的后缀 */
13
+ suffix?: string;
14
+ /** 是否保留开头部分 */
15
+ preserveHead?: boolean;
16
+ /** 保留开头的字符数(当 preserveHead 为 true 时有效) */
17
+ headLength?: number;
18
+ /** 保留结尾的字符数(当 preserveHead 为 true 时有效) */
19
+ tailLength?: number;
20
+ }
21
+
22
+ /**
23
+ * 默认截断配置
24
+ */
25
+ const DEFAULT_OPTIONS: Required<TruncateOptions> = {
26
+ maxLength: 10000,
27
+ suffix: '... [truncated]',
28
+ preserveHead: true,
29
+ headLength: 5000,
30
+ tailLength: 5000,
31
+ };
32
+
33
+ /**
34
+ * 智能截断字符串,避免输出过长被截断
35
+ *
36
+ * @param str - 原始字符串
37
+ * @param options - 截断选项
38
+ * @returns 截断后的字符串
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * // 简单截断
43
+ * const short = truncateString(longText, { maxLength: 1000 });
44
+ *
45
+ * // 保留首尾
46
+ * const summary = truncateString(longText, {
47
+ * preserveHead: true,
48
+ * headLength: 500,
49
+ * tailLength: 500
50
+ * });
51
+ * ```
52
+ */
53
+ export function truncateString(str: string, options: TruncateOptions = {}): string {
54
+ const opts = { ...DEFAULT_OPTIONS, ...options };
55
+
56
+ if (str.length <= opts.maxLength) {
57
+ return str;
58
+ }
59
+
60
+ if (opts.preserveHead) {
61
+ const totalPreserve = opts.headLength + opts.tailLength + opts.suffix.length;
62
+ if (totalPreserve < opts.maxLength) {
63
+ const head = str.slice(0, opts.headLength);
64
+ const tail = str.slice(-opts.tailLength);
65
+ return `${head}${opts.suffix}${tail}`;
66
+ }
67
+ }
68
+
69
+ return str.slice(0, opts.maxLength - opts.suffix.length) + opts.suffix;
70
+ }
71
+
72
+ /**
73
+ * 截断对象/数组的字符串表示,用于日志输出
74
+ *
75
+ * @param data - 任意数据
76
+ * @param maxLength - 最大长度
77
+ * @returns 截断后的字符串
78
+ */
79
+ export function truncateForLog(data: unknown, maxLength: number = 2000): string {
80
+ if (data === null) return 'null';
81
+ if (data === undefined) return 'undefined';
82
+
83
+ let str: string;
84
+
85
+ if (typeof data === 'string') {
86
+ str = data;
87
+ } else if (typeof data === 'number' || typeof data === 'boolean') {
88
+ return String(data);
89
+ } else if (data instanceof Error) {
90
+ str = `${data.name}: ${data.message}`;
91
+ if (data.stack) {
92
+ str += `\n${truncateString(data.stack, { maxLength: 1000 })}`;
93
+ }
94
+ return str;
95
+ } else if (data instanceof Uint8Array || data instanceof ArrayBuffer) {
96
+ const bytes = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
97
+ str = `Uint8Array(${bytes.length}) [${truncateString(
98
+ Array.from(bytes.slice(0, 100))
99
+ .map(b => b.toString(16).padStart(2, '0'))
100
+ .join(' '),
101
+ { maxLength: 300 }
102
+ )}]`;
103
+ if (bytes.length > 100) {
104
+ str += ` ... (${bytes.length} bytes total)`;
105
+ }
106
+ return str;
107
+ } else {
108
+ try {
109
+ str = JSON.stringify(data);
110
+ } catch {
111
+ str = Object.prototype.toString.call(data);
112
+ }
113
+ }
114
+
115
+ return truncateString(str, { maxLength });
116
+ }
117
+
118
+ /**
119
+ * 分批处理大量数据,避免一次性输出过多
120
+ *
121
+ * @param items - 数据项数组
122
+ * @param batchSize - 每批大小
123
+ * @param processor - 处理函数
124
+ * @returns 处理结果
125
+ *
126
+ * @example
127
+ * ```typescript
128
+ * const results = await batchProcess(
129
+ * largeArray,
130
+ * 100,
131
+ * async (batch) => {
132
+ * console.log(`Processing batch of ${batch.length} items`);
133
+ * return batch.map(item => process(item));
134
+ * }
135
+ * );
136
+ * ```
137
+ */
138
+ export async function batchProcess<T, R>(
139
+ items: T[],
140
+ batchSize: number,
141
+ processor: (batch: T[], batchIndex: number) => Promise<R[]> | R[]
142
+ ): Promise<R[]> {
143
+ const results: R[] = [];
144
+ const totalBatches = Math.ceil(items.length / batchSize);
145
+
146
+ for (let i = 0; i < items.length; i += batchSize) {
147
+ const batch = items.slice(i, i + batchSize);
148
+ const batchIndex = Math.floor(i / batchSize) + 1;
149
+
150
+ const batchResults = await processor(batch, batchIndex);
151
+ results.push(...batchResults);
152
+
153
+ // 添加小延迟,避免阻塞
154
+ if (batchIndex < totalBatches) {
155
+ await new Promise(resolve => setTimeout(resolve, 0));
156
+ }
157
+ }
158
+
159
+ return results;
160
+ }
161
+
162
+ /**
163
+ * 创建受限制的日志输出函数
164
+ *
165
+ * @param maxLength - 单条日志最大长度
166
+ * @returns 受限的日志函数
167
+ */
168
+ export function createLimitedLogger(maxLength: number = 5000) {
169
+ return {
170
+ log: (...args: unknown[]) => {
171
+ const limited = args.map(arg => truncateForLog(arg, maxLength));
172
+ // eslint-disable-next-line no-console
173
+ console.log(...limited);
174
+ },
175
+ warn: (...args: unknown[]) => {
176
+ const limited = args.map(arg => truncateForLog(arg, maxLength));
177
+ // eslint-disable-next-line no-console
178
+ console.warn(...limited);
179
+ },
180
+ error: (...args: unknown[]) => {
181
+ const limited = args.map(arg => truncateForLog(arg, maxLength));
182
+ // eslint-disable-next-line no-console
183
+ console.error(...limited);
184
+ },
185
+ };
186
+ }
187
+
188
+ /**
189
+ * 数据摘要生成器 - 用于生成数据的简短摘要
190
+ *
191
+ * @param data - 任意数据
192
+ * @returns 数据摘要
193
+ */
194
+ export function generateSummary(data: unknown): string {
195
+ if (data === null) return 'null';
196
+ if (data === undefined) return 'undefined';
197
+
198
+ if (typeof data === 'string') {
199
+ return `string(${data.length}): "${truncateString(data, { maxLength: 100 })}"`;
200
+ }
201
+
202
+ if (typeof data === 'number') return `number: ${data}`;
203
+ if (typeof data === 'boolean') return `boolean: ${data}`;
204
+
205
+ if (Array.isArray(data)) {
206
+ return `array[${data.length}]`;
207
+ }
208
+
209
+ if (data instanceof Uint8Array) {
210
+ return `Uint8Array(${data.length} bytes)`;
211
+ }
212
+
213
+ if (data instanceof ArrayBuffer) {
214
+ return `ArrayBuffer(${data.byteLength} bytes)`;
215
+ }
216
+
217
+ if (data instanceof Error) {
218
+ return `Error: ${data.name}: ${truncateString(data.message, { maxLength: 100 })}`;
219
+ }
220
+
221
+ if (typeof data === 'object') {
222
+ const keys = Object.keys(data as Record<string, unknown>);
223
+ return `object{${keys.length} keys: ${keys.slice(0, 5).join(', ')}${keys.length > 5 ? '...' : ''}}`;
224
+ }
225
+
226
+ return String(data as unknown);
227
+ }