@djodjonx/neo-syringe 1.1.5 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +6 -5
- package/.github/workflows/docs.yml +59 -0
- package/CHANGELOG.md +27 -0
- package/README.md +74 -740
- package/dist/{GraphValidator-G0F4QiLk.cjs → GraphValidator-CV4VoJl0.cjs} +18 -10
- package/dist/{GraphValidator-C8ldJtNp.mjs → GraphValidator-DXqqkNdS.mjs} +18 -10
- package/dist/cli/index.cjs +16 -1
- package/dist/cli/index.mjs +16 -1
- package/dist/index.d.cts +31 -5
- package/dist/index.d.mts +31 -5
- package/dist/lsp/index.cjs +1 -1
- package/dist/lsp/index.mjs +1 -1
- package/dist/unplugin/index.cjs +33 -9
- package/dist/unplugin/index.d.cts +7 -5
- package/dist/unplugin/index.d.mts +7 -5
- package/dist/unplugin/index.mjs +33 -9
- package/docs/.vitepress/config.ts +109 -0
- package/docs/.vitepress/theme/custom.css +150 -0
- package/docs/.vitepress/theme/index.ts +17 -0
- package/docs/api/configuration.md +274 -0
- package/docs/api/functions.md +291 -0
- package/docs/api/types.md +158 -0
- package/docs/guide/basic-usage.md +267 -0
- package/docs/guide/cli.md +174 -0
- package/docs/guide/generated-code.md +284 -0
- package/docs/guide/getting-started.md +171 -0
- package/docs/guide/ide-plugin.md +203 -0
- package/docs/guide/injection-types.md +287 -0
- package/docs/guide/legacy-migration.md +333 -0
- package/docs/guide/lifecycle.md +223 -0
- package/docs/guide/parent-container.md +321 -0
- package/docs/guide/scoped-injections.md +271 -0
- package/docs/guide/what-is-neo-syringe.md +162 -0
- package/docs/guide/why-neo-syringe.md +219 -0
- package/docs/index.md +138 -0
- package/docs/public/logo.png +0 -0
- package/package.json +15 -12
- package/src/analyzer/Analyzer.ts +20 -10
- package/src/analyzer/types.ts +55 -49
- package/src/cli/index.ts +15 -0
- package/src/generator/Generator.ts +24 -2
- package/src/generator/GraphValidator.ts +6 -2
- package/src/types.ts +30 -4
- package/src/unplugin/index.ts +13 -41
- package/tests/analyzer/Analyzer.test.ts +4 -4
- package/tests/analyzer/AnalyzerDeclarative.test.ts +1 -1
- package/tests/analyzer/Factory.test.ts +2 -2
- package/tests/analyzer/Scoped.test.ts +434 -0
- package/tests/cli/cli.test.ts +91 -0
- package/tests/e2e/container-integration.test.ts +21 -21
- package/tests/e2e/generated-code.test.ts +7 -7
- package/tests/e2e/scoped.test.ts +370 -0
- package/tests/e2e/snapshots.test.ts +2 -2
- package/tests/e2e/standalone.test.ts +2 -2
- package/tests/generator/ExternalGenerator.test.ts +1 -1
- package/tests/generator/FactoryGenerator.test.ts +6 -6
- package/tests/generator/Generator.test.ts +2 -2
- package/tests/generator/GeneratorDeclarative.test.ts +1 -1
- package/tests/generator/GraphValidator.test.ts +1 -1
- package/tsconfig.json +2 -1
- package/typedoc.json +0 -5
|
@@ -204,16 +204,19 @@ var Analyzer = class {
|
|
|
204
204
|
parseInjectionObject(obj, graph) {
|
|
205
205
|
let tokenNode;
|
|
206
206
|
let providerNode;
|
|
207
|
-
let
|
|
207
|
+
let lifecycle = "singleton";
|
|
208
208
|
let useFactory = false;
|
|
209
|
+
let isScoped = false;
|
|
209
210
|
for (const prop of obj.properties) {
|
|
210
211
|
if (!typescript.isPropertyAssignment(prop) || !typescript.isIdentifier(prop.name)) continue;
|
|
211
212
|
if (prop.name.text === "token") tokenNode = prop.initializer;
|
|
212
213
|
else if (prop.name.text === "provider") providerNode = prop.initializer;
|
|
213
|
-
else if (prop.name.text === "
|
|
214
|
-
if (prop.initializer.text === "transient")
|
|
214
|
+
else if (prop.name.text === "lifecycle" && typescript.isStringLiteral(prop.initializer)) {
|
|
215
|
+
if (prop.initializer.text === "transient") lifecycle = "transient";
|
|
215
216
|
} else if (prop.name.text === "useFactory") {
|
|
216
217
|
if (prop.initializer.kind === typescript.SyntaxKind.TrueKeyword) useFactory = true;
|
|
218
|
+
} else if (prop.name.text === "scoped") {
|
|
219
|
+
if (prop.initializer.kind === typescript.SyntaxKind.TrueKeyword) isScoped = true;
|
|
217
220
|
}
|
|
218
221
|
}
|
|
219
222
|
if (!tokenNode) return;
|
|
@@ -250,17 +253,18 @@ var Analyzer = class {
|
|
|
250
253
|
factorySource = providerNode.getText();
|
|
251
254
|
type = "factory";
|
|
252
255
|
if (tokenId) {
|
|
253
|
-
if (graph.nodes.has(tokenId)) throw new Error(`Duplicate registration: '${tokenId}' is already registered.`);
|
|
256
|
+
if (graph.nodes.has(tokenId) && !isScoped) throw new Error(`Duplicate registration: '${tokenId}' is already registered.`);
|
|
254
257
|
const definition = {
|
|
255
258
|
tokenId,
|
|
256
259
|
tokenSymbol: tokenSymbol ? this.resolveSymbol(tokenSymbol) : void 0,
|
|
257
260
|
registrationNode: obj,
|
|
258
261
|
type: "factory",
|
|
259
|
-
|
|
262
|
+
lifecycle,
|
|
260
263
|
isInterfaceToken,
|
|
261
264
|
isValueToken,
|
|
262
265
|
isFactory: true,
|
|
263
|
-
factorySource
|
|
266
|
+
factorySource,
|
|
267
|
+
isScoped
|
|
264
268
|
};
|
|
265
269
|
graph.nodes.set(tokenId, {
|
|
266
270
|
service: definition,
|
|
@@ -282,15 +286,16 @@ var Analyzer = class {
|
|
|
282
286
|
type = "autowire";
|
|
283
287
|
}
|
|
284
288
|
if (tokenId && implementationSymbol) {
|
|
285
|
-
if (graph.nodes.has(tokenId)) throw new Error(`Duplicate registration: '${tokenId}' is already registered.`);
|
|
289
|
+
if (graph.nodes.has(tokenId) && !isScoped) throw new Error(`Duplicate registration: '${tokenId}' is already registered.`);
|
|
286
290
|
const definition = {
|
|
287
291
|
tokenId,
|
|
288
292
|
implementationSymbol: this.resolveSymbol(implementationSymbol),
|
|
289
293
|
tokenSymbol: tokenSymbol ? this.resolveSymbol(tokenSymbol) : void 0,
|
|
290
294
|
registrationNode: obj,
|
|
291
295
|
type,
|
|
292
|
-
|
|
293
|
-
isInterfaceToken: isInterfaceToken || typescript.isCallExpression(tokenNode) && this.isUseInterfaceCall(tokenNode)
|
|
296
|
+
lifecycle,
|
|
297
|
+
isInterfaceToken: isInterfaceToken || typescript.isCallExpression(tokenNode) && this.isUseInterfaceCall(tokenNode),
|
|
298
|
+
isScoped
|
|
294
299
|
};
|
|
295
300
|
graph.nodes.set(tokenId, {
|
|
296
301
|
service: definition,
|
|
@@ -433,7 +438,10 @@ var GraphValidator = class {
|
|
|
433
438
|
const visited = /* @__PURE__ */ new Set();
|
|
434
439
|
const recursionStack = /* @__PURE__ */ new Set();
|
|
435
440
|
const parentTokens = graph.parentProvidedTokens ?? /* @__PURE__ */ new Set();
|
|
436
|
-
for (const nodeId of graph.nodes
|
|
441
|
+
for (const [nodeId, node] of graph.nodes) if (parentTokens.has(nodeId)) {
|
|
442
|
+
if (node.service.isScoped) continue;
|
|
443
|
+
throw new Error(`Duplicate registration: '${nodeId}' is already registered in the parent container. Use 'scoped: true' to override the parent's registration intentionally.`);
|
|
444
|
+
}
|
|
437
445
|
for (const [nodeId, node] of graph.nodes) for (const depId of node.dependencies) {
|
|
438
446
|
const isProvidedLocally = graph.nodes.has(depId);
|
|
439
447
|
const isProvidedByParent = parentTokens.has(depId);
|
|
@@ -176,16 +176,19 @@ var Analyzer = class {
|
|
|
176
176
|
parseInjectionObject(obj, graph) {
|
|
177
177
|
let tokenNode;
|
|
178
178
|
let providerNode;
|
|
179
|
-
let
|
|
179
|
+
let lifecycle = "singleton";
|
|
180
180
|
let useFactory = false;
|
|
181
|
+
let isScoped = false;
|
|
181
182
|
for (const prop of obj.properties) {
|
|
182
183
|
if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) continue;
|
|
183
184
|
if (prop.name.text === "token") tokenNode = prop.initializer;
|
|
184
185
|
else if (prop.name.text === "provider") providerNode = prop.initializer;
|
|
185
|
-
else if (prop.name.text === "
|
|
186
|
-
if (prop.initializer.text === "transient")
|
|
186
|
+
else if (prop.name.text === "lifecycle" && ts.isStringLiteral(prop.initializer)) {
|
|
187
|
+
if (prop.initializer.text === "transient") lifecycle = "transient";
|
|
187
188
|
} else if (prop.name.text === "useFactory") {
|
|
188
189
|
if (prop.initializer.kind === ts.SyntaxKind.TrueKeyword) useFactory = true;
|
|
190
|
+
} else if (prop.name.text === "scoped") {
|
|
191
|
+
if (prop.initializer.kind === ts.SyntaxKind.TrueKeyword) isScoped = true;
|
|
189
192
|
}
|
|
190
193
|
}
|
|
191
194
|
if (!tokenNode) return;
|
|
@@ -222,17 +225,18 @@ var Analyzer = class {
|
|
|
222
225
|
factorySource = providerNode.getText();
|
|
223
226
|
type = "factory";
|
|
224
227
|
if (tokenId) {
|
|
225
|
-
if (graph.nodes.has(tokenId)) throw new Error(`Duplicate registration: '${tokenId}' is already registered.`);
|
|
228
|
+
if (graph.nodes.has(tokenId) && !isScoped) throw new Error(`Duplicate registration: '${tokenId}' is already registered.`);
|
|
226
229
|
const definition = {
|
|
227
230
|
tokenId,
|
|
228
231
|
tokenSymbol: tokenSymbol ? this.resolveSymbol(tokenSymbol) : void 0,
|
|
229
232
|
registrationNode: obj,
|
|
230
233
|
type: "factory",
|
|
231
|
-
|
|
234
|
+
lifecycle,
|
|
232
235
|
isInterfaceToken,
|
|
233
236
|
isValueToken,
|
|
234
237
|
isFactory: true,
|
|
235
|
-
factorySource
|
|
238
|
+
factorySource,
|
|
239
|
+
isScoped
|
|
236
240
|
};
|
|
237
241
|
graph.nodes.set(tokenId, {
|
|
238
242
|
service: definition,
|
|
@@ -254,15 +258,16 @@ var Analyzer = class {
|
|
|
254
258
|
type = "autowire";
|
|
255
259
|
}
|
|
256
260
|
if (tokenId && implementationSymbol) {
|
|
257
|
-
if (graph.nodes.has(tokenId)) throw new Error(`Duplicate registration: '${tokenId}' is already registered.`);
|
|
261
|
+
if (graph.nodes.has(tokenId) && !isScoped) throw new Error(`Duplicate registration: '${tokenId}' is already registered.`);
|
|
258
262
|
const definition = {
|
|
259
263
|
tokenId,
|
|
260
264
|
implementationSymbol: this.resolveSymbol(implementationSymbol),
|
|
261
265
|
tokenSymbol: tokenSymbol ? this.resolveSymbol(tokenSymbol) : void 0,
|
|
262
266
|
registrationNode: obj,
|
|
263
267
|
type,
|
|
264
|
-
|
|
265
|
-
isInterfaceToken: isInterfaceToken || ts.isCallExpression(tokenNode) && this.isUseInterfaceCall(tokenNode)
|
|
268
|
+
lifecycle,
|
|
269
|
+
isInterfaceToken: isInterfaceToken || ts.isCallExpression(tokenNode) && this.isUseInterfaceCall(tokenNode),
|
|
270
|
+
isScoped
|
|
266
271
|
};
|
|
267
272
|
graph.nodes.set(tokenId, {
|
|
268
273
|
service: definition,
|
|
@@ -405,7 +410,10 @@ var GraphValidator = class {
|
|
|
405
410
|
const visited = /* @__PURE__ */ new Set();
|
|
406
411
|
const recursionStack = /* @__PURE__ */ new Set();
|
|
407
412
|
const parentTokens = graph.parentProvidedTokens ?? /* @__PURE__ */ new Set();
|
|
408
|
-
for (const nodeId of graph.nodes
|
|
413
|
+
for (const [nodeId, node] of graph.nodes) if (parentTokens.has(nodeId)) {
|
|
414
|
+
if (node.service.isScoped) continue;
|
|
415
|
+
throw new Error(`Duplicate registration: '${nodeId}' is already registered in the parent container. Use 'scoped: true' to override the parent's registration intentionally.`);
|
|
416
|
+
}
|
|
409
417
|
for (const [nodeId, node] of graph.nodes) for (const depId of node.dependencies) {
|
|
410
418
|
const isProvidedLocally = graph.nodes.has(depId);
|
|
411
419
|
const isProvidedByParent = parentTokens.has(depId);
|
package/dist/cli/index.cjs
CHANGED
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const require_GraphValidator = require('../GraphValidator-
|
|
2
|
+
const require_GraphValidator = require('../GraphValidator-CV4VoJl0.cjs');
|
|
3
3
|
let typescript = require("typescript");
|
|
4
4
|
typescript = require_GraphValidator.__toESM(typescript);
|
|
5
5
|
|
|
6
6
|
//#region src/cli/index.ts
|
|
7
|
+
/**
|
|
8
|
+
* Neo-Syringe CLI
|
|
9
|
+
*
|
|
10
|
+
* Validates the dependency graph for a TypeScript project.
|
|
11
|
+
* Detects circular dependencies, missing bindings, and duplicate registrations.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```bash
|
|
15
|
+
* npx neo-syringe
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* CLI entry point.
|
|
20
|
+
* Reads tsconfig.json, analyzes the project, and validates the dependency graph.
|
|
21
|
+
*/
|
|
7
22
|
function main() {
|
|
8
23
|
const cwd = process.cwd();
|
|
9
24
|
const configPath = typescript.findConfigFile(cwd, typescript.sys.fileExists, "tsconfig.json");
|
package/dist/cli/index.mjs
CHANGED
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { n as Analyzer, t as GraphValidator } from "../GraphValidator-
|
|
2
|
+
import { n as Analyzer, t as GraphValidator } from "../GraphValidator-DXqqkNdS.mjs";
|
|
3
3
|
import * as ts from "typescript";
|
|
4
4
|
|
|
5
5
|
//#region src/cli/index.ts
|
|
6
|
+
/**
|
|
7
|
+
* Neo-Syringe CLI
|
|
8
|
+
*
|
|
9
|
+
* Validates the dependency graph for a TypeScript project.
|
|
10
|
+
* Detects circular dependencies, missing bindings, and duplicate registrations.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```bash
|
|
14
|
+
* npx neo-syringe
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* CLI entry point.
|
|
19
|
+
* Reads tsconfig.json, analyzes the project, and validates the dependency graph.
|
|
20
|
+
*/
|
|
6
21
|
function main() {
|
|
7
22
|
const cwd = process.cwd();
|
|
8
23
|
const configPath = ts.findConfigFile(cwd, ts.sys.fileExists, "tsconfig.json");
|
package/dist/index.d.cts
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
* Represents a generic class constructor.
|
|
4
4
|
* @template T - The type of the instance created by the constructor.
|
|
5
5
|
*/
|
|
6
|
-
type Constructor<T =
|
|
6
|
+
type Constructor<T = any> = new (...args: any[]) => T;
|
|
7
7
|
/**
|
|
8
|
-
* Defines the lifecycle
|
|
8
|
+
* Defines the lifecycle of a service.
|
|
9
9
|
* - `singleton`: One instance per container.
|
|
10
10
|
* - `transient`: A new instance every time it is resolved.
|
|
11
11
|
*/
|
|
12
|
-
type
|
|
12
|
+
type Lifecycle = 'singleton' | 'transient';
|
|
13
13
|
/**
|
|
14
14
|
* The dependency injection container interface.
|
|
15
15
|
* Provides access to the registered services.
|
|
@@ -74,7 +74,33 @@ interface Injection<T = any> {
|
|
|
74
74
|
* Required when provider is a function, not a class.
|
|
75
75
|
*/
|
|
76
76
|
useFactory?: boolean;
|
|
77
|
-
|
|
77
|
+
/**
|
|
78
|
+
* Lifecycle of the service.
|
|
79
|
+
* - `singleton`: One instance per container (default).
|
|
80
|
+
* - `transient`: A new instance every time it is resolved.
|
|
81
|
+
*/
|
|
82
|
+
lifecycle?: Lifecycle;
|
|
83
|
+
/**
|
|
84
|
+
* If true, this injection is scoped to this container only.
|
|
85
|
+
* Allows overriding a token from a parent container without causing a duplicate error.
|
|
86
|
+
* The local instance will be used instead of delegating to the parent.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```typescript
|
|
90
|
+
* const parent = defineBuilderConfig({
|
|
91
|
+
* injections: [{ token: useInterface<ILogger>(), provider: ConsoleLogger }]
|
|
92
|
+
* });
|
|
93
|
+
*
|
|
94
|
+
* const child = defineBuilderConfig({
|
|
95
|
+
* useContainer: parent,
|
|
96
|
+
* injections: [
|
|
97
|
+
* // Override parent's ILogger with a local FileLogger
|
|
98
|
+
* { token: useInterface<ILogger>(), provider: FileLogger, scoped: true }
|
|
99
|
+
* ]
|
|
100
|
+
* });
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
scoped?: boolean;
|
|
78
104
|
}
|
|
79
105
|
/**
|
|
80
106
|
* Partial configuration that can be shared/extended.
|
|
@@ -139,4 +165,4 @@ declare function useProperty<T, C extends Constructor<any>>(targetClass: C, para
|
|
|
139
165
|
*/
|
|
140
166
|
declare function declareContainerTokens<T extends Record<string, any>>(container: any): T & { [K in keyof T]: T[K] };
|
|
141
167
|
//#endregion
|
|
142
|
-
export { BuilderConfig, Constructor, Container, Factory, Injection, InterfaceToken, PartialConfig, PropertyToken, Provider,
|
|
168
|
+
export { BuilderConfig, Constructor, Container, Factory, Injection, InterfaceToken, Lifecycle, PartialConfig, PropertyToken, Provider, Token, declareContainerTokens, defineBuilderConfig, definePartialConfig, useInterface, useProperty };
|
package/dist/index.d.mts
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
* Represents a generic class constructor.
|
|
4
4
|
* @template T - The type of the instance created by the constructor.
|
|
5
5
|
*/
|
|
6
|
-
type Constructor<T =
|
|
6
|
+
type Constructor<T = any> = new (...args: any[]) => T;
|
|
7
7
|
/**
|
|
8
|
-
* Defines the lifecycle
|
|
8
|
+
* Defines the lifecycle of a service.
|
|
9
9
|
* - `singleton`: One instance per container.
|
|
10
10
|
* - `transient`: A new instance every time it is resolved.
|
|
11
11
|
*/
|
|
12
|
-
type
|
|
12
|
+
type Lifecycle = 'singleton' | 'transient';
|
|
13
13
|
/**
|
|
14
14
|
* The dependency injection container interface.
|
|
15
15
|
* Provides access to the registered services.
|
|
@@ -74,7 +74,33 @@ interface Injection<T = any> {
|
|
|
74
74
|
* Required when provider is a function, not a class.
|
|
75
75
|
*/
|
|
76
76
|
useFactory?: boolean;
|
|
77
|
-
|
|
77
|
+
/**
|
|
78
|
+
* Lifecycle of the service.
|
|
79
|
+
* - `singleton`: One instance per container (default).
|
|
80
|
+
* - `transient`: A new instance every time it is resolved.
|
|
81
|
+
*/
|
|
82
|
+
lifecycle?: Lifecycle;
|
|
83
|
+
/**
|
|
84
|
+
* If true, this injection is scoped to this container only.
|
|
85
|
+
* Allows overriding a token from a parent container without causing a duplicate error.
|
|
86
|
+
* The local instance will be used instead of delegating to the parent.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```typescript
|
|
90
|
+
* const parent = defineBuilderConfig({
|
|
91
|
+
* injections: [{ token: useInterface<ILogger>(), provider: ConsoleLogger }]
|
|
92
|
+
* });
|
|
93
|
+
*
|
|
94
|
+
* const child = defineBuilderConfig({
|
|
95
|
+
* useContainer: parent,
|
|
96
|
+
* injections: [
|
|
97
|
+
* // Override parent's ILogger with a local FileLogger
|
|
98
|
+
* { token: useInterface<ILogger>(), provider: FileLogger, scoped: true }
|
|
99
|
+
* ]
|
|
100
|
+
* });
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
scoped?: boolean;
|
|
78
104
|
}
|
|
79
105
|
/**
|
|
80
106
|
* Partial configuration that can be shared/extended.
|
|
@@ -139,4 +165,4 @@ declare function useProperty<T, C extends Constructor<any>>(targetClass: C, para
|
|
|
139
165
|
*/
|
|
140
166
|
declare function declareContainerTokens<T extends Record<string, any>>(container: any): T & { [K in keyof T]: T[K] };
|
|
141
167
|
//#endregion
|
|
142
|
-
export { BuilderConfig, Constructor, Container, Factory, Injection, InterfaceToken, PartialConfig, PropertyToken, Provider,
|
|
168
|
+
export { BuilderConfig, Constructor, Container, Factory, Injection, InterfaceToken, Lifecycle, PartialConfig, PropertyToken, Provider, Token, declareContainerTokens, defineBuilderConfig, definePartialConfig, useInterface, useProperty };
|
package/dist/lsp/index.cjs
CHANGED
package/dist/lsp/index.mjs
CHANGED
package/dist/unplugin/index.cjs
CHANGED
|
@@ -1,16 +1,29 @@
|
|
|
1
|
-
const require_GraphValidator = require('../GraphValidator-
|
|
1
|
+
const require_GraphValidator = require('../GraphValidator-CV4VoJl0.cjs');
|
|
2
2
|
let unplugin = require("unplugin");
|
|
3
3
|
let typescript = require("typescript");
|
|
4
4
|
typescript = require_GraphValidator.__toESM(typescript);
|
|
5
5
|
|
|
6
6
|
//#region src/generator/Generator.ts
|
|
7
7
|
/**
|
|
8
|
-
* Generates
|
|
8
|
+
* Generates TypeScript code for the dependency injection container.
|
|
9
|
+
*
|
|
10
|
+
* Takes a validated dependency graph and produces:
|
|
11
|
+
* - Import statements for all dependencies
|
|
12
|
+
* - Factory functions for each service
|
|
13
|
+
* - A NeoContainer class with resolve logic
|
|
9
14
|
*/
|
|
10
15
|
var Generator = class {
|
|
16
|
+
/**
|
|
17
|
+
* Creates a new Generator.
|
|
18
|
+
* @param graph - The validated dependency graph to generate code from.
|
|
19
|
+
*/
|
|
11
20
|
constructor(graph) {
|
|
12
21
|
this.graph = graph;
|
|
13
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Generates the complete container code as a string.
|
|
25
|
+
* @returns TypeScript source code for the generated container.
|
|
26
|
+
*/
|
|
14
27
|
generate() {
|
|
15
28
|
const sorted = this.topologicalSort();
|
|
16
29
|
const imports = /* @__PURE__ */ new Map();
|
|
@@ -53,7 +66,7 @@ function ${factoryId}(container: NeoContainer) {
|
|
|
53
66
|
return new ${className}(${args});
|
|
54
67
|
}`);
|
|
55
68
|
}
|
|
56
|
-
const isTransient = node.service.
|
|
69
|
+
const isTransient = node.service.lifecycle === "transient";
|
|
57
70
|
let tokenKey;
|
|
58
71
|
let tokenCheck;
|
|
59
72
|
if (node.service.isInterfaceToken) {
|
|
@@ -147,6 +160,10 @@ export class NeoContainer {
|
|
|
147
160
|
export const container = new NeoContainer(${containerArgs});
|
|
148
161
|
`;
|
|
149
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* Sorts services in topological order (dependencies before dependents).
|
|
165
|
+
* @returns Array of TokenIds in dependency order.
|
|
166
|
+
*/
|
|
150
167
|
topologicalSort() {
|
|
151
168
|
const visited = /* @__PURE__ */ new Set();
|
|
152
169
|
const sorted = [];
|
|
@@ -162,6 +179,11 @@ export const container = new NeoContainer(${containerArgs});
|
|
|
162
179
|
for (const id of this.graph.nodes.keys()) visit(id);
|
|
163
180
|
return sorted;
|
|
164
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* Creates a valid JavaScript function name from a token ID.
|
|
184
|
+
* @param tokenId - The token identifier.
|
|
185
|
+
* @returns A sanitized factory function name.
|
|
186
|
+
*/
|
|
165
187
|
getFactoryName(tokenId) {
|
|
166
188
|
return `create_${tokenId.replace(/[^a-zA-Z0-9]/g, "_")}`;
|
|
167
189
|
}
|
|
@@ -170,21 +192,23 @@ export const container = new NeoContainer(${containerArgs});
|
|
|
170
192
|
//#endregion
|
|
171
193
|
//#region src/unplugin/index.ts
|
|
172
194
|
/**
|
|
173
|
-
*
|
|
195
|
+
* Neo-Syringe build plugin for Vite, Rollup, Webpack, and other bundlers.
|
|
174
196
|
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
197
|
+
* Intercepts files containing `defineBuilderConfig` calls, analyzes the
|
|
198
|
+
* dependency graph, validates it, and replaces the configuration with
|
|
199
|
+
* generated factory code.
|
|
178
200
|
*
|
|
179
201
|
* @example
|
|
202
|
+
* ```typescript
|
|
180
203
|
* // vite.config.ts
|
|
181
204
|
* import { neoSyringePlugin } from '@djodjonx/neo-syringe/plugin';
|
|
182
205
|
*
|
|
183
206
|
* export default defineConfig({
|
|
184
|
-
* plugins: [neoSyringePlugin.vite()]
|
|
207
|
+
* plugins: [neoSyringePlugin.vite()]
|
|
185
208
|
* });
|
|
209
|
+
* ```
|
|
186
210
|
*/
|
|
187
|
-
const neoSyringePlugin = (0, unplugin.createUnplugin)((
|
|
211
|
+
const neoSyringePlugin = (0, unplugin.createUnplugin)(() => {
|
|
188
212
|
return {
|
|
189
213
|
name: "neo-syringe-plugin",
|
|
190
214
|
transformInclude(id) {
|
|
@@ -2,19 +2,21 @@ import * as unplugin0 from "unplugin";
|
|
|
2
2
|
|
|
3
3
|
//#region src/unplugin/index.d.ts
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Neo-Syringe build plugin for Vite, Rollup, Webpack, and other bundlers.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Intercepts files containing `defineBuilderConfig` calls, analyzes the
|
|
8
|
+
* dependency graph, validates it, and replaces the configuration with
|
|
9
|
+
* generated factory code.
|
|
10
10
|
*
|
|
11
11
|
* @example
|
|
12
|
+
* ```typescript
|
|
12
13
|
* // vite.config.ts
|
|
13
14
|
* import { neoSyringePlugin } from '@djodjonx/neo-syringe/plugin';
|
|
14
15
|
*
|
|
15
16
|
* export default defineConfig({
|
|
16
|
-
* plugins: [neoSyringePlugin.vite()]
|
|
17
|
+
* plugins: [neoSyringePlugin.vite()]
|
|
17
18
|
* });
|
|
19
|
+
* ```
|
|
18
20
|
*/
|
|
19
21
|
declare const neoSyringePlugin: unplugin0.UnpluginInstance<unknown, boolean>;
|
|
20
22
|
//#endregion
|
|
@@ -2,19 +2,21 @@ import * as unplugin0 from "unplugin";
|
|
|
2
2
|
|
|
3
3
|
//#region src/unplugin/index.d.ts
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Neo-Syringe build plugin for Vite, Rollup, Webpack, and other bundlers.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Intercepts files containing `defineBuilderConfig` calls, analyzes the
|
|
8
|
+
* dependency graph, validates it, and replaces the configuration with
|
|
9
|
+
* generated factory code.
|
|
10
10
|
*
|
|
11
11
|
* @example
|
|
12
|
+
* ```typescript
|
|
12
13
|
* // vite.config.ts
|
|
13
14
|
* import { neoSyringePlugin } from '@djodjonx/neo-syringe/plugin';
|
|
14
15
|
*
|
|
15
16
|
* export default defineConfig({
|
|
16
|
-
* plugins: [neoSyringePlugin.vite()]
|
|
17
|
+
* plugins: [neoSyringePlugin.vite()]
|
|
17
18
|
* });
|
|
19
|
+
* ```
|
|
18
20
|
*/
|
|
19
21
|
declare const neoSyringePlugin: unplugin0.UnpluginInstance<unknown, boolean>;
|
|
20
22
|
//#endregion
|
package/dist/unplugin/index.mjs
CHANGED
|
@@ -1,15 +1,28 @@
|
|
|
1
|
-
import { n as Analyzer, t as GraphValidator } from "../GraphValidator-
|
|
1
|
+
import { n as Analyzer, t as GraphValidator } from "../GraphValidator-DXqqkNdS.mjs";
|
|
2
2
|
import { createUnplugin } from "unplugin";
|
|
3
3
|
import * as ts from "typescript";
|
|
4
4
|
|
|
5
5
|
//#region src/generator/Generator.ts
|
|
6
6
|
/**
|
|
7
|
-
* Generates
|
|
7
|
+
* Generates TypeScript code for the dependency injection container.
|
|
8
|
+
*
|
|
9
|
+
* Takes a validated dependency graph and produces:
|
|
10
|
+
* - Import statements for all dependencies
|
|
11
|
+
* - Factory functions for each service
|
|
12
|
+
* - A NeoContainer class with resolve logic
|
|
8
13
|
*/
|
|
9
14
|
var Generator = class {
|
|
15
|
+
/**
|
|
16
|
+
* Creates a new Generator.
|
|
17
|
+
* @param graph - The validated dependency graph to generate code from.
|
|
18
|
+
*/
|
|
10
19
|
constructor(graph) {
|
|
11
20
|
this.graph = graph;
|
|
12
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Generates the complete container code as a string.
|
|
24
|
+
* @returns TypeScript source code for the generated container.
|
|
25
|
+
*/
|
|
13
26
|
generate() {
|
|
14
27
|
const sorted = this.topologicalSort();
|
|
15
28
|
const imports = /* @__PURE__ */ new Map();
|
|
@@ -52,7 +65,7 @@ function ${factoryId}(container: NeoContainer) {
|
|
|
52
65
|
return new ${className}(${args});
|
|
53
66
|
}`);
|
|
54
67
|
}
|
|
55
|
-
const isTransient = node.service.
|
|
68
|
+
const isTransient = node.service.lifecycle === "transient";
|
|
56
69
|
let tokenKey;
|
|
57
70
|
let tokenCheck;
|
|
58
71
|
if (node.service.isInterfaceToken) {
|
|
@@ -146,6 +159,10 @@ export class NeoContainer {
|
|
|
146
159
|
export const container = new NeoContainer(${containerArgs});
|
|
147
160
|
`;
|
|
148
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* Sorts services in topological order (dependencies before dependents).
|
|
164
|
+
* @returns Array of TokenIds in dependency order.
|
|
165
|
+
*/
|
|
149
166
|
topologicalSort() {
|
|
150
167
|
const visited = /* @__PURE__ */ new Set();
|
|
151
168
|
const sorted = [];
|
|
@@ -161,6 +178,11 @@ export const container = new NeoContainer(${containerArgs});
|
|
|
161
178
|
for (const id of this.graph.nodes.keys()) visit(id);
|
|
162
179
|
return sorted;
|
|
163
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* Creates a valid JavaScript function name from a token ID.
|
|
183
|
+
* @param tokenId - The token identifier.
|
|
184
|
+
* @returns A sanitized factory function name.
|
|
185
|
+
*/
|
|
164
186
|
getFactoryName(tokenId) {
|
|
165
187
|
return `create_${tokenId.replace(/[^a-zA-Z0-9]/g, "_")}`;
|
|
166
188
|
}
|
|
@@ -169,21 +191,23 @@ export const container = new NeoContainer(${containerArgs});
|
|
|
169
191
|
//#endregion
|
|
170
192
|
//#region src/unplugin/index.ts
|
|
171
193
|
/**
|
|
172
|
-
*
|
|
194
|
+
* Neo-Syringe build plugin for Vite, Rollup, Webpack, and other bundlers.
|
|
173
195
|
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
196
|
+
* Intercepts files containing `defineBuilderConfig` calls, analyzes the
|
|
197
|
+
* dependency graph, validates it, and replaces the configuration with
|
|
198
|
+
* generated factory code.
|
|
177
199
|
*
|
|
178
200
|
* @example
|
|
201
|
+
* ```typescript
|
|
179
202
|
* // vite.config.ts
|
|
180
203
|
* import { neoSyringePlugin } from '@djodjonx/neo-syringe/plugin';
|
|
181
204
|
*
|
|
182
205
|
* export default defineConfig({
|
|
183
|
-
* plugins: [neoSyringePlugin.vite()]
|
|
206
|
+
* plugins: [neoSyringePlugin.vite()]
|
|
184
207
|
* });
|
|
208
|
+
* ```
|
|
185
209
|
*/
|
|
186
|
-
const neoSyringePlugin = createUnplugin((
|
|
210
|
+
const neoSyringePlugin = createUnplugin(() => {
|
|
187
211
|
return {
|
|
188
212
|
name: "neo-syringe-plugin",
|
|
189
213
|
transformInclude(id) {
|