wireweaver 0.1.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/LICENSE.md +9 -0
- package/README.md +252 -0
- package/dist/index.cjs +143 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +111 -0
- package/dist/vite-plugin.cjs +652 -0
- package/dist/vite-plugin.d.ts +35 -0
- package/dist/vite-plugin.js +614 -0
- package/package.json +53 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026-present, brandon-wesley
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
8
|
+
|
|
9
|
+
Source: http://opensource.org/licenses/ISC
|
package/README.md
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# WireWeaver
|
|
2
|
+
|
|
3
|
+
A lightweight TypeScript dependency injection (DI) library. Constructor injection only, no tokens, no boilerplate.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/wireweaver) [](./LICENSE)
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [Philosophy](#philosophy)
|
|
10
|
+
- [Features](#features)
|
|
11
|
+
- [Installation and Usage](#installation-and-usage)
|
|
12
|
+
- [1. Install the package](#1-install-the-package)
|
|
13
|
+
- [2. Setup the build plugin (Vite is recommended)](#2-setup-the-build-plugin-vite-is-recommended)
|
|
14
|
+
- [3. Start declaring and injecting dependencies](#3-start-declaring-and-injecting-dependencies)
|
|
15
|
+
- [Example Usage](#example-usage)
|
|
16
|
+
- [Resolving by interface](#resolving-by-interface)
|
|
17
|
+
- [Factories](#factories)
|
|
18
|
+
- [Enum beans (config tokens)](#enum-beans-config-tokens)
|
|
19
|
+
- [esbuild Plugin](#esbuild-plugin)
|
|
20
|
+
- [Usage Without Plugin](#usage-without-plugin)
|
|
21
|
+
|
|
22
|
+
## Philosophy
|
|
23
|
+
|
|
24
|
+
Inspired by the simplicity of Spring Framework's DI container and aspects of TSyringe, WireWeaver is designed with one core idea: code should be as simple as possible.
|
|
25
|
+
|
|
26
|
+
It should be optimized for maximum readability so that it's easy to understand and easy to change, without sacrificing features. Complexity should be handled behind the scenes so that the developer is free to write clean code without extra boilerplate or unnecessary repetition.
|
|
27
|
+
|
|
28
|
+
Many other TypeScript DI libraries require extra code which we find unnecessary:
|
|
29
|
+
|
|
30
|
+
- ❌ Manual construction of dependencies
|
|
31
|
+
- ❌ Manual registration of dependencies, e.g. `container.register(...)`
|
|
32
|
+
- ❌ Lack of support for interface registration, or requiring string or symbol tokens for it
|
|
33
|
+
- ❌ Using `@Inject()` or other decorators at the injection site
|
|
34
|
+
|
|
35
|
+
## Features
|
|
36
|
+
|
|
37
|
+
- ✅ Auto-instantiation and registration of dependencies
|
|
38
|
+
- no `new` calls, no `container.register()`, no `@Inject()` decorators.
|
|
39
|
+
- ✅ Auto-injection of dependencies
|
|
40
|
+
- no `@Inject()` decorators are required
|
|
41
|
+
- ✅ Simple interface injection
|
|
42
|
+
- dependencies are resolved by type, not by string or symbol tokens.
|
|
43
|
+
- specific instances of interfaces having multiple implementations are resolved by constructor parameter name (e.g. `localStorageService` resolves to `LocalStorageService`).
|
|
44
|
+
- ✅ Support for factories
|
|
45
|
+
- Use `@Bean()` / `@Instance()` decorators to register manually constructed instances (useful for third-party classes, env-driven config, or any value requiring custom construction logic).
|
|
46
|
+
- ✅ Support for enum values as beans (config tokens)
|
|
47
|
+
|
|
48
|
+
A build-time transformer (Vite or esbuild plugin) rewrites `@Service()` / `@Component()` decorators to perform injection without needing `emitDecoratorMetadata`, `reflect-metadata`, or interface tokens. All instances are singletons. Calling `resolve()` multiple times returns the same instance. No scopes or child containers are supported at this time.
|
|
49
|
+
|
|
50
|
+
## Installation and Usage
|
|
51
|
+
|
|
52
|
+
### 1. Install the package
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
npm install wireweaver
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 2. Setup the build plugin (Vite is recommended)
|
|
59
|
+
|
|
60
|
+
If your app uses Vite (including [Quasar](https://quasar.dev/), Nuxt, SvelteKit, etc.), add the wireweaver plugin to your `vite.config.ts`. Alternatively, you may use the [esbuild Plugin](#esbuild-plugin).
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
// vite.config.ts
|
|
64
|
+
import { defineConfig } from 'vite';
|
|
65
|
+
import { wireWeaverPlugin } from 'wireweaver/vite-plugin';
|
|
66
|
+
|
|
67
|
+
export default defineConfig({
|
|
68
|
+
plugins: [
|
|
69
|
+
wireWeaverPlugin(),
|
|
70
|
+
],
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 3. Start declaring and injecting dependencies
|
|
75
|
+
|
|
76
|
+
Decorate your classes with `@Service()`. `@Component()` is available as a semantic alias for `@Service()`. Call `resolve()` (or `getService()`) to get an instance. Dependencies are injected automatically. That's it. The plugin handles all the hard work.
|
|
77
|
+
|
|
78
|
+
### Example Usage
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
interface StorageService {
|
|
82
|
+
save(key: string, value: unknown): void;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@Service()
|
|
86
|
+
class LocalStorageService implements StorageService {
|
|
87
|
+
save(key: string, value: unknown) {}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@Service()
|
|
91
|
+
class CloudStorageService implements StorageService {
|
|
92
|
+
save(key: string, value: unknown) {}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@Service()
|
|
97
|
+
class UserDataService {
|
|
98
|
+
// When multiple services implement the same interface, use descriptive constructor parameter names so the DI container can correctly resolve each implementation.
|
|
99
|
+
constructor(
|
|
100
|
+
private localStorageService: StorageService,
|
|
101
|
+
private cloudStorageService: StorageService,
|
|
102
|
+
) {}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const userDataService = resolve(UserDataService);
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import { Service, Component, resolve } from 'wireweaver';
|
|
110
|
+
// The difference between @Service() and @Component() is purely semantic.
|
|
111
|
+
|
|
112
|
+
@Component()
|
|
113
|
+
interface IUserRepository {}
|
|
114
|
+
|
|
115
|
+
@Component()
|
|
116
|
+
class UserRepository implements IUserRepository {}
|
|
117
|
+
|
|
118
|
+
@Service()
|
|
119
|
+
class UserService {
|
|
120
|
+
constructor(private repository: IUserRepository) {}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const userService = resolve(UserService);
|
|
124
|
+
const userRepository = resolve(UserRepository);
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Resolving by interface
|
|
128
|
+
|
|
129
|
+
When using the Vite or esbuild plugin, you can also call `resolve` with an interface type parameter and no runtime argument. The plugin rewrites the call to pass the concrete class at build time:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
// Rewritten by the plugin to resolve(UserRepository) at build time
|
|
133
|
+
const repo = resolve<IUserRepository>();
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
If multiple classes implement the same interface, use `resolve(ConcreteClass)` directly to disambiguate.
|
|
137
|
+
|
|
138
|
+
## Factories
|
|
139
|
+
|
|
140
|
+
Use `@Bean()` (or its alias `@Instance()`) on **static methods** of any class to manually construct and register an instance. This is useful for third-party classes, environment-driven configuration, or anything that needs custom construction logic.
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
// app-config.ts
|
|
144
|
+
import { Bean, resolve } from 'wireweaver';
|
|
145
|
+
|
|
146
|
+
export class AppConfig {
|
|
147
|
+
@Bean()
|
|
148
|
+
static databaseConnection(): DatabaseConnection {
|
|
149
|
+
return new DatabaseConnection(process.env.DB_URL);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
@Bean()
|
|
153
|
+
static userRepository(): IUserRepository {
|
|
154
|
+
return new UserRepository(resolve(DatabaseConnection));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
// main.ts
|
|
161
|
+
import './app-config'; // Import your configuration files early to ensure @Bean() decorators are evaluated and instances are registered
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The `@Bean()` decorators fire when the module is first imported. The factory method itself is called **lazily** on the first `resolve()` of that type, so declaration order within the class does not matter — cross-bean dependencies are resolved correctly at runtime.
|
|
165
|
+
|
|
166
|
+
The Vite/esbuild plugin rewrites `@Bean()` to `@Bean(ConcreteClass)` at build time. For concrete return types this also works **without the plugin** since the key is inferred from `instance.constructor`.
|
|
167
|
+
|
|
168
|
+
## Enum beans (config tokens)
|
|
169
|
+
|
|
170
|
+
You can also register enum values as beans by using the enum object itself as the token. This is useful for app-level config like log levels.
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
// logger.ts
|
|
174
|
+
import { Component } from 'wireweaver';
|
|
175
|
+
|
|
176
|
+
export enum LogLevel {
|
|
177
|
+
DEBUG = 'debug',
|
|
178
|
+
INFO = 'info',
|
|
179
|
+
WARN = 'warn',
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
@Component()
|
|
183
|
+
export class Logger {
|
|
184
|
+
constructor(private readonly level: LogLevel = LogLevel.INFO) {}
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
// app-config.ts (consumer app)
|
|
190
|
+
import { Bean } from 'wireweaver';
|
|
191
|
+
import { LogLevel } from './logger';
|
|
192
|
+
|
|
193
|
+
export class AppConfig {
|
|
194
|
+
@Bean()
|
|
195
|
+
static loggerLogLevel(): LogLevel {
|
|
196
|
+
return LogLevel.WARN;
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
If no `@Bean(LogLevel)` is registered, enum dependencies are injected as `undefined`, so constructor defaults (like `LogLevel.INFO`) apply automatically.
|
|
201
|
+
|
|
202
|
+
## esbuild Plugin
|
|
203
|
+
|
|
204
|
+
For non-Vite pipelines that use esbuild directly, use `wireWeaverEsbuildPlugin`:
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
// esbuild.config.ts
|
|
208
|
+
import { build } from 'esbuild';
|
|
209
|
+
import { wireWeaverEsbuildPlugin } from 'wireweaver/vite-plugin';
|
|
210
|
+
|
|
211
|
+
await build({
|
|
212
|
+
...,
|
|
213
|
+
plugins: [
|
|
214
|
+
wireWeaverEsbuildPlugin(),
|
|
215
|
+
],
|
|
216
|
+
});
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
The esbuild plugin provides the same compile-time transforms as the Vite plugin:
|
|
220
|
+
|
|
221
|
+
- Rewrites `@Service()` / `@Component()` decorators with resolved constructor dependencies.
|
|
222
|
+
- Rewrites `resolve<IFoo>()` / `getService<IFoo>()` calls to pass the concrete implementation class.
|
|
223
|
+
|
|
224
|
+
## Usage Without Plugin
|
|
225
|
+
|
|
226
|
+
If you are not using the Vite or esbuild plugin, you can declare dependencies explicitly:
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
@Service()
|
|
230
|
+
class UserRepository implements IUserRepository {}
|
|
231
|
+
|
|
232
|
+
@Service([UserRepository])
|
|
233
|
+
class UserService {
|
|
234
|
+
constructor(private repository: IUserRepository) {}
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Or, for tsc-based pipelines, enable `emitDecoratorMetadata` in your `tsconfig.json` and import `reflect-metadata` in your app entry point:
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
// main.ts
|
|
242
|
+
import 'reflect-metadata';
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
```json
|
|
246
|
+
{
|
|
247
|
+
"compilerOptions": {
|
|
248
|
+
"experimentalDecorators": true,
|
|
249
|
+
"emitDecoratorMetadata": true
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
```
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/container.ts
|
|
21
|
+
var container_exports = {};
|
|
22
|
+
__export(container_exports, {
|
|
23
|
+
Bean: () => Bean,
|
|
24
|
+
Component: () => Component,
|
|
25
|
+
Instance: () => Instance,
|
|
26
|
+
Service: () => Service,
|
|
27
|
+
getService: () => getService,
|
|
28
|
+
resetRegistry: () => resetRegistry,
|
|
29
|
+
resolve: () => resolve
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(container_exports);
|
|
32
|
+
var import_reflect_metadata = require("reflect-metadata");
|
|
33
|
+
var containerRegistry = /* @__PURE__ */ new Map();
|
|
34
|
+
var deferredBeans = [];
|
|
35
|
+
function Service(dependencies) {
|
|
36
|
+
return (target) => {
|
|
37
|
+
const resolvedDependencies = dependencies ?? Reflect.getMetadata("design:paramtypes", target) ?? [];
|
|
38
|
+
containerRegistry.set(target, { dependencies: resolvedDependencies });
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function Component(dependencies) {
|
|
42
|
+
return Service(dependencies);
|
|
43
|
+
}
|
|
44
|
+
function getService(componentToken, resolving = /* @__PURE__ */ new Set()) {
|
|
45
|
+
if (!componentToken) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
"WireWeaver: getService<T>() called without a token argument. Ensure the WireWeaver Vite or esbuild plugin is configured to rewrite interface resolve calls at build time."
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
let registration = findRegistration(componentToken);
|
|
51
|
+
if (!registration) throw new Error(`${tokenToDisplayName(componentToken)} is not registered as a WireWeaver Component.`);
|
|
52
|
+
if (registration.instance !== void 0) return registration.instance;
|
|
53
|
+
if (registration.factory) {
|
|
54
|
+
registration.instance = registration.factory();
|
|
55
|
+
return registration.instance;
|
|
56
|
+
}
|
|
57
|
+
if (resolving.has(componentToken)) {
|
|
58
|
+
throw new Error(`Circular dependency detected: ${tokenToDisplayName(componentToken)}`);
|
|
59
|
+
}
|
|
60
|
+
resolving.add(componentToken);
|
|
61
|
+
const constructorArguments = registration.dependencies.map((dependency) => {
|
|
62
|
+
if (!findRegistration(dependency) && isEnumToken(dependency)) {
|
|
63
|
+
return void 0;
|
|
64
|
+
}
|
|
65
|
+
return getService(dependency, resolving);
|
|
66
|
+
});
|
|
67
|
+
const instance = new componentToken(...constructorArguments);
|
|
68
|
+
registration.instance = instance;
|
|
69
|
+
resolving.delete(componentToken);
|
|
70
|
+
return instance;
|
|
71
|
+
}
|
|
72
|
+
function resolve(componentToken, resolving = /* @__PURE__ */ new Set()) {
|
|
73
|
+
return getService(componentToken, resolving);
|
|
74
|
+
}
|
|
75
|
+
function resetRegistry() {
|
|
76
|
+
containerRegistry.clear();
|
|
77
|
+
deferredBeans.length = 0;
|
|
78
|
+
}
|
|
79
|
+
function Bean(registrationToken) {
|
|
80
|
+
return (target, _propertyKey, descriptor) => {
|
|
81
|
+
if (typeof target !== "function") {
|
|
82
|
+
throw new Error(
|
|
83
|
+
"WireWeaver: @Bean() is only supported on static methods. Move the method to a static method, or use @Service() for constructor-injected classes."
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
const method = descriptor.value;
|
|
87
|
+
if (typeof method !== "function") return;
|
|
88
|
+
const factory = () => {
|
|
89
|
+
return method.call(target);
|
|
90
|
+
};
|
|
91
|
+
if (registrationToken) {
|
|
92
|
+
containerRegistry.set(registrationToken, { dependencies: [], factory });
|
|
93
|
+
} else {
|
|
94
|
+
deferredBeans.push(() => {
|
|
95
|
+
const instance = method.call(target);
|
|
96
|
+
if (instance == null || typeof instance !== "object") {
|
|
97
|
+
throw new Error(
|
|
98
|
+
"WireWeaver: @Bean() could not determine a registration key. Annotate the return type and configure the Vite/esbuild plugin, or pass the class explicitly: @Bean(ClassName)."
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
const key = instance.constructor;
|
|
102
|
+
containerRegistry.set(key, { dependencies: [], instance });
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function Instance(registrationToken) {
|
|
108
|
+
return Bean(registrationToken);
|
|
109
|
+
}
|
|
110
|
+
function findRegistration(token) {
|
|
111
|
+
let registration = containerRegistry.get(token);
|
|
112
|
+
while (!registration && deferredBeans.length > 0) {
|
|
113
|
+
const deferred = deferredBeans.shift();
|
|
114
|
+
deferred();
|
|
115
|
+
registration = containerRegistry.get(token);
|
|
116
|
+
}
|
|
117
|
+
return registration;
|
|
118
|
+
}
|
|
119
|
+
function tokenToDisplayName(token) {
|
|
120
|
+
if (typeof token === "function" && token.name) return token.name;
|
|
121
|
+
if (isEnumToken(token)) return "Enum token";
|
|
122
|
+
return "Unknown token";
|
|
123
|
+
}
|
|
124
|
+
function isEnumToken(token) {
|
|
125
|
+
if (typeof token !== "object" || token == null || Array.isArray(token)) return false;
|
|
126
|
+
const entries = Object.entries(token);
|
|
127
|
+
if (entries.length === 0) return false;
|
|
128
|
+
return entries.every(([key, value]) => {
|
|
129
|
+
const isEnumKey = /^\d+$/.test(key) || /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key);
|
|
130
|
+
return isEnumKey && (typeof value === "string" || typeof value === "number");
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
134
|
+
0 && (module.exports = {
|
|
135
|
+
Bean,
|
|
136
|
+
Component,
|
|
137
|
+
Instance,
|
|
138
|
+
Service,
|
|
139
|
+
getService,
|
|
140
|
+
resetRegistry,
|
|
141
|
+
resolve
|
|
142
|
+
});
|
|
143
|
+
if (typeof module.exports.default === "function") module.exports = Object.assign(module.exports.default, module.exports);
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
type Constructor<T = unknown> = new (...constructorArguments: any[]) => T;
|
|
2
|
+
type InjectionToken<T = unknown> = Constructor<T> | object;
|
|
3
|
+
declare function Service(dependencies?: InjectionToken[]): ClassDecorator;
|
|
4
|
+
/** Alias for Service(). */
|
|
5
|
+
declare function Component(dependencies?: InjectionToken[]): ClassDecorator;
|
|
6
|
+
declare function getService<T>(): T;
|
|
7
|
+
declare function getService<T>(componentToken: InjectionToken<T>, resolving?: Set<InjectionToken>): T;
|
|
8
|
+
/** Alias for getService(). */
|
|
9
|
+
declare function resolve<T>(): T;
|
|
10
|
+
declare function resolve<T>(componentToken: InjectionToken<T>, resolving?: Set<InjectionToken>): T;
|
|
11
|
+
/** Clear all registrations and singletons (useful for testing). */
|
|
12
|
+
declare function resetRegistry(): void;
|
|
13
|
+
declare function Bean<T>(registrationToken?: InjectionToken<T>): MethodDecorator;
|
|
14
|
+
/** Alias for Bean(). */
|
|
15
|
+
declare function Instance<T>(registrationToken?: InjectionToken<T>): MethodDecorator;
|
|
16
|
+
|
|
17
|
+
export { Bean, Component, Instance, Service, getService, resetRegistry, resolve };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// src/container.ts
|
|
2
|
+
import "reflect-metadata";
|
|
3
|
+
var containerRegistry = /* @__PURE__ */ new Map();
|
|
4
|
+
var deferredBeans = [];
|
|
5
|
+
function Service(dependencies) {
|
|
6
|
+
return (target) => {
|
|
7
|
+
const resolvedDependencies = dependencies ?? Reflect.getMetadata("design:paramtypes", target) ?? [];
|
|
8
|
+
containerRegistry.set(target, { dependencies: resolvedDependencies });
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
function Component(dependencies) {
|
|
12
|
+
return Service(dependencies);
|
|
13
|
+
}
|
|
14
|
+
function getService(componentToken, resolving = /* @__PURE__ */ new Set()) {
|
|
15
|
+
if (!componentToken) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
"WireWeaver: getService<T>() called without a token argument. Ensure the WireWeaver Vite or esbuild plugin is configured to rewrite interface resolve calls at build time."
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
let registration = findRegistration(componentToken);
|
|
21
|
+
if (!registration) throw new Error(`${tokenToDisplayName(componentToken)} is not registered as a WireWeaver Component.`);
|
|
22
|
+
if (registration.instance !== void 0) return registration.instance;
|
|
23
|
+
if (registration.factory) {
|
|
24
|
+
registration.instance = registration.factory();
|
|
25
|
+
return registration.instance;
|
|
26
|
+
}
|
|
27
|
+
if (resolving.has(componentToken)) {
|
|
28
|
+
throw new Error(`Circular dependency detected: ${tokenToDisplayName(componentToken)}`);
|
|
29
|
+
}
|
|
30
|
+
resolving.add(componentToken);
|
|
31
|
+
const constructorArguments = registration.dependencies.map((dependency) => {
|
|
32
|
+
if (!findRegistration(dependency) && isEnumToken(dependency)) {
|
|
33
|
+
return void 0;
|
|
34
|
+
}
|
|
35
|
+
return getService(dependency, resolving);
|
|
36
|
+
});
|
|
37
|
+
const instance = new componentToken(...constructorArguments);
|
|
38
|
+
registration.instance = instance;
|
|
39
|
+
resolving.delete(componentToken);
|
|
40
|
+
return instance;
|
|
41
|
+
}
|
|
42
|
+
function resolve(componentToken, resolving = /* @__PURE__ */ new Set()) {
|
|
43
|
+
return getService(componentToken, resolving);
|
|
44
|
+
}
|
|
45
|
+
function resetRegistry() {
|
|
46
|
+
containerRegistry.clear();
|
|
47
|
+
deferredBeans.length = 0;
|
|
48
|
+
}
|
|
49
|
+
function Bean(registrationToken) {
|
|
50
|
+
return (target, _propertyKey, descriptor) => {
|
|
51
|
+
if (typeof target !== "function") {
|
|
52
|
+
throw new Error(
|
|
53
|
+
"WireWeaver: @Bean() is only supported on static methods. Move the method to a static method, or use @Service() for constructor-injected classes."
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
const method = descriptor.value;
|
|
57
|
+
if (typeof method !== "function") return;
|
|
58
|
+
const factory = () => {
|
|
59
|
+
return method.call(target);
|
|
60
|
+
};
|
|
61
|
+
if (registrationToken) {
|
|
62
|
+
containerRegistry.set(registrationToken, { dependencies: [], factory });
|
|
63
|
+
} else {
|
|
64
|
+
deferredBeans.push(() => {
|
|
65
|
+
const instance = method.call(target);
|
|
66
|
+
if (instance == null || typeof instance !== "object") {
|
|
67
|
+
throw new Error(
|
|
68
|
+
"WireWeaver: @Bean() could not determine a registration key. Annotate the return type and configure the Vite/esbuild plugin, or pass the class explicitly: @Bean(ClassName)."
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
const key = instance.constructor;
|
|
72
|
+
containerRegistry.set(key, { dependencies: [], instance });
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function Instance(registrationToken) {
|
|
78
|
+
return Bean(registrationToken);
|
|
79
|
+
}
|
|
80
|
+
function findRegistration(token) {
|
|
81
|
+
let registration = containerRegistry.get(token);
|
|
82
|
+
while (!registration && deferredBeans.length > 0) {
|
|
83
|
+
const deferred = deferredBeans.shift();
|
|
84
|
+
deferred();
|
|
85
|
+
registration = containerRegistry.get(token);
|
|
86
|
+
}
|
|
87
|
+
return registration;
|
|
88
|
+
}
|
|
89
|
+
function tokenToDisplayName(token) {
|
|
90
|
+
if (typeof token === "function" && token.name) return token.name;
|
|
91
|
+
if (isEnumToken(token)) return "Enum token";
|
|
92
|
+
return "Unknown token";
|
|
93
|
+
}
|
|
94
|
+
function isEnumToken(token) {
|
|
95
|
+
if (typeof token !== "object" || token == null || Array.isArray(token)) return false;
|
|
96
|
+
const entries = Object.entries(token);
|
|
97
|
+
if (entries.length === 0) return false;
|
|
98
|
+
return entries.every(([key, value]) => {
|
|
99
|
+
const isEnumKey = /^\d+$/.test(key) || /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key);
|
|
100
|
+
return isEnumKey && (typeof value === "string" || typeof value === "number");
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
export {
|
|
104
|
+
Bean,
|
|
105
|
+
Component,
|
|
106
|
+
Instance,
|
|
107
|
+
Service,
|
|
108
|
+
getService,
|
|
109
|
+
resetRegistry,
|
|
110
|
+
resolve
|
|
111
|
+
};
|