@mxweb/classable 1.0.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 +44 -0
- package/LICENSE +21 -0
- package/README.md +337 -0
- package/dist/index.d.ts +225 -0
- package/dist/index.mjs +1 -0
- package/package.json +69 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.0] - 2026-01-17
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Type Utilities**
|
|
13
|
+
- `UnitClass<T>` - Class constructor with no arguments
|
|
14
|
+
- `ClassType<T, Args>` - Class constructor with specific arguments
|
|
15
|
+
- `AbstractClassType<T, Args>` - Abstract class constructor
|
|
16
|
+
- `AnyClass<T>` - Class constructor with any arguments
|
|
17
|
+
- `AnyAbstractClass<T>` - Abstract class with any arguments
|
|
18
|
+
- `AnyConstructor` - Union type for any constructor
|
|
19
|
+
- `Classable<T, Args, Runtime>` - Class or resolver configuration
|
|
20
|
+
- `ClassableByResolver<T, Args, Runtime>` - Resolver configuration interface
|
|
21
|
+
- `ThisExtended<Extend>` - Utility for extending Placeholder
|
|
22
|
+
- `StaticExtended<Extend, T, Args>` - Utility for static class extension
|
|
23
|
+
|
|
24
|
+
- **classable API**
|
|
25
|
+
- `classable.is(fn)` - Type guard for class constructors
|
|
26
|
+
- `classable.isAbstract(fn)` - Type guard for abstract classes
|
|
27
|
+
- `classable.isResolver(obj)` - Type guard for resolver objects
|
|
28
|
+
- `classable.create(cls, runtime?)` - Instance creation with sync/async resolver support
|
|
29
|
+
- `classable.toResolver(cls)` - Convert class to resolver format
|
|
30
|
+
- `classable.withResolve(base, resolve)` - Create resolver with custom resolve function
|
|
31
|
+
- `classable.wrap(cls, wrapper)` - Apply decorators/middleware to classes
|
|
32
|
+
- `classable.getTarget(cls)` - Extract target class from Classable
|
|
33
|
+
- `classable.getDescriptor(cls)` - Get metadata (type and target name)
|
|
34
|
+
|
|
35
|
+
- **Utilities**
|
|
36
|
+
- `Placeholder` class - Marker for unresolved bindings
|
|
37
|
+
- `placeholder` - Pre-configured placeholder resolver
|
|
38
|
+
|
|
39
|
+
- **Documentation**
|
|
40
|
+
- Full JSDoc documentation for all types and methods
|
|
41
|
+
- Comprehensive README with examples
|
|
42
|
+
- MIT License
|
|
43
|
+
|
|
44
|
+
[1.0.0]: https://github.com/mxwebio/mxweb-classable/releases/tag/v1.0.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 MXWeb
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
# @mxweb/classable
|
|
2
|
+
|
|
3
|
+
A TypeScript utility library for working with classes and deferred instantiation through resolver patterns. Provides type-safe class manipulation, dependency injection patterns, and lazy initialization support.
|
|
4
|
+
|
|
5
|
+
## ESM-only (by design)
|
|
6
|
+
|
|
7
|
+
Classable relies on the semantic identity of classes:
|
|
8
|
+
|
|
9
|
+
- native `class` syntax
|
|
10
|
+
- stable constructor identity
|
|
11
|
+
- intact static surfaces
|
|
12
|
+
- predictable module graph
|
|
13
|
+
|
|
14
|
+
CommonJS transforms classes and breaks these guarantees at runtime.
|
|
15
|
+
|
|
16
|
+
- This is not a compatibility limitation.
|
|
17
|
+
- It is a design requirement.
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @mxweb/classable
|
|
23
|
+
# or
|
|
24
|
+
yarn add @mxweb/classable
|
|
25
|
+
# or
|
|
26
|
+
pnpm add @mxweb/classable
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Features
|
|
30
|
+
|
|
31
|
+
- 🎯 **Type-safe class utilities** - Full TypeScript support with precise type inference
|
|
32
|
+
- 🔄 **Resolver pattern** - Separate class instantiation from argument resolution
|
|
33
|
+
- ⏳ **Async support** - Handle async dependency resolution seamlessly
|
|
34
|
+
- 🎁 **Class wrapping** - Apply decorators, mixins, and middleware to classes
|
|
35
|
+
- 🔍 **Type guards** - Runtime checks with TypeScript type narrowing
|
|
36
|
+
- 📦 **Zero dependencies** - Lightweight and self-contained
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { classable, Classable, ClassableByResolver } from '@mxweb/classable';
|
|
42
|
+
|
|
43
|
+
// Basic class instantiation
|
|
44
|
+
class Logger {
|
|
45
|
+
log(msg: string) {
|
|
46
|
+
console.log(msg);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const logger = classable.create(Logger);
|
|
51
|
+
logger.log('Hello!');
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Core Concepts
|
|
55
|
+
|
|
56
|
+
### Classable
|
|
57
|
+
|
|
58
|
+
A `Classable` is either a plain class constructor or a resolver configuration object:
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { Classable, ClassableByResolver } from '@mxweb/classable';
|
|
62
|
+
|
|
63
|
+
class User {
|
|
64
|
+
constructor(public name: string, public age: number) {}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Plain class
|
|
68
|
+
const cls: Classable<User> = User;
|
|
69
|
+
|
|
70
|
+
// Resolver with dependency injection
|
|
71
|
+
const resolver: ClassableByResolver<User, [string, number], AppContext> = {
|
|
72
|
+
target: User,
|
|
73
|
+
resolve: (ctx) => [ctx.config.userName, ctx.config.userAge]
|
|
74
|
+
};
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### ClassableByResolver
|
|
78
|
+
|
|
79
|
+
A configuration object that separates class instantiation from argument resolution:
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
interface ClassableByResolver<InstanceType, Args, Runtime> {
|
|
83
|
+
target: ClassType<InstanceType, Args>; // The class to instantiate
|
|
84
|
+
resolve: (runtime: Runtime) => Args | Promise<Args>; // Argument resolver
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## API Reference
|
|
89
|
+
|
|
90
|
+
### Type Utilities
|
|
91
|
+
|
|
92
|
+
| Type | Description |
|
|
93
|
+
|------|-------------|
|
|
94
|
+
| `UnitClass<T>` | Class constructor with no arguments |
|
|
95
|
+
| `ClassType<T, Args>` | Class constructor with specific arguments |
|
|
96
|
+
| `AbstractClassType<T, Args>` | Abstract class constructor |
|
|
97
|
+
| `AnyClass<T>` | Class constructor with any arguments |
|
|
98
|
+
| `AnyAbstractClass<T>` | Abstract class with any arguments |
|
|
99
|
+
| `AnyConstructor` | Any class or abstract class |
|
|
100
|
+
| `Classable<T, Args, Runtime>` | Class or resolver configuration |
|
|
101
|
+
| `ClassableByResolver<T, Args, Runtime>` | Resolver configuration |
|
|
102
|
+
|
|
103
|
+
### `classable` API
|
|
104
|
+
|
|
105
|
+
#### `classable.is(fn)`
|
|
106
|
+
|
|
107
|
+
Checks if a value is a class constructor.
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
class MyClass {}
|
|
111
|
+
classable.is(MyClass); // true
|
|
112
|
+
classable.is(() => {}); // false
|
|
113
|
+
classable.is({}); // false
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
#### `classable.isAbstract(fn)`
|
|
117
|
+
|
|
118
|
+
Checks if a value is an abstract class constructor.
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
abstract class BaseService {}
|
|
122
|
+
classable.isAbstract(BaseService); // true
|
|
123
|
+
classable.isAbstract(MyClass); // false
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
#### `classable.isResolver(obj)`
|
|
127
|
+
|
|
128
|
+
Checks if a value is a `ClassableByResolver` object.
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
const resolver = { target: User, resolve: () => ['John', 30] };
|
|
132
|
+
classable.isResolver(resolver); // true
|
|
133
|
+
classable.isResolver(User); // false
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
#### `classable.create(cls, runtime?)`
|
|
137
|
+
|
|
138
|
+
Creates an instance from a class or resolver. Handles both sync and async resolvers.
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
// Plain class
|
|
142
|
+
const logger = classable.create(Logger);
|
|
143
|
+
|
|
144
|
+
// Sync resolver
|
|
145
|
+
const user = classable.create({
|
|
146
|
+
target: User,
|
|
147
|
+
resolve: (ctx) => [ctx.name, ctx.age]
|
|
148
|
+
}, context);
|
|
149
|
+
|
|
150
|
+
// Async resolver
|
|
151
|
+
const user = await classable.create({
|
|
152
|
+
target: User,
|
|
153
|
+
resolve: async (ctx) => {
|
|
154
|
+
const data = await fetchUserData();
|
|
155
|
+
return [data.name, data.age];
|
|
156
|
+
}
|
|
157
|
+
}, context);
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
#### `classable.toResolver(cls)`
|
|
161
|
+
|
|
162
|
+
Converts a class constructor to a resolver configuration.
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
const resolver = classable.toResolver(User);
|
|
166
|
+
// { target: User, resolve: () => [] }
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
#### `classable.getTarget(cls)`
|
|
170
|
+
|
|
171
|
+
Extracts the target class from a Classable.
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
const target = classable.getTarget(resolver); // User class
|
|
175
|
+
const target2 = classable.getTarget(User); // User class
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
#### `classable.withResolve(base, resolve)`
|
|
179
|
+
|
|
180
|
+
Creates a new resolver with a custom resolve function.
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
const customResolver = classable.withResolve(User, (ctx) => {
|
|
184
|
+
return [ctx.name, ctx.age];
|
|
185
|
+
});
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
#### `classable.wrap(cls, wrapper)`
|
|
189
|
+
|
|
190
|
+
Wraps a class or resolver's target with a transformation function.
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
const timestampedLogger = classable.wrap(Logger, (Target) => {
|
|
194
|
+
return class extends Target {
|
|
195
|
+
log(msg: string) {
|
|
196
|
+
super.log(`[${new Date().toISOString()}] ${msg}`);
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
});
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
#### `classable.getDescriptor(cls)`
|
|
203
|
+
|
|
204
|
+
Returns metadata about the classable.
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
classable.getDescriptor(User);
|
|
208
|
+
// { type: "class", target: "User" }
|
|
209
|
+
|
|
210
|
+
classable.getDescriptor(resolver);
|
|
211
|
+
// { type: "resolver", target: "User" }
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Placeholder
|
|
215
|
+
|
|
216
|
+
A utility class for marking unresolved bindings:
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
import { classable } from '@mxweb/classable';
|
|
220
|
+
|
|
221
|
+
// Use as default value
|
|
222
|
+
class Container {
|
|
223
|
+
private bindings = new Map();
|
|
224
|
+
|
|
225
|
+
register(key: string, cls = classable.placeholder) {
|
|
226
|
+
this.bindings.set(key, cls);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Advanced Usage
|
|
232
|
+
|
|
233
|
+
### Dependency Injection Pattern
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
interface AppContext {
|
|
237
|
+
db: Database;
|
|
238
|
+
config: Config;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
class UserService {
|
|
242
|
+
constructor(
|
|
243
|
+
private db: Database,
|
|
244
|
+
private maxUsers: number
|
|
245
|
+
) {}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const userServiceResolver: ClassableByResolver<
|
|
249
|
+
UserService,
|
|
250
|
+
[Database, number],
|
|
251
|
+
AppContext
|
|
252
|
+
> = {
|
|
253
|
+
target: UserService,
|
|
254
|
+
resolve: (ctx) => [ctx.db, ctx.config.maxUsers]
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
// Create with context
|
|
258
|
+
const context: AppContext = { db: new Database(), config: { maxUsers: 100 } };
|
|
259
|
+
const service = classable.create(userServiceResolver, context);
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Async Dependency Resolution
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
const asyncResolver: ClassableByResolver<User, [string], DbContext> = {
|
|
266
|
+
target: User,
|
|
267
|
+
resolve: async (ctx) => {
|
|
268
|
+
const userData = await ctx.db.fetchUser(ctx.userId);
|
|
269
|
+
return [userData.name];
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
// Returns Promise<User>
|
|
274
|
+
const user = await classable.create(asyncResolver, dbContext);
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Class Middleware/Decorators
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
// Add logging to all methods
|
|
281
|
+
const withLogging = classable.wrap(MyService, (Target) => {
|
|
282
|
+
return class extends Target {
|
|
283
|
+
constructor(...args: any[]) {
|
|
284
|
+
super(...args);
|
|
285
|
+
console.log(`Created ${Target.name}`);
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
// Chain multiple wrappers
|
|
291
|
+
const enhanced = classable.wrap(
|
|
292
|
+
classable.wrap(MyService, withLogging),
|
|
293
|
+
withMetrics
|
|
294
|
+
);
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Type Guards with Narrowing
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
function processClassable(input: unknown) {
|
|
301
|
+
if (classable.isResolver(input)) {
|
|
302
|
+
// TypeScript knows input has 'target' and 'resolve'
|
|
303
|
+
console.log(`Resolver for: ${input.target.name}`);
|
|
304
|
+
} else if (classable.is(input)) {
|
|
305
|
+
// TypeScript knows input is a class
|
|
306
|
+
console.log(`Class: ${input.name}`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
## TypeScript Support
|
|
312
|
+
|
|
313
|
+
This library is written in TypeScript and provides full type inference:
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
// Types are automatically inferred
|
|
317
|
+
const resolver = {
|
|
318
|
+
target: User,
|
|
319
|
+
resolve: (ctx: AppContext) => [ctx.name, ctx.age] as [string, number]
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
// Return type is correctly inferred as User
|
|
323
|
+
const user = classable.create(resolver, context);
|
|
324
|
+
|
|
325
|
+
// Async resolver returns Promise<User>
|
|
326
|
+
const asyncResolver = {
|
|
327
|
+
target: User,
|
|
328
|
+
resolve: async (ctx: AppContext) => {
|
|
329
|
+
return [await getName(), await getAge()] as [string, number];
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
const asyncUser = classable.create(asyncResolver, context); // Promise<User>
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
## License
|
|
336
|
+
|
|
337
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a class constructor that takes no arguments.
|
|
3
|
+
* @template InstanceType - The type of instance created by this constructor.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```typescript
|
|
7
|
+
* class Logger {}
|
|
8
|
+
* const LoggerClass: UnitClass<Logger> = Logger;
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
export type UnitClass<InstanceType> = new () => InstanceType;
|
|
12
|
+
/**
|
|
13
|
+
* Represents a class constructor with specific constructor arguments.
|
|
14
|
+
* @template InstanceType - The type of instance created by this constructor.
|
|
15
|
+
* @template Args - Tuple type of constructor arguments, defaults to empty array.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* class User {
|
|
20
|
+
* constructor(name: string, age: number) {}
|
|
21
|
+
* }
|
|
22
|
+
* const UserClass: ClassType<User, [string, number]> = User;
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export type ClassType<InstanceType, Args extends any[] = []> = new (...args: Args) => InstanceType;
|
|
26
|
+
/**
|
|
27
|
+
* Represents an abstract class constructor.
|
|
28
|
+
* @template InstanceType - The type of instance this abstract class represents.
|
|
29
|
+
* @template Args - Tuple type of constructor arguments, defaults to empty array.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* abstract class BaseService {
|
|
34
|
+
* abstract execute(): void;
|
|
35
|
+
* }
|
|
36
|
+
* const ServiceClass: AbstractClassType<BaseService> = BaseService;
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export type AbstractClassType<InstanceType, Args extends any[] = []> = abstract new (...args: Args) => InstanceType;
|
|
40
|
+
/**
|
|
41
|
+
* Shorthand for a class constructor with any arguments.
|
|
42
|
+
* @template T - The type of instance created by this constructor.
|
|
43
|
+
*/
|
|
44
|
+
export type AnyClass<T> = ClassType<T, any[]>;
|
|
45
|
+
/**
|
|
46
|
+
* Shorthand for an abstract class constructor with any arguments.
|
|
47
|
+
* @template T - The type of instance this abstract class represents.
|
|
48
|
+
*/
|
|
49
|
+
export type AnyAbstractClass<T> = AbstractClassType<T, any[]>;
|
|
50
|
+
/**
|
|
51
|
+
* Union type representing any constructor (class or abstract class).
|
|
52
|
+
*/
|
|
53
|
+
export type AnyConstructor = ClassType<any, any[]> | AbstractClassType<any, any[]>;
|
|
54
|
+
/**
|
|
55
|
+
* Configuration object for deferred class instantiation with dependency resolution.
|
|
56
|
+
*
|
|
57
|
+
* This interface allows you to separate class instantiation from argument resolution,
|
|
58
|
+
* enabling lazy evaluation and runtime dependency injection.
|
|
59
|
+
*
|
|
60
|
+
* @template InstanceType - The type of instance to be created.
|
|
61
|
+
* @template Args - Tuple type of constructor arguments.
|
|
62
|
+
* @template Runtime - Optional runtime context type passed to the resolver.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```typescript
|
|
66
|
+
* // Sync resolver
|
|
67
|
+
* const userResolver: ClassableByResolver<User, [string, number], AppContext> = {
|
|
68
|
+
* target: User,
|
|
69
|
+
* resolve: (ctx) => [ctx.config.name, ctx.config.age]
|
|
70
|
+
* };
|
|
71
|
+
*
|
|
72
|
+
* // Async resolver
|
|
73
|
+
* const asyncResolver: ClassableByResolver<User, [string], DbContext> = {
|
|
74
|
+
* target: User,
|
|
75
|
+
* resolve: async (ctx) => {
|
|
76
|
+
* const name = await ctx.db.fetchName();
|
|
77
|
+
* return [name];
|
|
78
|
+
* }
|
|
79
|
+
* };
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
export interface ClassableByResolver<InstanceType, Args extends any[] = [], Runtime = never> {
|
|
83
|
+
/** The target class constructor to instantiate. */
|
|
84
|
+
target: ClassType<InstanceType, Args>;
|
|
85
|
+
/**
|
|
86
|
+
* Function that resolves constructor arguments.
|
|
87
|
+
* Can return arguments synchronously or as a Promise.
|
|
88
|
+
*/
|
|
89
|
+
resolve: (...args: Runtime extends never ? [] : [runtime: Runtime]) => Args | Promise<Args>;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Union type representing either a direct class constructor or a resolver configuration.
|
|
93
|
+
*
|
|
94
|
+
* Use this type when a function accepts both plain classes and resolver objects.
|
|
95
|
+
*
|
|
96
|
+
* @template InstanceType - The type of instance to be created.
|
|
97
|
+
* @template Args - Tuple type of constructor arguments.
|
|
98
|
+
* @template Runtime - Optional runtime context type for resolvers.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```typescript
|
|
102
|
+
* function register<T>(cls: Classable<T>) {
|
|
103
|
+
* // Accepts both User and { target: User, resolve: () => [...] }
|
|
104
|
+
* }
|
|
105
|
+
*
|
|
106
|
+
* register(User);
|
|
107
|
+
* register({ target: User, resolve: () => ["John", 30] });
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
export type Classable<InstanceType, Args extends any[] = [], Runtime = never> = ClassType<InstanceType, Args> | ClassableByResolver<InstanceType, Args, Runtime>;
|
|
111
|
+
/**
|
|
112
|
+
* A placeholder class used as a marker for unresolved or pending class registrations.
|
|
113
|
+
* Useful in dependency injection containers or lazy initialization patterns.
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```typescript
|
|
117
|
+
* class Container {
|
|
118
|
+
* private bindings = new Map<string, Classable<any>>();
|
|
119
|
+
*
|
|
120
|
+
* register(key: string, cls: Classable<any> = classable.placeholder) {
|
|
121
|
+
* this.bindings.set(key, cls);
|
|
122
|
+
* }
|
|
123
|
+
* }
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
export declare class Placeholder {
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Pre-configured placeholder resolver that creates an empty Placeholder instance.
|
|
130
|
+
* Use this as a default value when a classable binding is not yet configured.
|
|
131
|
+
*/
|
|
132
|
+
export declare const placeholder: Readonly<{
|
|
133
|
+
target: typeof Placeholder;
|
|
134
|
+
resolve: () => readonly [];
|
|
135
|
+
}>;
|
|
136
|
+
/**
|
|
137
|
+
* Utility type for extending the Placeholder class in type definitions.
|
|
138
|
+
* Useful for creating typed placeholders with additional properties.
|
|
139
|
+
*
|
|
140
|
+
* @template Extend - Additional properties to add to the Placeholder type.
|
|
141
|
+
*/
|
|
142
|
+
export type ThisExtended<Extend> = Placeholder & Extend;
|
|
143
|
+
/**
|
|
144
|
+
* Utility type for creating a static class type with additional properties.
|
|
145
|
+
*
|
|
146
|
+
* @template Extend - Static properties to add to the class.
|
|
147
|
+
* @template InstanceType - The instance type of the class.
|
|
148
|
+
* @template Args - Constructor argument types.
|
|
149
|
+
*/
|
|
150
|
+
export type StaticExtended<Extend, InstanceType = any, Args extends any[] = []> = ClassType<InstanceType, Args> & Extend;
|
|
151
|
+
/**
|
|
152
|
+
* Interface defining all available methods on the `classable` API object.
|
|
153
|
+
* This provides type-safe access to class manipulation utilities.
|
|
154
|
+
*/
|
|
155
|
+
export interface ClassableAPI {
|
|
156
|
+
/** Reference to the Placeholder class constructor. */
|
|
157
|
+
Placeholder: ClassType<Placeholder>;
|
|
158
|
+
/** Pre-configured placeholder resolver instance. */
|
|
159
|
+
placeholder: ClassableByResolver<Placeholder>;
|
|
160
|
+
/** Checks if a value is a class constructor. */
|
|
161
|
+
is: (fn: unknown) => fn is AnyClass<any>;
|
|
162
|
+
/** Checks if a value is an abstract class constructor. */
|
|
163
|
+
isAbstract: (fn: unknown) => fn is AnyAbstractClass<any>;
|
|
164
|
+
/** Checks if a value is a ClassableByResolver object. */
|
|
165
|
+
isResolver: <InstanceType, Args extends any[] = [], Runtime = never>(obj: unknown) => obj is ClassableByResolver<InstanceType, Args, Runtime>;
|
|
166
|
+
/** Converts a class constructor to a resolver configuration. */
|
|
167
|
+
toResolver<T, A extends any[] = [], R = never>(cls: ClassType<T, A>, runtime?: R): ClassableByResolver<T, [], R>;
|
|
168
|
+
/** Extracts the target class from a Classable (class or resolver). */
|
|
169
|
+
getTarget: <Target>(cls: Classable<Target, any[], any>) => Target;
|
|
170
|
+
/** Creates a new resolver with a custom resolve function. */
|
|
171
|
+
withResolve: <InstanceType, Args extends any[] = [], Runtime = never>(base: Classable<InstanceType, Args, Runtime>, resolve: ClassableByResolver<InstanceType, Args, Runtime>["resolve"]) => ClassableByResolver<InstanceType, Args, Runtime>;
|
|
172
|
+
/** Wraps a class with a transformation function. */
|
|
173
|
+
wrap<T>(cls: ClassType<T>, wrapper: (target: ClassType<T>) => ClassType<T>): ClassType<T>;
|
|
174
|
+
/** Wraps a resolver's target with a transformation function. */
|
|
175
|
+
wrap<T, A extends any[], R>(cls: ClassableByResolver<T, A, R>, wrapper: (target: ClassType<T>) => ClassType<T>): ClassableByResolver<T, A, R>;
|
|
176
|
+
/** Returns metadata about the classable (type and target name). */
|
|
177
|
+
getDescriptor: (cls: Classable<any, any[], any>) => {
|
|
178
|
+
type: "class" | "resolver";
|
|
179
|
+
target: string;
|
|
180
|
+
};
|
|
181
|
+
/** Creates an instance from a plain class. */
|
|
182
|
+
create<InstanceType>(cls: ClassType<InstanceType>): InstanceType;
|
|
183
|
+
/** Creates an instance from a resolver with sync resolve function. */
|
|
184
|
+
create<InstanceType, Args extends any[], Runtime>(cls: ClassableByResolver<InstanceType, Args, Runtime> & {
|
|
185
|
+
resolve: (runtime: Runtime) => Args;
|
|
186
|
+
}, runtime: Runtime): InstanceType;
|
|
187
|
+
/** Creates an instance from a resolver with async resolve function. */
|
|
188
|
+
create<InstanceType, Args extends any[], Runtime>(cls: ClassableByResolver<InstanceType, Args, Runtime> & {
|
|
189
|
+
resolve: (runtime: Runtime) => Promise<Args>;
|
|
190
|
+
}, runtime: Runtime): Promise<InstanceType>;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* The main classable API object providing utilities for working with classes and resolvers.
|
|
194
|
+
*
|
|
195
|
+
* This frozen object contains methods for:
|
|
196
|
+
* - Type checking (`is`, `isAbstract`, `isResolver`)
|
|
197
|
+
* - Converting between formats (`toResolver`, `withResolve`)
|
|
198
|
+
* - Instance creation (`create`)
|
|
199
|
+
* - Class manipulation (`wrap`, `getTarget`, `getDescriptor`)
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* ```typescript
|
|
203
|
+
* // Check if something is a class
|
|
204
|
+
* if (classable.is(MyClass)) {
|
|
205
|
+
* const instance = classable.create(MyClass);
|
|
206
|
+
* }
|
|
207
|
+
*
|
|
208
|
+
* // Create instance with resolver
|
|
209
|
+
* const resolver = {
|
|
210
|
+
* target: User,
|
|
211
|
+
* resolve: (ctx: AppContext) => [ctx.userName, ctx.userAge]
|
|
212
|
+
* };
|
|
213
|
+
* const user = classable.create(resolver, appContext);
|
|
214
|
+
*
|
|
215
|
+
* // Wrap a class with middleware
|
|
216
|
+
* const wrapped = classable.wrap(Logger, (Target) => {
|
|
217
|
+
* return class extends Target {
|
|
218
|
+
* log(msg: string) {
|
|
219
|
+
* super.log(`[${Date.now()}] ${msg}`);
|
|
220
|
+
* }
|
|
221
|
+
* };
|
|
222
|
+
* });
|
|
223
|
+
* ```
|
|
224
|
+
*/
|
|
225
|
+
export declare const classable: Readonly<ClassableAPI>;
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function e(e,t){return"object"==typeof e&&null!==e&&Object.prototype.hasOwnProperty.call(e,t)}class t{}const r=Object.freeze({target:t,resolve:()=>[]}),o=Object.freeze({Placeholder:t,placeholder:r,is:e=>"function"==typeof e&&/^class\s/.test(Function.prototype.toString.call(e)),isAbstract:e=>"function"==typeof e&&/^abstract\s+class\s/.test(Function.prototype.toString.call(e)),isResolver:t=>e(t,"target")&&o.is(t.target)&&e(t,"resolve")&&"function"==typeof t.resolve,toResolver:e=>o.isResolver(e)?e:{target:e,resolve:()=>[]},create:(e,t)=>{if(o.isResolver(e)){const r=e.resolve(...void 0===t?[]:[t]);return r instanceof Promise?r.then(t=>new e.target(...t)):new e.target(...r)}return new e(...[])},getTarget:e=>o.isResolver(e)?e.target:e,withResolve:(e,t)=>({target:(o.isResolver(e)?e:o.toResolver(e)).target,resolve:t}),wrap:(e,t)=>o.isResolver(e)?{target:t(e.target),resolve:e.resolve}:t(e),getDescriptor:e=>o.isResolver(e)?{type:"resolver",target:e.target.name}:{type:"class",target:e.name}});export{t as Placeholder,o as classable,r as placeholder};
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mxweb/classable",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A class-first abstraction for defining logic as resolvable units without runtime assumptions.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"classable",
|
|
8
|
+
"class-first",
|
|
9
|
+
"logic-units",
|
|
10
|
+
"declarative",
|
|
11
|
+
"resolver",
|
|
12
|
+
"meta-programming",
|
|
13
|
+
"framework-agnostic",
|
|
14
|
+
"esm-only"
|
|
15
|
+
],
|
|
16
|
+
"type": "module",
|
|
17
|
+
"module": "dist/index.js",
|
|
18
|
+
"types": "dist/index.d.ts",
|
|
19
|
+
"author": "MxWeb Team <mxwebio@gmail.com>",
|
|
20
|
+
"homepage": "https://edge.mxweb.io/classable",
|
|
21
|
+
"sideEffects": false,
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"url": "https://github.com/mxwebio/mxweb-classable"
|
|
27
|
+
},
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"import": "./dist/index.js",
|
|
31
|
+
"types": "./dist/index.d.ts"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"clean": "rimraf dist",
|
|
36
|
+
"build": "yarn clean && yarn lint && rollup -c",
|
|
37
|
+
"build:watch": "rollup -c -w",
|
|
38
|
+
"lint": "eslint \"src/**/*.{ts,tsx}\"",
|
|
39
|
+
"lint:fix": "eslint \"src/**/*.{ts,tsx}\" --fix",
|
|
40
|
+
"format": "prettier --write \"src/**/*.{ts,tsx,json,md}\"",
|
|
41
|
+
"format:check": "prettier --check \"src/**/*.{ts,tsx,json,md}\""
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@babel/core": "^7.28.6",
|
|
45
|
+
"@rollup/plugin-terser": "^0.4.4",
|
|
46
|
+
"@rollup/plugin-typescript": "^12.3.0",
|
|
47
|
+
"@types/node": "^20",
|
|
48
|
+
"@typescript-eslint/eslint-plugin": "^8.53.0",
|
|
49
|
+
"@typescript-eslint/parser": "^8.53.0",
|
|
50
|
+
"eslint": "^9.39.2",
|
|
51
|
+
"eslint-config-prettier": "^10.1.8",
|
|
52
|
+
"eslint-plugin-prettier": "^5.5.5",
|
|
53
|
+
"glob": "^13.0.0",
|
|
54
|
+
"prettier": "^3.8.0",
|
|
55
|
+
"rimraf": "^6.1.2",
|
|
56
|
+
"rollup": "^4.55.1",
|
|
57
|
+
"tslib": "^2.8.1",
|
|
58
|
+
"typescript": "^5"
|
|
59
|
+
},
|
|
60
|
+
"engines": {
|
|
61
|
+
"node": ">=14.0.0"
|
|
62
|
+
},
|
|
63
|
+
"files": [
|
|
64
|
+
"dist",
|
|
65
|
+
"README.md",
|
|
66
|
+
"CHANGELOG.md",
|
|
67
|
+
"LICENSE"
|
|
68
|
+
]
|
|
69
|
+
}
|