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.
- package/CHANGELOG.md +33 -0
- 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/BluetoothPrinter.d.ts +21 -4
- 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/errors/CommandBuildError.d.ts +40 -0
- package/dist/types/errors/ConnectionError.d.ts +45 -0
- package/dist/types/errors/PrintJobError.d.ts +42 -0
- package/dist/types/errors/index.d.ts +9 -0
- package/dist/types/factory/PrinterFactory.d.ts +108 -0
- package/dist/types/factory/di-factory.d.ts +52 -0
- package/dist/types/factory/index.d.ts +6 -0
- package/dist/types/index.d.ts +9 -1
- package/dist/types/providers/ServiceProvider.d.ts +56 -0
- package/dist/types/providers/index.d.ts +2 -0
- package/dist/types/services/interfaces/ICommandBuilder.d.ts +123 -0
- package/dist/types/services/interfaces/IConnectionManager.d.ts +49 -0
- package/dist/types/services/interfaces/IPrintJobManager.d.ts +67 -0
- package/dist/types/services/interfaces/index.d.ts +6 -233
- 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 +48 -50
- package/src/core/di/Container.ts +330 -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/errors/CommandBuildError.ts +72 -0
- package/src/errors/ConnectionError.ts +78 -0
- package/src/errors/PrintJobError.ts +75 -0
- package/src/errors/index.ts +10 -0
- package/src/factory/PrinterFactory.ts +139 -0
- package/src/factory/di-factory.ts +61 -0
- package/src/factory/index.ts +12 -0
- package/src/index.ts +61 -1
- package/src/providers/ServiceProvider.ts +213 -0
- package/src/providers/index.ts +2 -0
- package/src/services/interfaces/ICommandBuilder.ts +145 -0
- package/src/services/interfaces/IConnectionManager.ts +58 -0
- package/src/services/interfaces/IPrintJobManager.ts +83 -0
- package/src/services/interfaces/index.ts +5 -265
- 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,40 @@
|
|
|
1
|
+
import { ValidationResult, PrinterDataSchema, PrintJobSchema } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Validate printer connection data
|
|
4
|
+
*
|
|
5
|
+
* @param data - Data to validate
|
|
6
|
+
* @param schema - Validation schema
|
|
7
|
+
* @returns Validation result
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* const result = validatePrinterData({
|
|
12
|
+
* deviceId: 'device-123',
|
|
13
|
+
* deviceName: 'Thermal Printer',
|
|
14
|
+
* }, {
|
|
15
|
+
* deviceId: { required: true },
|
|
16
|
+
* deviceName: { required: true, maxLength: 50 }
|
|
17
|
+
* });
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare function validatePrinterData(data: Record<string, unknown>, schema: PrinterDataSchema): ValidationResult;
|
|
21
|
+
/**
|
|
22
|
+
* Validate print job data
|
|
23
|
+
*
|
|
24
|
+
* @param data - Data to validate
|
|
25
|
+
* @param schema - Validation schema
|
|
26
|
+
* @returns Validation result
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* const result = validatePrintJob({
|
|
31
|
+
* jobId: 'job-001',
|
|
32
|
+
* data: new ArrayBuffer(1024),
|
|
33
|
+
* priority: 5,
|
|
34
|
+
* }, {
|
|
35
|
+
* jobId: { required: true },
|
|
36
|
+
* data: { required: true, minSize: 1 }
|
|
37
|
+
* });
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export declare function validatePrintJob(data: Record<string, unknown>, schema: PrintJobSchema): ValidationResult;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core validation types
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Validation error details
|
|
6
|
+
*/
|
|
7
|
+
export interface ValidationError {
|
|
8
|
+
/** Field that failed validation */
|
|
9
|
+
field: string;
|
|
10
|
+
/** Human-readable error message */
|
|
11
|
+
message: string;
|
|
12
|
+
/** Error code for programmatic handling */
|
|
13
|
+
code: string;
|
|
14
|
+
/** Actual value that failed (optional) */
|
|
15
|
+
value?: unknown;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Validation result
|
|
19
|
+
*/
|
|
20
|
+
export interface ValidationResult {
|
|
21
|
+
/** Whether validation passed */
|
|
22
|
+
valid: boolean;
|
|
23
|
+
/** List of validation errors */
|
|
24
|
+
errors: ValidationError[];
|
|
25
|
+
/** Warnings (non-fatal issues) */
|
|
26
|
+
warnings?: ValidationError[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Validator function type
|
|
30
|
+
*/
|
|
31
|
+
export type ValidatorFn<T = unknown> = (value: T) => ValidationResult;
|
|
32
|
+
/**
|
|
33
|
+
* Validation rule definition
|
|
34
|
+
*/
|
|
35
|
+
export interface ValidationRule<T = unknown> {
|
|
36
|
+
/** Rule name */
|
|
37
|
+
name: string;
|
|
38
|
+
/** Validation function */
|
|
39
|
+
validate: (value: T) => boolean;
|
|
40
|
+
/** Error message if validation fails */
|
|
41
|
+
message: string;
|
|
42
|
+
/** Error code */
|
|
43
|
+
code: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Common error codes
|
|
47
|
+
*/
|
|
48
|
+
export declare const ValidationCodes: {
|
|
49
|
+
readonly REQUIRED: "REQUIRED";
|
|
50
|
+
readonly INVALID_TYPE: "INVALID_TYPE";
|
|
51
|
+
readonly INVALID_FORMAT: "INVALID_FORMAT";
|
|
52
|
+
readonly OUT_OF_RANGE: "OUT_OF_RANGE";
|
|
53
|
+
readonly TOO_SHORT: "TOO_SHORT";
|
|
54
|
+
readonly TOO_LONG: "TOO_LONG";
|
|
55
|
+
readonly INVALID_ENUM: "INVALID_ENUM";
|
|
56
|
+
readonly PATTERN_MISMATCH: "PATTERN_MISMATCH";
|
|
57
|
+
readonly INVALID_BUFFER: "INVALID_BUFFER";
|
|
58
|
+
readonly INVALID_ENCODING: "INVALID_ENCODING";
|
|
59
|
+
readonly EMPTY_ARRAY: "EMPTY_ARRAY";
|
|
60
|
+
readonly ARRAY_TOO_SHORT: "ARRAY_TOO_SHORT";
|
|
61
|
+
readonly ARRAY_TOO_LONG: "ARRAY_TOO_LONG";
|
|
62
|
+
readonly DUPLICATE_VALUE: "DUPLICATE_VALUE";
|
|
63
|
+
readonly NEGATIVE_VALUE: "NEGATIVE_VALUE";
|
|
64
|
+
readonly ZERO_VALUE: "ZERO_VALUE";
|
|
65
|
+
readonly NOT_A_NUMBER: "NOT_A_NUMBER";
|
|
66
|
+
readonly INFINITY_VALUE: "INFINITY_VALUE";
|
|
67
|
+
readonly INVALID_JSON: "INVALID_JSON";
|
|
68
|
+
readonly UNSUPPORTED_VALUE: "UNSUPPORTED_VALUE";
|
|
69
|
+
readonly INVALID_VERSION: "INVALID_VERSION";
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Printer data validation schema
|
|
73
|
+
*/
|
|
74
|
+
export interface PrinterDataSchema {
|
|
75
|
+
/** Device ID */
|
|
76
|
+
deviceId?: {
|
|
77
|
+
required?: boolean;
|
|
78
|
+
pattern?: RegExp;
|
|
79
|
+
};
|
|
80
|
+
/** Device name */
|
|
81
|
+
deviceName?: {
|
|
82
|
+
required?: boolean;
|
|
83
|
+
maxLength?: number;
|
|
84
|
+
};
|
|
85
|
+
/** Service UUID */
|
|
86
|
+
serviceUUID?: {
|
|
87
|
+
required?: boolean;
|
|
88
|
+
pattern?: RegExp;
|
|
89
|
+
};
|
|
90
|
+
/** Characteristic UUID */
|
|
91
|
+
characteristicUUID?: {
|
|
92
|
+
required?: boolean;
|
|
93
|
+
pattern?: RegExp;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Print job data validation schema
|
|
98
|
+
*/
|
|
99
|
+
export interface PrintJobSchema {
|
|
100
|
+
/** Job ID */
|
|
101
|
+
jobId?: {
|
|
102
|
+
required?: boolean;
|
|
103
|
+
maxLength?: number;
|
|
104
|
+
};
|
|
105
|
+
/** Data buffer */
|
|
106
|
+
data?: {
|
|
107
|
+
required?: boolean;
|
|
108
|
+
minSize?: number;
|
|
109
|
+
maxSize?: number;
|
|
110
|
+
};
|
|
111
|
+
/** Priority */
|
|
112
|
+
priority?: {
|
|
113
|
+
min?: number;
|
|
114
|
+
max?: number;
|
|
115
|
+
};
|
|
116
|
+
/** Retry count */
|
|
117
|
+
retryCount?: {
|
|
118
|
+
min?: number;
|
|
119
|
+
max?: number;
|
|
120
|
+
};
|
|
121
|
+
/** Metadata */
|
|
122
|
+
metadata?: {
|
|
123
|
+
required?: boolean;
|
|
124
|
+
};
|
|
125
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UUID validation
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Validate a string is valid UUID
|
|
6
|
+
*
|
|
7
|
+
* @param value - Value to validate
|
|
8
|
+
* @param options - Validation options
|
|
9
|
+
* @returns Validation result
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* const result = isValidUUID(value, { required: true });
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export declare function isValidUUID(value: unknown, options?: {
|
|
17
|
+
required?: boolean;
|
|
18
|
+
versions?: number[];
|
|
19
|
+
}): {
|
|
20
|
+
valid: boolean;
|
|
21
|
+
version?: number;
|
|
22
|
+
error?: string;
|
|
23
|
+
};
|
package/package.json
CHANGED
|
@@ -77,44 +77,53 @@ export class BluetoothPrinter extends EventEmitter<PrinterEvents> {
|
|
|
77
77
|
/**
|
|
78
78
|
* Creates a new BluetoothPrinter instance
|
|
79
79
|
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
80
|
+
* Supports two calling conventions:
|
|
81
|
+
* - Modern DI: new BluetoothPrinter(connectionManager, printJobManager, commandBuilder)
|
|
82
|
+
* - Legacy API: new BluetoothPrinter(adapter) - adapter is wrapped in ConnectionManager
|
|
83
|
+
*
|
|
84
|
+
* @param connectionManagerOrAdapter - Connection manager (recommended) or IPrinterAdapter (legacy)
|
|
85
|
+
* @param printJobManager - Print job manager instance
|
|
86
|
+
* @param commandBuilder - Command builder instance
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```typescript
|
|
90
|
+
* // Recommended: use the factory
|
|
91
|
+
* import { createBluetoothPrinter } from 'taro-bluetooth-print';
|
|
92
|
+
* const printer = createBluetoothPrinter({ adapter: myAdapter });
|
|
93
|
+
*
|
|
94
|
+
* // Direct instantiation with DI
|
|
95
|
+
* const printer = new BluetoothPrinter(connectionManager, printJobManager, commandBuilder);
|
|
96
|
+
*
|
|
97
|
+
* // Legacy API (backward compatible)
|
|
98
|
+
* const printer = new BluetoothPrinter(adapter);
|
|
99
|
+
* ```
|
|
83
100
|
*/
|
|
84
101
|
constructor(
|
|
85
102
|
connectionManagerOrAdapter?: IConnectionManager | IPrinterAdapter,
|
|
86
|
-
|
|
103
|
+
printJobManager?: IPrintJobManager,
|
|
87
104
|
commandBuilder?: ICommandBuilder
|
|
88
105
|
) {
|
|
89
106
|
super();
|
|
90
107
|
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
connectionManagerOrAdapter &&
|
|
94
|
-
typeof (connectionManagerOrAdapter as IPrinterAdapter).connect === 'function'
|
|
95
|
-
) {
|
|
96
|
-
// Old API: new BluetoothPrinter(adapter)
|
|
97
|
-
const adapter = connectionManagerOrAdapter as IPrinterAdapter;
|
|
108
|
+
// Detect legacy API: first arg is an adapter (has connect method) vs connection manager
|
|
109
|
+
const isAdapter =
|
|
110
|
+
connectionManagerOrAdapter != null &&
|
|
111
|
+
typeof (connectionManagerOrAdapter as IPrinterAdapter).connect === 'function';
|
|
98
112
|
|
|
99
|
-
|
|
113
|
+
if (isAdapter) {
|
|
114
|
+
// Legacy API: wrap adapter in ConnectionManager
|
|
115
|
+
const adapter = connectionManagerOrAdapter as IPrinterAdapter;
|
|
100
116
|
this.connectionManager = new ConnectionManager(adapter);
|
|
101
|
-
this.printJobManager = new PrintJobManager(this.connectionManager);
|
|
102
|
-
this.commandBuilder = commandBuilder
|
|
117
|
+
this.printJobManager = printJobManager ?? new PrintJobManager(this.connectionManager);
|
|
118
|
+
this.commandBuilder = commandBuilder ?? new CommandBuilder();
|
|
103
119
|
} else {
|
|
104
|
-
//
|
|
105
|
-
this.connectionManager =
|
|
106
|
-
|
|
107
|
-
this.
|
|
108
|
-
|
|
109
|
-
// If services are not provided, create them using default implementations
|
|
110
|
-
if (!this.connectionManager) {
|
|
111
|
-
this.connectionManager = new ConnectionManager();
|
|
112
|
-
this.printJobManager = new PrintJobManager(this.connectionManager);
|
|
113
|
-
this.commandBuilder = new CommandBuilder();
|
|
114
|
-
}
|
|
120
|
+
// Modern DI
|
|
121
|
+
this.connectionManager =
|
|
122
|
+
(connectionManagerOrAdapter as IConnectionManager) ?? new ConnectionManager();
|
|
123
|
+
this.printJobManager = printJobManager ?? new PrintJobManager(this.connectionManager);
|
|
124
|
+
this.commandBuilder = commandBuilder ?? new CommandBuilder();
|
|
115
125
|
}
|
|
116
126
|
|
|
117
|
-
// Listen to connection manager state changes
|
|
118
127
|
this.updateState();
|
|
119
128
|
}
|
|
120
129
|
|
|
@@ -122,28 +131,19 @@ export class BluetoothPrinter extends EventEmitter<PrinterEvents> {
|
|
|
122
131
|
* Updates the current state based on the connection manager and print job manager states
|
|
123
132
|
*/
|
|
124
133
|
private updateState(): void {
|
|
125
|
-
//
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
// Default to CONNECTED for backward compatibility
|
|
131
|
-
connectionState = PrinterState.CONNECTED;
|
|
132
|
-
}
|
|
134
|
+
// Safe fallbacks for mock objects in tests
|
|
135
|
+
const connectionState =
|
|
136
|
+
typeof this.connectionManager.getState === 'function'
|
|
137
|
+
? this.connectionManager.getState()
|
|
138
|
+
: PrinterState.CONNECTED;
|
|
133
139
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
: false;
|
|
142
|
-
isPaused =
|
|
143
|
-
typeof this.printJobManager.isPaused === 'function'
|
|
144
|
-
? this.printJobManager.isPaused()
|
|
145
|
-
: false;
|
|
146
|
-
}
|
|
140
|
+
const isPrinting =
|
|
141
|
+
typeof this.printJobManager.isInProgress === 'function'
|
|
142
|
+
? this.printJobManager.isInProgress()
|
|
143
|
+
: false;
|
|
144
|
+
|
|
145
|
+
const isPaused =
|
|
146
|
+
typeof this.printJobManager.isPaused === 'function' ? this.printJobManager.isPaused() : false;
|
|
147
147
|
|
|
148
148
|
// Determine the final state
|
|
149
149
|
if (isPaused) {
|
|
@@ -425,11 +425,10 @@ export class BluetoothPrinter extends EventEmitter<PrinterEvents> {
|
|
|
425
425
|
* ```
|
|
426
426
|
*/
|
|
427
427
|
async print(): Promise<void> {
|
|
428
|
-
// Check if connectionManager has isConnected method, default to true for mock objects in tests
|
|
429
428
|
const isConnected =
|
|
430
429
|
typeof this.connectionManager.isConnected === 'function'
|
|
431
430
|
? this.connectionManager.isConnected()
|
|
432
|
-
: true;
|
|
431
|
+
: true; // Default to true for mock objects
|
|
433
432
|
|
|
434
433
|
if (!isConnected) {
|
|
435
434
|
throw new BluetoothPrintError(
|
|
@@ -454,7 +453,6 @@ export class BluetoothPrinter extends EventEmitter<PrinterEvents> {
|
|
|
454
453
|
try {
|
|
455
454
|
await this.printJobManager.start(buffer);
|
|
456
455
|
|
|
457
|
-
// Check if the job was paused
|
|
458
456
|
const isPaused =
|
|
459
457
|
typeof this.printJobManager.isPaused === 'function'
|
|
460
458
|
? this.printJobManager.isPaused()
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 依赖注入容器
|
|
3
|
+
* 提供现代化的依赖管理和服务定位功能
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
|
+
export type Constructor<T = object> = new (...args: any[]) => T;
|
|
8
|
+
export type Factory<T> = (container: Container) => T;
|
|
9
|
+
export type Provider<T> = Constructor<T> | Factory<T> | T;
|
|
10
|
+
|
|
11
|
+
export interface RegistrationOptions {
|
|
12
|
+
/** 是否单例 */
|
|
13
|
+
singleton?: boolean;
|
|
14
|
+
/** 生命周期 */
|
|
15
|
+
lifecycle?: 'transient' | 'singleton' | 'scoped';
|
|
16
|
+
/** 标签,用于区分同一接口的不同实现 */
|
|
17
|
+
tag?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Angular 风格的服务提供者配置
|
|
22
|
+
*/
|
|
23
|
+
export interface ServiceProviderConfig<T = unknown> {
|
|
24
|
+
/** 使用类 */
|
|
25
|
+
useClass?: Constructor<T>;
|
|
26
|
+
/** 使用工厂函数 */
|
|
27
|
+
useFactory?: Factory<T>;
|
|
28
|
+
/** 使用实例值 */
|
|
29
|
+
useValue?: T;
|
|
30
|
+
/** 生命周期 */
|
|
31
|
+
lifecycle?: 'transient' | 'singleton' | 'scoped';
|
|
32
|
+
/** 依赖项 */
|
|
33
|
+
dependencies?: (string | symbol | Constructor<unknown>)[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface Registration<T> {
|
|
37
|
+
provider: Provider<T>;
|
|
38
|
+
options: RegistrationOptions;
|
|
39
|
+
instance?: T;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export class Container {
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
44
|
+
private registrations = new Map<string | symbol, Registration<any>[]>();
|
|
45
|
+
private parent?: Container;
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
47
|
+
private scopedInstances = new Map<string | symbol, any>();
|
|
48
|
+
|
|
49
|
+
constructor(parent?: Container) {
|
|
50
|
+
this.parent = parent;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 创建子容器
|
|
55
|
+
*/
|
|
56
|
+
createChild(): Container {
|
|
57
|
+
return new Container(this);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 注册服务
|
|
62
|
+
*/
|
|
63
|
+
register<T>(
|
|
64
|
+
token: string | symbol | Constructor<T>,
|
|
65
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
66
|
+
provider: Provider<T> | ServiceProviderConfig<T>,
|
|
67
|
+
options: RegistrationOptions = {}
|
|
68
|
+
): this {
|
|
69
|
+
const key = this.getTokenKey(token);
|
|
70
|
+
|
|
71
|
+
// 支持 ServiceProviderConfig 格式
|
|
72
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
73
|
+
const config = provider as ServiceProviderConfig<T>;
|
|
74
|
+
|
|
75
|
+
// 如果是对象语法(Angular 风格)
|
|
76
|
+
if (config && typeof config === 'object' && (config.useClass || config.useFactory || config.useValue !== undefined)) {
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
78
|
+
let actualProvider: Provider<any>;
|
|
79
|
+
|
|
80
|
+
if (config.useClass) {
|
|
81
|
+
// 使用类 - 创建工厂函数来解析依赖
|
|
82
|
+
const Cls = config.useClass;
|
|
83
|
+
actualProvider = (container: Container) => {
|
|
84
|
+
// 简化:直接实例化(实际应该解析依赖)
|
|
85
|
+
return container.createInstance(Cls);
|
|
86
|
+
};
|
|
87
|
+
} else if (config.useFactory) {
|
|
88
|
+
actualProvider = config.useFactory;
|
|
89
|
+
} else {
|
|
90
|
+
actualProvider = config.useValue as T;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const registration: Registration<T> = {
|
|
94
|
+
provider: actualProvider,
|
|
95
|
+
options: {
|
|
96
|
+
lifecycle: config.lifecycle || 'transient',
|
|
97
|
+
...options,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
if (!this.registrations.has(key)) {
|
|
102
|
+
this.registrations.set(key, []);
|
|
103
|
+
}
|
|
104
|
+
this.registrations.get(key)!.push(registration);
|
|
105
|
+
return this;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 原有逻辑:Provider<T> 格式
|
|
109
|
+
const registration: Registration<T> = {
|
|
110
|
+
provider: provider as Provider<T>,
|
|
111
|
+
options: {
|
|
112
|
+
lifecycle: 'transient',
|
|
113
|
+
...options,
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
if (!this.registrations.has(key)) {
|
|
118
|
+
this.registrations.set(key, []);
|
|
119
|
+
}
|
|
120
|
+
this.registrations.get(key)!.push(registration);
|
|
121
|
+
return this;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 注册单例服务
|
|
126
|
+
*/
|
|
127
|
+
registerSingleton<T>(token: string | symbol | Constructor<T>, provider: Provider<T>): this {
|
|
128
|
+
return this.register(token, provider, { lifecycle: 'singleton' });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 注册类型映射
|
|
133
|
+
*/
|
|
134
|
+
registerType<T>(
|
|
135
|
+
from: string | symbol | Constructor<T>,
|
|
136
|
+
to: Constructor<T>,
|
|
137
|
+
options: RegistrationOptions = {}
|
|
138
|
+
): this {
|
|
139
|
+
return this.register(from, c => c.resolve(to), options);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* 注册实例
|
|
144
|
+
*/
|
|
145
|
+
registerInstance<T>(token: string | symbol | Constructor<T>, instance: T): this {
|
|
146
|
+
return this.register(token, instance, { lifecycle: 'singleton' });
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 解析服务
|
|
151
|
+
*/
|
|
152
|
+
resolve<T>(token: string | symbol | Constructor<T>, tag?: string): T {
|
|
153
|
+
const key = this.getTokenKey(token);
|
|
154
|
+
|
|
155
|
+
// 1. 检查当前容器的注册
|
|
156
|
+
const registrations = this.registrations.get(key);
|
|
157
|
+
if (registrations) {
|
|
158
|
+
const registration = tag
|
|
159
|
+
? registrations.find(r => r.options.tag === tag)
|
|
160
|
+
: registrations[registrations.length - 1];
|
|
161
|
+
|
|
162
|
+
if (registration) {
|
|
163
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
164
|
+
return this.getOrCreateInstance(registration, key);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// 2. 检查父容器
|
|
169
|
+
if (this.parent) {
|
|
170
|
+
return this.parent.resolve(token, tag);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// 3. 如果是构造函数,自动创建实例
|
|
174
|
+
if (typeof token === 'function' && this.isConstructor(token)) {
|
|
175
|
+
return this.createInstance(token);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
throw new Error(`No registration found for token: ${String(key)}`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* 解析所有注册的服务
|
|
183
|
+
*/
|
|
184
|
+
resolveAll<T>(token: string | symbol | Constructor<T>): T[] {
|
|
185
|
+
const key = this.getTokenKey(token);
|
|
186
|
+
const results: T[] = [];
|
|
187
|
+
|
|
188
|
+
// 从当前容器获取
|
|
189
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
190
|
+
const registrations = this.registrations.get(key);
|
|
191
|
+
if (registrations) {
|
|
192
|
+
for (const r of registrations) {
|
|
193
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
194
|
+
results.push(this.getOrCreateInstance(r, key));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// 从父容器获取
|
|
199
|
+
if (this.parent) {
|
|
200
|
+
results.push(...this.parent.resolveAll(token));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return results;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* 检查是否已注册
|
|
208
|
+
*/
|
|
209
|
+
isRegistered<T>(token: string | symbol | Constructor<T>): boolean {
|
|
210
|
+
const key = this.getTokenKey(token);
|
|
211
|
+
return this.registrations.has(key) || (this.parent?.isRegistered(token) ?? false);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* 清除所有注册
|
|
216
|
+
*/
|
|
217
|
+
clear(): void {
|
|
218
|
+
this.registrations.clear();
|
|
219
|
+
this.scopedInstances.clear();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* 创建作用域
|
|
224
|
+
*/
|
|
225
|
+
createScope(): Container {
|
|
226
|
+
const scope = this.createChild();
|
|
227
|
+
return scope;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
231
|
+
private getTokenKey(token: string | symbol | Constructor<any>): string | symbol {
|
|
232
|
+
if (typeof token === 'function') {
|
|
233
|
+
return token.name || (token as unknown as symbol);
|
|
234
|
+
}
|
|
235
|
+
return token;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
239
|
+
private isConstructor(fn: Function): boolean {
|
|
240
|
+
return (
|
|
241
|
+
typeof fn.prototype === 'object' &&
|
|
242
|
+
fn.prototype !== null &&
|
|
243
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
244
|
+
fn.prototype.constructor === fn
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
private getOrCreateInstance<T>(registration: Registration<T>, key: string | symbol): T {
|
|
249
|
+
const { provider, options } = registration;
|
|
250
|
+
|
|
251
|
+
// 单例模式
|
|
252
|
+
if (options.lifecycle === 'singleton') {
|
|
253
|
+
if (registration.instance === undefined) {
|
|
254
|
+
registration.instance = this.createProviderInstance(provider);
|
|
255
|
+
}
|
|
256
|
+
return registration.instance;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// 作用域模式
|
|
260
|
+
if (options.lifecycle === 'scoped') {
|
|
261
|
+
if (this.scopedInstances.has(key)) {
|
|
262
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
263
|
+
return this.scopedInstances.get(key) as T;
|
|
264
|
+
}
|
|
265
|
+
const instance = this.createProviderInstance(provider);
|
|
266
|
+
this.scopedInstances.set(key, instance);
|
|
267
|
+
return instance;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// 瞬态模式
|
|
271
|
+
return this.createProviderInstance(provider);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
private createProviderInstance<T>(provider: Provider<T>): T {
|
|
275
|
+
if (typeof provider === 'function') {
|
|
276
|
+
// 工厂函数
|
|
277
|
+
if (provider.length === 1 && !this.isConstructor(provider)) {
|
|
278
|
+
return (provider as Factory<T>)(this);
|
|
279
|
+
}
|
|
280
|
+
// 构造函数
|
|
281
|
+
return this.createInstance(provider as Constructor<T>);
|
|
282
|
+
}
|
|
283
|
+
// 实例
|
|
284
|
+
return provider;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
private createInstance<T>(constructor: Constructor<T>): T {
|
|
288
|
+
// 获取构造函数的参数类型(简化实现,实际可能需要反射元数据)
|
|
289
|
+
const paramCount = constructor.length;
|
|
290
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
291
|
+
const args: any[] = [];
|
|
292
|
+
|
|
293
|
+
for (let i = 0; i < paramCount; i++) {
|
|
294
|
+
// 尝试从容器解析参数
|
|
295
|
+
// 这里简化处理,实际应该使用参数装饰器
|
|
296
|
+
args.push(undefined);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
300
|
+
return new constructor(...args);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// 全局容器实例
|
|
305
|
+
export const rootContainer = new Container();
|
|
306
|
+
|
|
307
|
+
// 装饰器辅助函数
|
|
308
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
309
|
+
export function injectable<T extends Constructor>(constructor: T): T {
|
|
310
|
+
// 标记类为可注入
|
|
311
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
312
|
+
(constructor as any).__injectable = true;
|
|
313
|
+
return constructor;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export function inject(token: string | symbol) {
|
|
317
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
318
|
+
return function (target: any, _propertyKey: string | symbol, parameterIndex: number) {
|
|
319
|
+
// 存储注入元数据
|
|
320
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
321
|
+
const existingInjections: (string | symbol)[] =
|
|
322
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
323
|
+
(target.__injections as (string | symbol)[]) || [];
|
|
324
|
+
existingInjections[parameterIndex] = token;
|
|
325
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
326
|
+
target.__injections = existingInjections;
|
|
327
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
328
|
+
return target;
|
|
329
|
+
};
|
|
330
|
+
}
|