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.
- package/CHANGELOG.md +16 -1
- package/README.md +53 -4
- package/dist/index.cjs.js +1 -1
- package/dist/index.es.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/types/core/di/Container.d.ts +84 -0
- package/dist/types/core/di/Tokens.d.ts +29 -0
- package/dist/types/core/di/index.d.ts +3 -0
- package/dist/types/core/event/EventBus.d.ts +66 -0
- package/dist/types/core/event/index.d.ts +2 -0
- package/dist/types/core/index.d.ts +5 -4
- package/dist/types/core/plugin/PluginManager.d.ts +64 -0
- package/dist/types/core/plugin/index.d.ts +2 -0
- package/dist/types/device/MultiPrinterManager.d.ts +2 -0
- package/dist/types/factory/di-factory.d.ts +52 -0
- package/dist/types/index.d.ts +5 -1
- package/dist/types/providers/ServiceProvider.d.ts +56 -0
- package/dist/types/providers/index.d.ts +2 -0
- package/dist/types/template/TemplateEngine.d.ts +24 -68
- package/dist/types/template/engines/TemplateRenderer.d.ts +71 -0
- package/dist/types/template/parsers/TemplateParser.d.ts +23 -0
- package/dist/types/utils/index.d.ts +8 -0
- package/dist/types/utils/logger.d.ts +4 -3
- package/dist/types/utils/outputLimiter.d.ts +87 -0
- package/dist/types/utils/validation.d.ts +11 -309
- package/dist/types/utils/validators/array.d.ts +19 -0
- package/dist/types/utils/validators/buffer.d.ts +18 -0
- package/dist/types/utils/validators/chain.d.ts +31 -0
- package/dist/types/utils/validators/common.d.ts +22 -0
- package/dist/types/utils/validators/number.d.ts +20 -0
- package/dist/types/utils/validators/object.d.ts +24 -0
- package/dist/types/utils/validators/printer.d.ts +40 -0
- package/dist/types/utils/validators/types.d.ts +125 -0
- package/dist/types/utils/validators/uuid.d.ts +23 -0
- package/package.json +1 -1
- package/src/core/BluetoothPrinter.ts +2 -1
- package/src/core/di/Container.ts +332 -0
- package/src/core/di/Tokens.ts +45 -0
- package/src/core/di/index.ts +3 -0
- package/src/core/event/EventBus.ts +251 -0
- package/src/core/event/index.ts +2 -0
- package/src/core/index.ts +10 -4
- package/src/core/plugin/PluginManager.ts +161 -0
- package/src/core/plugin/index.ts +2 -0
- package/src/device/MultiPrinterManager.ts +15 -6
- package/src/factory/di-factory.ts +61 -0
- package/src/index.ts +50 -1
- package/src/providers/ServiceProvider.ts +213 -0
- package/src/providers/index.ts +2 -0
- package/src/template/TemplateEngine.ts +27 -792
- package/src/template/engines/TemplateRenderer.ts +762 -0
- package/src/template/parsers/TemplateParser.ts +94 -0
- package/src/utils/index.ts +9 -0
- package/src/utils/logger.ts +17 -4
- package/src/utils/outputLimiter.ts +227 -0
- package/src/utils/validation.ts +21 -1138
- package/src/utils/validators/array.ts +95 -0
- package/src/utils/validators/buffer.ts +81 -0
- package/src/utils/validators/chain.ts +181 -0
- package/src/utils/validators/common.ts +216 -0
- package/src/utils/validators/number.ts +101 -0
- package/src/utils/validators/object.ts +63 -0
- package/src/utils/validators/printer.ts +294 -0
- package/src/utils/validators/types.ts +105 -0
- 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
|
+
}
|
package/src/utils/logger.ts
CHANGED
|
@@ -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, ...
|
|
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, ...
|
|
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, ...
|
|
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
|
+
}
|