@mxweb/classable 1.0.0 → 1.1.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 CHANGED
@@ -5,6 +5,45 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.1.1] - 2026-01-18
9
+
10
+ ### Added
11
+
12
+ - **Type Utilities**
13
+ - `SyncClassableByResolver<T, Args, Runtime>` - Specialized resolver interface with synchronous `resolve` function, enables correct type inference for sync instantiation
14
+ - `AsyncClassableByResolver<T, Args, Runtime>` - Specialized resolver interface with asynchronous `resolve` function, enables correct type inference for async instantiation
15
+
16
+ ### Fixed
17
+
18
+ - **classable.create() type overloads**
19
+ - Added missing overload for `Classable<InstanceType, []>` to properly handle resolvers with empty args and no runtime
20
+ - Fixed return type inference: now correctly returns `InstanceType` for sync resolvers and `Promise<InstanceType>` for async resolvers
21
+ - Improved overload ordering for better TypeScript type narrowing
22
+
23
+ ## [1.1.0] - 2026-01-17
24
+
25
+ ### Added
26
+
27
+ - **Type Utilities**
28
+ - `Readonlyable<T>` - Utility type that accepts both mutable and readonly versions, useful for `Object.freeze()` or `as const`
29
+ - `InstanceByStatic<T, Method, Args, Runtime>` - Type for classes using static factory methods instead of direct constructor instantiation
30
+ - `ClassableSelector<T, Args, Runtime>` - Selector function type for choosing a classable from a list with custom logic
31
+
32
+ - **classable API**
33
+ - `classable.from(def, runtime?)` - Create an instance via a static factory method definition
34
+ - `classable.select(selector)` - Higher-order function to create a selector that picks a classable based on custom criteria
35
+ - `classable.placeholderInstance` - Pre-configured placeholder using InstanceByStatic pattern
36
+
37
+ - **Placeholder**
38
+ - Added `Placeholder.getInstance()` static method for factory pattern support
39
+
40
+ ### Changed
41
+
42
+ - Updated `Args` type parameter across all APIs to use `Readonlyable<any[]>` for flexible readonly/mutable array support
43
+ - `ClassableByResolver.target` now uses `[...Args]` spread to convert readonly arrays to mutable
44
+ - `Classable` type now properly handles readonly arrays
45
+ - `StaticExtended` type updated to support `Readonlyable<any[]>`
46
+
8
47
  ## [1.0.0] - 2026-01-17
9
48
 
10
49
  ### Added
@@ -41,4 +80,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
41
80
  - Comprehensive README with examples
42
81
  - MIT License
43
82
 
83
+ [1.1.0]: https://github.com/mxwebio/mxweb-classable/releases/tag/v1.1.0
44
84
  [1.0.0]: https://github.com/mxwebio/mxweb-classable/releases/tag/v1.0.0
package/README.md CHANGED
@@ -85,12 +85,30 @@ interface ClassableByResolver<InstanceType, Args, Runtime> {
85
85
  }
86
86
  ```
87
87
 
88
+ ### Readonlyable
89
+
90
+ All `Args` type parameters accept both mutable and readonly arrays, so you can use `Object.freeze()` or `as const` without type errors:
91
+
92
+ ```typescript
93
+ // Both work seamlessly
94
+ const mutableResolver = {
95
+ target: User,
96
+ resolve: () => ['John', 30] // mutable array
97
+ };
98
+
99
+ const frozenResolver = Object.freeze({
100
+ target: User,
101
+ resolve: () => ['John', 30] as const // readonly array
102
+ });
103
+ ```
104
+
88
105
  ## API Reference
89
106
 
90
107
  ### Type Utilities
91
108
 
92
109
  | Type | Description |
93
110
  |------|-------------|
111
+ | `Readonlyable<T>` | Accepts both mutable and readonly versions of a type |
94
112
  | `UnitClass<T>` | Class constructor with no arguments |
95
113
  | `ClassType<T, Args>` | Class constructor with specific arguments |
96
114
  | `AbstractClassType<T, Args>` | Abstract class constructor |
@@ -99,6 +117,8 @@ interface ClassableByResolver<InstanceType, Args, Runtime> {
99
117
  | `AnyConstructor` | Any class or abstract class |
100
118
  | `Classable<T, Args, Runtime>` | Class or resolver configuration |
101
119
  | `ClassableByResolver<T, Args, Runtime>` | Resolver configuration |
120
+ | `InstanceByStatic<T, Method, Args, Runtime>` | Static factory method pattern |
121
+ | `ClassableSelector<T, Args, Runtime>` | Selector function for choosing classables |
102
122
 
103
123
  ### `classable` API
104
124
 
@@ -211,14 +231,57 @@ classable.getDescriptor(resolver);
211
231
  // { type: "resolver", target: "User" }
212
232
  ```
213
233
 
234
+ #### `classable.from(def, runtime?)`
235
+
236
+ Creates an instance from a static factory method definition.
237
+
238
+ ```typescript
239
+ class Cache {
240
+ private constructor(private ttl: number) {}
241
+
242
+ static create(ttl: number): Cache {
243
+ return new Cache(ttl);
244
+ }
245
+ }
246
+
247
+ const cacheInstance = classable.from({
248
+ target: Cache,
249
+ selector: () => ({ method: "create", args: [3600] })
250
+ });
251
+
252
+ // With runtime context
253
+ const dynamicCache = classable.from({
254
+ target: Cache,
255
+ selector: (ctx) => ({ method: "create", args: [ctx.cacheTTL] })
256
+ }, appContext);
257
+ ```
258
+
259
+ #### `classable.select(selector)`
260
+
261
+ Creates a selector function that chooses a classable from a list based on custom logic.
262
+
263
+ ```typescript
264
+ // Simple selector
265
+ const pickFirst = classable.select((...classes) => {
266
+ return [classes[0], []];
267
+ });
268
+ const [selected, args] = pickFirst(ServiceA, ServiceB);
269
+
270
+ // With runtime context
271
+ const pickByEnv = classable.select<Logger, [], Env>((env, ...loggers) => {
272
+ return env.isDev ? [loggers[0], []] : [loggers[1], []];
273
+ });
274
+ const [logger, loggerArgs] = pickByEnv(devEnv, DevLogger, ProdLogger);
275
+ ```
276
+
214
277
  ### Placeholder
215
278
 
216
- A utility class for marking unresolved bindings:
279
+ Utility classes for marking unresolved bindings:
217
280
 
218
281
  ```typescript
219
282
  import { classable } from '@mxweb/classable';
220
283
 
221
- // Use as default value
284
+ // Use placeholder resolver as default value
222
285
  class Container {
223
286
  private bindings = new Map();
224
287
 
@@ -226,10 +289,51 @@ class Container {
226
289
  this.bindings.set(key, cls);
227
290
  }
228
291
  }
292
+
293
+ // Use placeholderInstance for static factory pattern
294
+ const instance = classable.from(classable.placeholderInstance);
295
+ // Returns new Placeholder via getInstance()
229
296
  ```
230
297
 
231
298
  ## Advanced Usage
232
299
 
300
+ ### Static Factory Method Pattern
301
+
302
+ Use `InstanceByStatic` for classes that use static factory methods instead of direct instantiation:
303
+
304
+ ```typescript
305
+ import { classable, InstanceByStatic } from '@mxweb/classable';
306
+
307
+ class Database {
308
+ private constructor(private connectionString: string) {}
309
+
310
+ static connect(connectionString: string): Database {
311
+ return new Database(connectionString);
312
+ }
313
+
314
+ static createInMemory(): Database {
315
+ return new Database(':memory:');
316
+ }
317
+ }
318
+
319
+ // Define static factory configuration
320
+ const dbDef: InstanceByStatic<Database, 'connect', [string]> = {
321
+ target: Database,
322
+ selector: () => ({ method: 'connect', args: ['postgres://localhost'] })
323
+ };
324
+
325
+ // Create instance via factory method
326
+ const db = classable.from(dbDef);
327
+
328
+ // With runtime context for dynamic selection
329
+ const dynamicDb = classable.from({
330
+ target: Database,
331
+ selector: (ctx) => ctx.isTest
332
+ ? { method: 'createInMemory', args: [] }
333
+ : { method: 'connect', args: [ctx.dbUrl] }
334
+ }, appContext);
335
+ ```
336
+
233
337
  ### Dependency Injection Pattern
234
338
 
235
339
  ```typescript
@@ -332,6 +436,12 @@ const asyncResolver = {
332
436
  const asyncUser = classable.create(asyncResolver, context); // Promise<User>
333
437
  ```
334
438
 
439
+ ## Documentation
440
+
441
+ For detailed documentation, guides, and API reference, visit:
442
+
443
+ [https://edge.mxweb.io/classable](https://edge.mxweb.io/classable)
444
+
335
445
  ## License
336
446
 
337
447
  MIT
package/dist/index.d.ts CHANGED
@@ -1,3 +1,21 @@
1
+ /**
2
+ * Utility type that accepts both mutable and readonly versions of a type.
3
+ *
4
+ * This is useful for accepting values from `Object.freeze()` or `as const`
5
+ * without forcing users to always use readonly modifiers.
6
+ *
7
+ * @template T - The base type to make flexible.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * // Accepts both mutable and readonly arrays
12
+ * function process(items: Readonlyable<string[]>) { ... }
13
+ *
14
+ * process(["a", "b"]); // mutable array - OK
15
+ * process(["a", "b"] as const); // readonly array - OK
16
+ * ```
17
+ */
18
+ export type Readonlyable<T> = T extends Readonly<infer U> ? U | Readonly<U> : T | Readonly<T>;
1
19
  /**
2
20
  * Represents a class constructor that takes no arguments.
3
21
  * @template InstanceType - The type of instance created by this constructor.
@@ -79,15 +97,87 @@ export type AnyConstructor = ClassType<any, any[]> | AbstractClassType<any, any[
79
97
  * };
80
98
  * ```
81
99
  */
82
- export interface ClassableByResolver<InstanceType, Args extends any[] = [], Runtime = never> {
100
+ export interface ClassableByResolver<InstanceType, Args extends Readonlyable<any[]> = [], Runtime = never> {
83
101
  /** The target class constructor to instantiate. */
84
- target: ClassType<InstanceType, Args>;
102
+ target: ClassType<InstanceType, [...Args]>;
85
103
  /**
86
104
  * Function that resolves constructor arguments.
87
105
  * Can return arguments synchronously or as a Promise.
88
106
  */
89
107
  resolve: (...args: Runtime extends never ? [] : [runtime: Runtime]) => Args | Promise<Args>;
90
108
  }
109
+ /**
110
+ * A resolver configuration with synchronous argument resolution.
111
+ *
112
+ * This interface is a specialized version of `ClassableByResolver` where the
113
+ * `resolve` function is guaranteed to return arguments synchronously (not a Promise).
114
+ * Use this type when you need TypeScript to correctly infer that `classable.create()`
115
+ * returns `InstanceType` directly instead of `Promise<InstanceType>`.
116
+ *
117
+ * @template InstanceType - The type of instance to be created.
118
+ * @template Args - Tuple type of constructor arguments.
119
+ * @template Runtime - Optional runtime context type passed to the resolver.
120
+ *
121
+ * @example
122
+ * ```typescript
123
+ * // Without runtime context
124
+ * const simpleResolver: SyncClassableByResolver<User, [string, number]> = {
125
+ * target: User,
126
+ * resolve: () => ["John", 30]
127
+ * };
128
+ * const user = classable.create(simpleResolver); // Type: User
129
+ *
130
+ * // With runtime context
131
+ * const contextResolver: SyncClassableByResolver<User, [string], AppContext> = {
132
+ * target: User,
133
+ * resolve: (ctx) => [ctx.userName]
134
+ * };
135
+ * const user2 = classable.create(contextResolver, appContext); // Type: User
136
+ * ```
137
+ */
138
+ export interface SyncClassableByResolver<InstanceType, Args extends Readonlyable<any[]> = [], Runtime = never> {
139
+ target: ClassType<InstanceType, [...Args]>;
140
+ resolve: (...args: Runtime extends never ? [] : [runtime: Runtime]) => Args;
141
+ }
142
+ /**
143
+ * A resolver configuration with asynchronous argument resolution.
144
+ *
145
+ * This interface is a specialized version of `ClassableByResolver` where the
146
+ * `resolve` function is guaranteed to return a Promise. Use this type when you
147
+ * need TypeScript to correctly infer that `classable.create()` returns
148
+ * `Promise<InstanceType>` instead of `InstanceType`.
149
+ *
150
+ * @template InstanceType - The type of instance to be created.
151
+ * @template Args - Tuple type of constructor arguments.
152
+ * @template Runtime - Optional runtime context type passed to the resolver.
153
+ *
154
+ * @example
155
+ * ```typescript
156
+ * // Without runtime context
157
+ * const asyncResolver: AsyncClassableByResolver<User, [string]> = {
158
+ * target: User,
159
+ * resolve: async () => {
160
+ * const name = await fetchUserName();
161
+ * return [name];
162
+ * }
163
+ * };
164
+ * const user = await classable.create(asyncResolver); // Type: Promise<User>
165
+ *
166
+ * // With runtime context
167
+ * const dbResolver: AsyncClassableByResolver<User, [string], DbContext> = {
168
+ * target: User,
169
+ * resolve: async (ctx) => {
170
+ * const name = await ctx.db.fetchName();
171
+ * return [name];
172
+ * }
173
+ * };
174
+ * const user2 = await classable.create(dbResolver, dbContext); // Type: Promise<User>
175
+ * ```
176
+ */
177
+ export interface AsyncClassableByResolver<InstanceType, Args extends Readonlyable<any[]> = [], Runtime = never> {
178
+ target: ClassType<InstanceType, [...Args]>;
179
+ resolve: (...args: Runtime extends never ? [] : [runtime: Runtime]) => Promise<Args>;
180
+ }
91
181
  /**
92
182
  * Union type representing either a direct class constructor or a resolver configuration.
93
183
  *
@@ -107,7 +197,7 @@ export interface ClassableByResolver<InstanceType, Args extends any[] = [], Runt
107
197
  * register({ target: User, resolve: () => ["John", 30] });
108
198
  * ```
109
199
  */
110
- export type Classable<InstanceType, Args extends any[] = [], Runtime = never> = ClassType<InstanceType, Args> | ClassableByResolver<InstanceType, Args, Runtime>;
200
+ export type Classable<InstanceType, Args extends Readonlyable<any[]> = [], Runtime = never> = ClassType<InstanceType, [...Args]> | ClassableByResolver<InstanceType, Args, Runtime>;
111
201
  /**
112
202
  * A placeholder class used as a marker for unresolved or pending class registrations.
113
203
  * Useful in dependency injection containers or lazy initialization patterns.
@@ -124,6 +214,7 @@ export type Classable<InstanceType, Args extends any[] = [], Runtime = never> =
124
214
  * ```
125
215
  */
126
216
  export declare class Placeholder {
217
+ static getInstance(): Placeholder;
127
218
  }
128
219
  /**
129
220
  * Pre-configured placeholder resolver that creates an empty Placeholder instance.
@@ -147,7 +238,91 @@ export type ThisExtended<Extend> = Placeholder & Extend;
147
238
  * @template InstanceType - The instance type of the class.
148
239
  * @template Args - Constructor argument types.
149
240
  */
150
- export type StaticExtended<Extend, InstanceType = any, Args extends any[] = []> = ClassType<InstanceType, Args> & Extend;
241
+ export type StaticExtended<Extend, InstanceType = any, Args extends Readonlyable<any[]> = []> = ClassType<InstanceType, [...Args]> & Extend;
242
+ /**
243
+ * Represents a class whose instances are created via a static factory method.
244
+ *
245
+ * This type is used for classes that expose a static method (e.g., `factory`, `create`)
246
+ * for instantiation instead of using `new` directly.
247
+ *
248
+ * @template InstanceType - The type of instance created by the static method.
249
+ * @template Method - The name of the static method to call (defaults to "factory").
250
+ * @template Args - Tuple type of arguments passed to the static method.
251
+ * @template Runtime - Optional runtime context type passed to the selector.
252
+ *
253
+ * @remarks
254
+ * - The constructor MAY exist and MAY take arguments.
255
+ * - Constructor arguments are considered an implementation detail
256
+ * and are intentionally typed as `any[]`.
257
+ * - Consumers MUST NOT rely on `new`.
258
+ *
259
+ * @example
260
+ * ```typescript
261
+ * class UserService {
262
+ * private constructor(private db: Database) {}
263
+ *
264
+ * static factory(db: Database): UserService {
265
+ * return new UserService(db);
266
+ * }
267
+ * }
268
+ *
269
+ * const def: InstanceByStatic<UserService, "factory", [Database]> = {
270
+ * target: UserService,
271
+ * selector: () => ({ method: "factory", args: [new Database()] })
272
+ * };
273
+ * ```
274
+ */
275
+ export type InstanceByStatic<InstanceType, Method extends string = "factory", Args extends Readonlyable<any[]> = [], Runtime = never> = {
276
+ target: StaticExtended<{
277
+ [K in Method]: (...args: Args) => InstanceType;
278
+ }, InstanceType, any[]>;
279
+ selector: (...args: Runtime extends never ? [] : [runtime: Runtime]) => {
280
+ method: Method;
281
+ args: Args;
282
+ };
283
+ };
284
+ /**
285
+ * Pre-configured placeholder instance using static factory method pattern.
286
+ * Use this as a default value for InstanceByStatic bindings.
287
+ *
288
+ * @example
289
+ * ```typescript
290
+ * const instance = classable.from(classable.placeholderInstance);
291
+ * // Returns new Placeholder instance via getInstance()
292
+ * ```
293
+ */
294
+ export declare const placeholderInstance: Readonly<{
295
+ target: typeof Placeholder;
296
+ selector: () => {
297
+ method: string;
298
+ args: [];
299
+ };
300
+ }>;
301
+ /**
302
+ * A selector function that chooses and returns a classable with its arguments.
303
+ *
304
+ * This type represents a function that takes a list of classables (and optionally a runtime context)
305
+ * and returns the selected classable along with its constructor arguments.
306
+ *
307
+ * @template InstanceType - The type of instance to be created.
308
+ * @template Args - Tuple type of constructor arguments.
309
+ * @template Runtime - Optional runtime context type for dependency resolution.
310
+ *
311
+ * @returns A tuple of [Classable, Args] or a Promise resolving to the same.
312
+ *
313
+ * @example
314
+ * ```typescript
315
+ * const selectByEnv: ClassableSelector<Logger, [], AppContext> = (runtime, ...classes) => {
316
+ * const LoggerClass = runtime.isDev ? DevLogger : ProdLogger;
317
+ * return [LoggerClass, []];
318
+ * };
319
+ *
320
+ * const selectFirst: ClassableSelector<Service, [string]> = (...classes) => {
321
+ * return [classes[0] as Classable<Service, [string]>, ["default"]];
322
+ * };
323
+ * ```
324
+ */
325
+ export type ClassableSelector<InstanceType, Args extends Readonlyable<any[]> = [], Runtime = never> = (...args: Runtime extends never ? Array<Classable<any, any[]>> : [runtime: Runtime, ...Array<Classable<any, any[], Runtime>>]) => [Classable<InstanceType, Args>, Args] | Promise<[Classable<InstanceType, Args>, Args]>;
151
326
  /**
152
327
  * Interface defining all available methods on the `classable` API object.
153
328
  * This provides type-safe access to class manipulation utilities.
@@ -156,19 +331,21 @@ export interface ClassableAPI {
156
331
  /** Reference to the Placeholder class constructor. */
157
332
  Placeholder: ClassType<Placeholder>;
158
333
  /** Pre-configured placeholder resolver instance. */
159
- placeholder: ClassableByResolver<Placeholder>;
334
+ placeholder: ClassableByResolver<Placeholder, readonly []>;
335
+ /** Pre-configured placeholder instance using InstanceByStatic pattern. */
336
+ placeholderInstance: InstanceByStatic<Placeholder, "getInstance", []>;
160
337
  /** Checks if a value is a class constructor. */
161
338
  is: (fn: unknown) => fn is AnyClass<any>;
162
339
  /** Checks if a value is an abstract class constructor. */
163
340
  isAbstract: (fn: unknown) => fn is AnyAbstractClass<any>;
164
341
  /** Checks if a value is a ClassableByResolver object. */
165
- isResolver: <InstanceType, Args extends any[] = [], Runtime = never>(obj: unknown) => obj is ClassableByResolver<InstanceType, Args, Runtime>;
342
+ isResolver: <InstanceType, Args extends Readonlyable<any[]> = [], Runtime = never>(obj: unknown) => obj is ClassableByResolver<InstanceType, Args, Runtime>;
166
343
  /** 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>;
344
+ toResolver<T, A extends Readonlyable<any[]> = [], R = never>(cls: ClassType<T, [...A]>, runtime?: R): ClassableByResolver<T, [], R>;
168
345
  /** Extracts the target class from a Classable (class or resolver). */
169
346
  getTarget: <Target>(cls: Classable<Target, any[], any>) => Target;
170
347
  /** 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>;
348
+ withResolve: <InstanceType, Args extends Readonlyable<any[]> = [], Runtime = never>(base: Classable<InstanceType, Args, Runtime>, resolve: ClassableByResolver<InstanceType, Args, Runtime>["resolve"]) => ClassableByResolver<InstanceType, Args, Runtime>;
172
349
  /** Wraps a class with a transformation function. */
173
350
  wrap<T>(cls: ClassType<T>, wrapper: (target: ClassType<T>) => ClassType<T>): ClassType<T>;
174
351
  /** Wraps a resolver's target with a transformation function. */
@@ -178,16 +355,69 @@ export interface ClassableAPI {
178
355
  type: "class" | "resolver";
179
356
  target: string;
180
357
  };
181
- /** Creates an instance from a plain class. */
358
+ /** Creates an instance from a plain class (no constructor arguments). */
182
359
  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>;
360
+ /** Creates an instance from a Classable (class or resolver) with no arguments. */
361
+ create<InstanceType>(cls: Classable<InstanceType, []>): InstanceType;
362
+ /** Creates an instance from a sync resolver without runtime context. */
363
+ create<InstanceType, Args extends Readonlyable<any[]>>(cls: SyncClassableByResolver<InstanceType, Args, never>): InstanceType;
364
+ /** Creates an instance from an async resolver without runtime context. */
365
+ create<InstanceType, Args extends Readonlyable<any[]>>(cls: AsyncClassableByResolver<InstanceType, Args, never>): Promise<InstanceType>;
366
+ /** Creates an instance from a sync resolver with runtime context. */
367
+ create<InstanceType, Args extends Readonlyable<any[]>, Runtime>(cls: SyncClassableByResolver<InstanceType, Args, Runtime>, runtime: Runtime): InstanceType;
368
+ /** Creates an instance from an async resolver with runtime context. */
369
+ create<InstanceType, Args extends Readonlyable<any[]>, Runtime>(cls: AsyncClassableByResolver<InstanceType, Args, Runtime>, runtime: Runtime): Promise<InstanceType>;
370
+ /**
371
+ * Creates an instance from a static factory method definition.
372
+ *
373
+ * This method invokes the static method specified in the `InstanceByStatic` definition
374
+ * to create an instance, allowing for factory pattern implementations.
375
+ *
376
+ * @template InstanceType - The type of instance created.
377
+ * @template Method - The static method name to invoke.
378
+ * @template Args - Arguments passed to the static method.
379
+ * @template Runtime - Optional runtime context type.
380
+ * @param def - The static factory definition containing target class and selector.
381
+ * @param runtime - Optional runtime context passed to the selector.
382
+ * @returns The created instance.
383
+ *
384
+ * @example
385
+ * ```typescript
386
+ * class Cache {
387
+ * static create(ttl: number): Cache { return new Cache(ttl); }
388
+ * }
389
+ *
390
+ * const instance = classable.from({
391
+ * target: Cache,
392
+ * selector: () => ({ method: "create", args: [3600] })
393
+ * });
394
+ * ```
395
+ */
396
+ from: <InstanceType, Method extends string = "factory", Args extends Readonlyable<any[]> = [], Runtime = never>(def: InstanceByStatic<InstanceType, Method, Args, Runtime>, runtime?: Runtime) => InstanceType;
397
+ /**
398
+ * Creates a selector function that chooses a classable from a list based on custom logic.
399
+ *
400
+ * This higher-order function takes a selection strategy and returns a function
401
+ * that can be called with classables to select and instantiate the appropriate one.
402
+ *
403
+ * @template InstanceType - The type of instance to be created.
404
+ * @template Args - Constructor arguments for the selected classable.
405
+ * @template Runtime - Optional runtime context type.
406
+ * @param select - The selector function that implements the selection logic.
407
+ * @returns A function that accepts classables and returns the selection result.
408
+ *
409
+ * @example
410
+ * ```typescript
411
+ * const pickFirst = classable.select((cls1, cls2) => [cls1, []]);
412
+ * const [selected, args] = pickFirst(ServiceA, ServiceB);
413
+ *
414
+ * // With runtime context
415
+ * const pickByEnv = classable.select<Logger, [], Env>((env, ...loggers) => {
416
+ * return env.isDev ? [loggers[0], []] : [loggers[1], []];
417
+ * });
418
+ * ```
419
+ */
420
+ select: <InstanceType, Args extends Readonlyable<any[]> = [], Runtime = never>(select: ClassableSelector<InstanceType, Args, Runtime>) => (...args: Runtime extends never ? [...classes: Array<Classable<any, any[], Runtime>>] : [runtime: Runtime, ...classes: Array<Classable<any, any[], Runtime>>]) => ReturnType<ClassableSelector<InstanceType, Args, Runtime>>;
191
421
  }
192
422
  /**
193
423
  * The main classable API object providing utilities for working with classes and resolvers.
package/dist/index.mjs CHANGED
@@ -1 +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};
1
+ function e(e,t){return"object"==typeof e&&null!==e&&Object.prototype.hasOwnProperty.call(e,t)}class t{static getInstance(){return new t}}const r=Object.freeze({target:t,resolve:()=>[]}),s=Object.freeze({target:t,selector:()=>({method:"getInstance",args:[]})}),o=Object.freeze({Placeholder:t,placeholder:r,placeholderInstance:s,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},from:(e,t)=>{const{target:r,selector:s}=e,{method:o,args:a}=s(...void 0===t?[]:[t]);return r[o](...a)},select:e=>(...t)=>e(...t)});export{t as Placeholder,o as classable,r as placeholder,s as placeholderInstance};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mxweb/classable",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "A class-first abstraction for defining logic as resolvable units without runtime assumptions.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -11,7 +11,9 @@
11
11
  "resolver",
12
12
  "meta-programming",
13
13
  "framework-agnostic",
14
- "esm-only"
14
+ "esm-only",
15
+ "static-factory",
16
+ "dependency-injection"
15
17
  ],
16
18
  "type": "module",
17
19
  "module": "dist/index.js",
@@ -38,7 +40,8 @@
38
40
  "lint": "eslint \"src/**/*.{ts,tsx}\"",
39
41
  "lint:fix": "eslint \"src/**/*.{ts,tsx}\" --fix",
40
42
  "format": "prettier --write \"src/**/*.{ts,tsx,json,md}\"",
41
- "format:check": "prettier --check \"src/**/*.{ts,tsx,json,md}\""
43
+ "format:check": "prettier --check \"src/**/*.{ts,tsx,json,md}\"",
44
+ "prepublishOnly": "yarn build"
42
45
  },
43
46
  "devDependencies": {
44
47
  "@babel/core": "^7.28.6",