composed-di 0.0.4-alpha → 0.0.5-alpha

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/README.md CHANGED
@@ -4,8 +4,8 @@ A tiny, type-friendly dependency injection helper for composing services via key
4
4
 
5
5
  It provides:
6
6
  - ServiceKey<T>: a typed token used to identify a service
7
- - ServiceFactory<T>: a contract to create a service (with helpers singletonFactory and oneShotFactory)
8
- - ServiceModule: a resolver that wires factories, validates dependencies, and injects services
7
+ - ServiceFactory<T>: a contract to create a service (with static methods `singleton` and `oneShot`)
8
+ - ServiceModule: a resolver that wires factories, validates dependencies, and provides services
9
9
 
10
10
  ServiceModule will:
11
11
  - detect recursive dependencies (a factory that depends on its own key)
@@ -13,7 +13,13 @@ ServiceModule will:
13
13
 
14
14
  ## Install
15
15
 
16
- This repository is set up as a library. Build artifacts are generated under `dist/` by running:
16
+ Install from npm:
17
+
18
+ - npm: `npm install composed-di`
19
+ - pnpm: `pnpm add composed-di`
20
+ - yarn: `yarn add composed-di`
21
+
22
+ If you're working on this repo locally, build artifacts are generated under `dist/` by running:
17
23
 
18
24
  ```
19
25
  npm run build
@@ -21,15 +27,14 @@ npm run build
21
27
 
22
28
  ## Usage
23
29
 
24
- Below is an example based on the previous sample `main.ts` usage pattern.
30
+ Below is a minimal example using the public API.
25
31
 
26
32
  ```ts
27
33
  import {
28
34
  ServiceKey,
29
35
  ServiceModule,
30
- singletonFactory,
31
- oneShotFactory,
32
- } from 'composed-di'; // if used locally, import from the relative path to `src/index`
36
+ ServiceFactory,
37
+ } from 'composed-di'; // when developing this repo locally, import from './src'
33
38
 
34
39
  // 1) Define service types
35
40
  interface Config {
@@ -53,50 +58,84 @@ const LoggerKey = new ServiceKey<Logger>('Logger');
53
58
  const AppKey = new ServiceKey<App>('App');
54
59
 
55
60
  // 3) Create factories (singleton or one-shot)
56
- const configFactory = singletonFactory({
61
+ const configFactory = ServiceFactory.singleton({
57
62
  provides: ConfigKey,
58
- dependsOn: [] as const,
59
- async initialize() {
63
+ dependsOn: [],
64
+ initialize: () => {
60
65
  return { baseUrl: 'https://api.example.com' } satisfies Config;
61
66
  },
62
67
  });
63
68
 
64
- const loggerFactory = singletonFactory({
69
+ const loggerFactory = ServiceFactory.singleton({
65
70
  provides: LoggerKey,
66
- dependsOn: [] as const,
67
- async initialize() {
71
+ dependsOn: [],
72
+ initialize: () => {
68
73
  return console as unknown as Logger;
69
74
  },
70
75
  });
71
76
 
72
- const appFactory = oneShotFactory({
77
+ const appFactory = ServiceFactory.oneShot({
73
78
  provides: AppKey,
74
- dependsOn: [ConfigKey, LoggerKey] as const,
75
- async initialize(config, logger) {
79
+ dependsOn: [ConfigKey, LoggerKey],
80
+ initialize: (config, logger) => {
76
81
  return new App(config, logger);
77
82
  },
78
83
  });
79
84
 
80
- // 4) Compose a module
85
+ // 4) Compose a module (you can pass factories and/or other ServiceModule instances)
81
86
  const module = ServiceModule.from([configFactory, loggerFactory, appFactory]);
82
87
 
83
- // 5) Inject and use
88
+ // 5) Resolve and use
84
89
  (async () => {
85
- const app = await module.inject(AppKey);
90
+ const app = await module.get(AppKey);
86
91
  app.start();
87
92
  })();
88
93
  ```
89
94
 
90
95
  Notes:
91
- - Use `as const` on your `dependsOn` list to preserve tuple types and keep constructor parameters strongly typed.
92
- - `ServiceModule.inject` resolves dependencies recursively, so factories can depend on other services.
96
+ - `ServiceModule.get` resolves dependencies recursively, so factories can depend on other services.
93
97
  - If a dependency is missing or recursive, `ServiceModule` throws with a helpful error message.
94
98
 
99
+ ## Visualizing Dependencies
100
+
101
+ The library includes utilities to generate a DOT graph representation of your service dependencies, which can be visualized using Graphviz tools.
102
+
103
+ ```ts
104
+ import { ServiceModule, printDotGraph, createDotGraph } from 'composed-di';
105
+
106
+ // After creating your ServiceModule
107
+ const module = ServiceModule.from([configFactory, loggerFactory, appFactory]);
108
+
109
+ // Option 1: Print the DOT graph to console with instructions
110
+ printDotGraph(module);
111
+
112
+ // Option 2: Generate DOT graph with custom options
113
+ const dotGraph = createDotGraph(module, {
114
+ direction: 'LR', // 'TB' (top-bottom), 'LR' (left-right), 'BT', 'RL'
115
+ title: 'My Service Dependencies',
116
+ highlightLeaves: true, // Highlight services with no dependencies (green)
117
+ highlightRoots: true, // Highlight services with no dependents (orange)
118
+ });
119
+ console.log(dotGraph);
120
+ ```
121
+
122
+ The generated DOT notation can be visualized using:
123
+ - [GraphvizOnline](https://dreampuf.github.io/GraphvizOnline/)
124
+ - [Edotor](https://edotor.net/)
125
+
95
126
  ## API
96
127
 
97
- - `class ServiceKey<T>(name: string)`
98
- - `interface ServiceFactory<T, D extends ServiceKey<unknown>[]>` with `provides`, `dependsOn`, `initialize`, `dispose`
99
- - `singletonFactory({ provides, dependsOn?, initialize, dispose? })`
100
- - `oneShotFactory({ provides, dependsOn, initialize, dispose? })`
101
- - `class ServiceModule` with `static from(factories)`, `inject(key)`
128
+ - `class ServiceKey<T>(name: string)` - Creates a typed token to identify a service
129
+ - `abstract class ServiceFactory<T, D extends readonly ServiceKey<unknown>[]>` with:
130
+ - `provides: ServiceKey<T>` - The service key this factory provides
131
+ - `dependsOn: D` - Array of service keys this factory depends on
132
+ - `initialize(...dependencies)` - Creates the service instance
133
+ - `dispose(instance)` - Cleans up the service instance
134
+ - `static singleton({ provides, dependsOn?, initialize, dispose? })` - Creates a singleton factory
135
+ - `static oneShot({ provides, dependsOn, initialize, dispose? })` - Creates a one-shot factory
136
+ - `class ServiceModule` with:
137
+ - `static from(factoriesOrModules)` - Creates a module from factories and/or other modules
138
+ - `async get(key)` - Resolves and returns a service by its key
139
+ - `createDotGraph(module, options?)` - Generates DOT notation graph from a ServiceModule
140
+ - `printDotGraph(module)` - Prints DOT graph to console with visualization instructions
102
141
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export { ServiceKey } from './serviceKey';
2
- export { ServiceModule } from './serviceModule';
3
- export { type ServiceFactory, singletonFactory, oneShotFactory } from './serviceFactory';
4
- export { type ServiceProvider } from './serviceProvider';
1
+ export * from './ServiceKey';
2
+ export * from './ServiceModule';
3
+ export * from './ServiceFactory';
4
+ export * from './utils';
5
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,KAAK,cAAc,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACzF,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,SAAS,CAAA"}
package/dist/index.js CHANGED
@@ -1,11 +1,21 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
2
16
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.oneShotFactory = exports.singletonFactory = exports.ServiceModule = exports.ServiceKey = void 0;
4
- var serviceKey_1 = require("./serviceKey");
5
- Object.defineProperty(exports, "ServiceKey", { enumerable: true, get: function () { return serviceKey_1.ServiceKey; } });
6
- var serviceModule_1 = require("./serviceModule");
7
- Object.defineProperty(exports, "ServiceModule", { enumerable: true, get: function () { return serviceModule_1.ServiceModule; } });
8
- var serviceFactory_1 = require("./serviceFactory");
9
- Object.defineProperty(exports, "singletonFactory", { enumerable: true, get: function () { return serviceFactory_1.singletonFactory; } });
10
- Object.defineProperty(exports, "oneShotFactory", { enumerable: true, get: function () { return serviceFactory_1.oneShotFactory; } });
17
+ __exportStar(require("./ServiceKey"), exports);
18
+ __exportStar(require("./ServiceModule"), exports);
19
+ __exportStar(require("./ServiceFactory"), exports);
20
+ __exportStar(require("./utils"), exports);
11
21
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,2CAA0C;AAAjC,wGAAA,UAAU,OAAA;AACnB,iDAAgD;AAAvC,8GAAA,aAAa,OAAA;AACtB,mDAAyF;AAA3D,kHAAA,gBAAgB,OAAA;AAAE,gHAAA,cAAc,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,+CAA6B;AAC7B,kDAAgC;AAChC,mDAAiC;AACjC,0CAAuB"}
@@ -1,25 +1,33 @@
1
- import { ServiceKey } from './serviceKey';
1
+ import { ServiceKey } from './ServiceKey';
2
2
  type ServiceType<T> = T extends ServiceKey<infer U> ? U : never;
3
3
  type DependencyTypes<T extends readonly ServiceKey<unknown>[]> = {
4
4
  [K in keyof T]: ServiceType<T[K]>;
5
5
  };
6
- export interface ServiceFactory<T, D extends readonly ServiceKey<unknown>[] = []> {
7
- provides: ServiceKey<T>;
8
- dependsOn: D;
9
- initialize(...dependencies: DependencyTypes<D>): T | Promise<T>;
10
- dispose(instance: T): void;
6
+ export declare abstract class ServiceFactory<const T, const D extends readonly ServiceKey<unknown>[] = []> {
7
+ abstract provides: ServiceKey<T>;
8
+ abstract dependsOn: D;
9
+ abstract initialize(...dependencies: DependencyTypes<D>): T | Promise<T>;
10
+ abstract dispose(instance: T): void;
11
+ /**
12
+ * Creates a singleton service factory that ensures a single instance of the provided service is initialized
13
+ * and used throughout its lifecycle.
14
+ */
15
+ static singleton<const T, const D extends readonly ServiceKey<unknown>[] = []>({ provides, dependsOn, initialize, dispose, }: {
16
+ provides: ServiceKey<T>;
17
+ dependsOn?: D;
18
+ initialize: (...dependencies: DependencyTypes<D>) => T | Promise<T>;
19
+ dispose?: (instance: T) => void;
20
+ }): ServiceFactory<T, D>;
21
+ /**
22
+ * Creates a one-shot service factory that initializes a new instance of the provided service
23
+ * every time it is requested.
24
+ */
25
+ static oneShot<const T, const D extends readonly ServiceKey<unknown>[] = []>({ provides, dependsOn, initialize, dispose, }: {
26
+ provides: ServiceKey<T>;
27
+ dependsOn: D;
28
+ initialize: (...dependencies: DependencyTypes<D>) => T | Promise<T>;
29
+ dispose?: (instance: T) => void;
30
+ }): ServiceFactory<T, D>;
11
31
  }
12
- export declare function singletonFactory<T, D extends readonly ServiceKey<unknown>[] = []>({ provides, dependsOn, initialize, dispose, }: {
13
- provides: ServiceKey<T>;
14
- dependsOn?: D;
15
- initialize: (...dependencies: DependencyTypes<D>) => T | Promise<T>;
16
- dispose?: (instance: T) => void;
17
- }): ServiceFactory<T, D>;
18
- export declare function oneShotFactory<T, D extends readonly ServiceKey<unknown>[] = []>({ provides, dependsOn, initialize, dispose, }: {
19
- provides: ServiceKey<T>;
20
- dependsOn: D;
21
- initialize: (...dependencies: DependencyTypes<D>) => T | Promise<T>;
22
- dispose?: (instance: T) => void;
23
- }): ServiceFactory<T, D>;
24
32
  export {};
25
- //# sourceMappingURL=serviceFactory.d.ts.map
33
+ //# sourceMappingURL=ServiceFactory.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"serviceFactory.d.ts","sourceRoot":"","sources":["../src/serviceFactory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG1C,KAAK,WAAW,CAAC,CAAC,IAAI,CAAC,SAAS,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAGhE,KAAK,eAAe,CAAC,CAAC,SAAS,SAAS,UAAU,CAAC,OAAO,CAAC,EAAE,IAAI;KAC9D,CAAC,IAAI,MAAM,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAClC,CAAC;AAEF,MAAM,WAAW,cAAc,CAC7B,CAAC,EACD,CAAC,SAAS,SAAS,UAAU,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE;IAE7C,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;IACxB,SAAS,EAAE,CAAC,CAAC;IAEb,UAAU,CAAC,GAAG,YAAY,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAEhE,OAAO,CAAC,QAAQ,EAAE,CAAC,GAAG,IAAI,CAAC;CAC5B;AAED,wBAAgB,gBAAgB,CAC9B,CAAC,EACD,CAAC,SAAS,SAAS,UAAU,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,EAC7C,EACA,QAAQ,EACR,SAA8B,EAC9B,UAAU,EACV,OAAkB,GACnB,EAAE;IACD,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;IACxB,SAAS,CAAC,EAAE,CAAC,CAAC;IACd,UAAU,EAAE,CAAC,GAAG,YAAY,EAAE,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACpE,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,IAAI,CAAC;CACjC,GAAG,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,CAoBvB;AAED,wBAAgB,cAAc,CAC5B,CAAC,EACD,CAAC,SAAS,SAAS,UAAU,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,EAC7C,EACA,QAAQ,EACR,SAAS,EACT,UAAU,EACV,OAAkB,GACnB,EAAE;IACD,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;IACxB,SAAS,EAAE,CAAC,CAAC;IACb,UAAU,EAAE,CAAC,GAAG,YAAY,EAAE,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACpE,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,IAAI,CAAC;CACjC,GAAG,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,CAOvB"}
1
+ {"version":3,"file":"ServiceFactory.d.ts","sourceRoot":"","sources":["../src/ServiceFactory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG1C,KAAK,WAAW,CAAC,CAAC,IAAI,CAAC,SAAS,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAGhE,KAAK,eAAe,CAAC,CAAC,SAAS,SAAS,UAAU,CAAC,OAAO,CAAC,EAAE,IAAI;KAC9D,CAAC,IAAI,MAAM,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAClC,CAAC;AAEF,8BAAsB,cAAc,CAClC,KAAK,CAAC,CAAC,EACP,KAAK,CAAC,CAAC,SAAS,SAAS,UAAU,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE;IAEnD,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC;IAEtB,QAAQ,CAAC,UAAU,CAAC,GAAG,YAAY,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAExE,QAAQ,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,GAAG,IAAI;IAEnC;;;OAGG;IACH,MAAM,CAAC,SAAS,CACd,KAAK,CAAC,CAAC,EACP,KAAK,CAAC,CAAC,SAAS,SAAS,UAAU,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,EACnD,EACA,QAAQ,EACR,SAA8B,EAC9B,UAAU,EACV,OAAkB,GACnB,EAAE;QACD,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;QACxB,SAAS,CAAC,EAAE,CAAC,CAAC;QACd,UAAU,EAAE,CAAC,GAAG,YAAY,EAAE,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;QACpE,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,IAAI,CAAC;KACjC,GAAG,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC;IAsBxB;;;OAGG;IACH,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,SAAS,SAAS,UAAU,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,EAAE,EAC3E,QAAQ,EACR,SAAS,EACT,UAAU,EACV,OAAkB,GACnB,EAAE;QACD,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;QACxB,SAAS,EAAE,CAAC,CAAC;QACb,UAAU,EAAE,CAAC,GAAG,YAAY,EAAE,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;QACpE,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,IAAI,CAAC;KACjC,GAAG,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC;CAQzB"}
@@ -9,36 +9,46 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.singletonFactory = singletonFactory;
13
- exports.oneShotFactory = oneShotFactory;
14
- function singletonFactory({ provides, dependsOn = [], initialize, dispose = () => { }, }) {
15
- let instance;
16
- return {
17
- provides,
18
- dependsOn,
19
- initialize(...dependencies) {
20
- return __awaiter(this, void 0, void 0, function* () {
21
- if (instance) {
12
+ exports.ServiceFactory = void 0;
13
+ class ServiceFactory {
14
+ /**
15
+ * Creates a singleton service factory that ensures a single instance of the provided service is initialized
16
+ * and used throughout its lifecycle.
17
+ */
18
+ static singleton({ provides, dependsOn = [], initialize, dispose = () => { }, }) {
19
+ let instance;
20
+ return {
21
+ provides,
22
+ dependsOn,
23
+ initialize(...dependencies) {
24
+ return __awaiter(this, void 0, void 0, function* () {
25
+ if (instance) {
26
+ return instance;
27
+ }
28
+ instance = yield initialize(...dependencies);
22
29
  return instance;
30
+ });
31
+ },
32
+ dispose(serviceInstance) {
33
+ if (instance === serviceInstance) {
34
+ dispose(serviceInstance);
35
+ instance = undefined;
23
36
  }
24
- instance = yield initialize(...dependencies);
25
- return instance;
26
- });
27
- },
28
- dispose(serviceInstance) {
29
- if (instance === serviceInstance) {
30
- dispose(serviceInstance);
31
- instance = undefined;
32
- }
33
- },
34
- };
37
+ },
38
+ };
39
+ }
40
+ /**
41
+ * Creates a one-shot service factory that initializes a new instance of the provided service
42
+ * every time it is requested.
43
+ */
44
+ static oneShot({ provides, dependsOn, initialize, dispose = () => { }, }) {
45
+ return {
46
+ provides,
47
+ dependsOn,
48
+ initialize,
49
+ dispose,
50
+ };
51
+ }
35
52
  }
36
- function oneShotFactory({ provides, dependsOn, initialize, dispose = () => { }, }) {
37
- return {
38
- provides,
39
- dependsOn,
40
- initialize,
41
- dispose,
42
- };
43
- }
44
- //# sourceMappingURL=serviceFactory.js.map
53
+ exports.ServiceFactory = ServiceFactory;
54
+ //# sourceMappingURL=ServiceFactory.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"serviceFactory.js","sourceRoot":"","sources":["../src/serviceFactory.ts"],"names":[],"mappings":";;;;;;;;;;;AAsBA,4CAiCC;AAED,wCAoBC;AAvDD,SAAgB,gBAAgB,CAG9B,EACA,QAAQ,EACR,SAAS,GAAG,EAAkB,EAC9B,UAAU,EACV,OAAO,GAAG,GAAG,EAAE,GAAE,CAAC,GAMnB;IACC,IAAI,QAAuB,CAAC;IAE5B,OAAO;QACL,QAAQ;QACR,SAAS;QACH,UAAU,CAAC,GAAG,YAAgC;;gBAClD,IAAI,QAAQ,EAAE,CAAC;oBACb,OAAO,QAAQ,CAAC;gBAClB,CAAC;gBACD,QAAQ,GAAG,MAAM,UAAU,CAAC,GAAG,YAAY,CAAC,CAAC;gBAC7C,OAAO,QAAQ,CAAC;YAClB,CAAC;SAAA;QACD,OAAO,CAAC,eAAkB;YACxB,IAAI,QAAQ,KAAK,eAAe,EAAE,CAAC;gBACjC,OAAO,CAAC,eAAe,CAAC,CAAC;gBACzB,QAAQ,GAAG,SAAS,CAAC;YACvB,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAgB,cAAc,CAG5B,EACA,QAAQ,EACR,SAAS,EACT,UAAU,EACV,OAAO,GAAG,GAAG,EAAE,GAAE,CAAC,GAMnB;IACC,OAAO;QACL,QAAQ;QACR,SAAS;QACT,UAAU;QACV,OAAO;KACR,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"ServiceFactory.js","sourceRoot":"","sources":["../src/ServiceFactory.ts"],"names":[],"mappings":";;;;;;;;;;;;AAUA,MAAsB,cAAc;IAWlC;;;OAGG;IACH,MAAM,CAAC,SAAS,CAGd,EACA,QAAQ,EACR,SAAS,GAAG,EAAkB,EAC9B,UAAU,EACV,OAAO,GAAG,GAAG,EAAE,GAAE,CAAC,GAMnB;QACC,IAAI,QAAuB,CAAC;QAE5B,OAAO;YACL,QAAQ;YACR,SAAS;YACH,UAAU,CAAC,GAAG,YAAgC;;oBAClD,IAAI,QAAQ,EAAE,CAAC;wBACb,OAAO,QAAQ,CAAC;oBAClB,CAAC;oBACD,QAAQ,GAAG,MAAM,UAAU,CAAC,GAAG,YAAY,CAAC,CAAC;oBAC7C,OAAO,QAAQ,CAAC;gBAClB,CAAC;aAAA;YACD,OAAO,CAAC,eAAkB;gBACxB,IAAI,QAAQ,KAAK,eAAe,EAAE,CAAC;oBACjC,OAAO,CAAC,eAAe,CAAC,CAAC;oBACzB,QAAQ,GAAG,SAAS,CAAC;gBACvB,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,OAAO,CAA+D,EAC3E,QAAQ,EACR,SAAS,EACT,UAAU,EACV,OAAO,GAAG,GAAG,EAAE,GAAE,CAAC,GAMnB;QACC,OAAO;YACL,QAAQ;YACR,SAAS;YACT,UAAU;YACV,OAAO;SACR,CAAC;IACJ,CAAC;CACF;AAxED,wCAwEC"}
@@ -3,4 +3,4 @@ export declare class ServiceKey<T> {
3
3
  private readonly symbol;
4
4
  constructor(name: string);
5
5
  }
6
- //# sourceMappingURL=serviceKey.d.ts.map
6
+ //# sourceMappingURL=ServiceKey.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"serviceKey.d.ts","sourceRoot":"","sources":["../src/serviceKey.ts"],"names":[],"mappings":"AACA,qBAAa,UAAU,CAAC,CAAC;aAGK,IAAI,EAAE,MAAM;IAFxC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAEJ,IAAI,EAAE,MAAM;CAGzC"}
1
+ {"version":3,"file":"ServiceKey.d.ts","sourceRoot":"","sources":["../src/ServiceKey.ts"],"names":[],"mappings":"AACA,qBAAa,UAAU,CAAC,CAAC;aAGK,IAAI,EAAE,MAAM;IAFxC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAEJ,IAAI,EAAE,MAAM;CAGzC"}
@@ -9,4 +9,4 @@ class ServiceKey {
9
9
  }
10
10
  }
11
11
  exports.ServiceKey = ServiceKey;
12
- //# sourceMappingURL=serviceKey.js.map
12
+ //# sourceMappingURL=ServiceKey.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"serviceKey.js","sourceRoot":"","sources":["../src/serviceKey.ts"],"names":[],"mappings":";;;AAAA,aAAa;AACb,MAAa,UAAU;IAGrB,YAA4B,IAAY;QAAZ,SAAI,GAAJ,IAAI,CAAQ;QACtC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;CACF;AAND,gCAMC"}
1
+ {"version":3,"file":"ServiceKey.js","sourceRoot":"","sources":["../src/ServiceKey.ts"],"names":[],"mappings":";;;AAAA,aAAa;AACb,MAAa,UAAU;IAGrB,YAA4B,IAAY;QAAZ,SAAI,GAAJ,IAAI,CAAQ;QACtC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;CACF;AAND,gCAMC"}
@@ -1,10 +1,9 @@
1
- import { ServiceKey } from './serviceKey';
2
- import { ServiceFactory } from './serviceFactory';
3
- import { ServiceProvider } from './serviceProvider';
4
- export declare class ServiceModule implements ServiceProvider {
5
- private readonly factories;
1
+ import { ServiceKey } from './ServiceKey';
2
+ import { ServiceFactory } from './ServiceFactory';
3
+ export declare class ServiceModule {
4
+ readonly factories: ServiceFactory<any, any>[];
6
5
  constructor(factories: Set<ServiceFactory<unknown, readonly ServiceKey<unknown>[]>>);
7
6
  get<T>(key: ServiceKey<T>): Promise<T>;
8
7
  static from(entries: (ServiceModule | ServiceFactory<unknown, readonly ServiceKey<unknown>[]>)[]): ServiceModule;
9
8
  }
10
- //# sourceMappingURL=serviceModule.d.ts.map
9
+ //# sourceMappingURL=ServiceModule.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"serviceModule.d.ts","sourceRoot":"","sources":["../src/serviceModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,qBAAa,aAAc,YAAW,eAAe;IACnD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAkC;gBAG1D,SAAS,EAAE,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,SAAS,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAS5D,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAqBnD,MAAM,CAAC,IAAI,CACT,OAAO,EAAE,CAAC,aAAa,GAAG,cAAc,CAAC,OAAO,EAAE,SAAS,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,GACnF,aAAa;CAajB"}
1
+ {"version":3,"file":"ServiceModule.d.ts","sourceRoot":"","sources":["../src/ServiceModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,qBAAa,aAAa;IACxB,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,CAAM;gBAGlD,SAAS,EAAE,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,SAAS,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAS5D,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAqBnD,MAAM,CAAC,IAAI,CACT,OAAO,EAAE,CAAC,aAAa,GAAG,cAAc,CAAC,OAAO,EAAE,SAAS,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,GACnF,aAAa;CAajB"}
@@ -74,4 +74,4 @@ function isRegistered(key, factories) {
74
74
  function isSuitable(key, factory) {
75
75
  return (factory === null || factory === void 0 ? void 0 : factory.provides) === key;
76
76
  }
77
- //# sourceMappingURL=serviceModule.js.map
77
+ //# sourceMappingURL=ServiceModule.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"serviceModule.js","sourceRoot":"","sources":["../src/serviceModule.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ServiceModule.js","sourceRoot":"","sources":["../src/ServiceModule.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import { ServiceModule } from './ServiceModule';
2
+ export interface DotGraphOptions {
3
+ /** Graph direction: 'TB' (top-bottom), 'LR' (left-right), 'BT' (bottom-top), 'RL' (right-left) */
4
+ direction?: 'TB' | 'LR' | 'BT' | 'RL';
5
+ /** Title for the graph */
6
+ title?: string;
7
+ /** Show nodes with no dependencies in a different color */
8
+ highlightLeaves?: boolean;
9
+ /** Show nodes with no dependents in a different color */
10
+ highlightRoots?: boolean;
11
+ }
12
+ /**
13
+ * Generates a DOT notation graph from a ServiceModule.
14
+ * The output can be visualized using Graphviz tools or online viewers like:
15
+ * - https://dreampuf.github.io/GraphvizOnline/
16
+ * - https://edotor.net/
17
+ *
18
+ * Arrows point from dependencies to dependents (from what is needed to what needs it).
19
+ *
20
+ * @param module - The ServiceModule to convert to DOT notation
21
+ * @param options - Optional configuration for the graph appearance
22
+ * @returns A string containing the DOT notation graph
23
+ */
24
+ export declare function createDotGraph(module: ServiceModule, { direction, title, highlightLeaves, highlightRoots }?: DotGraphOptions): string;
25
+ export declare function printDotGraph(module: ServiceModule): void;
26
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGhD,MAAM,WAAW,eAAe;IAC9B,kGAAkG;IAClG,SAAS,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IACtC,0BAA0B;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,yDAAyD;IACzD,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AASD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,aAAa,EACrB,EAAE,SAAS,EAAE,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,GAAE,eAKtD,GACA,MAAM,CA6FR;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,QAIlD"}
package/dist/utils.js ADDED
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createDotGraph = createDotGraph;
4
+ exports.printDotGraph = printDotGraph;
5
+ /**
6
+ * Escapes special characters in strings for DOT notation
7
+ */
8
+ function escapeDotString(str) {
9
+ return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
10
+ }
11
+ /**
12
+ * Generates a DOT notation graph from a ServiceModule.
13
+ * The output can be visualized using Graphviz tools or online viewers like:
14
+ * - https://dreampuf.github.io/GraphvizOnline/
15
+ * - https://edotor.net/
16
+ *
17
+ * Arrows point from dependencies to dependents (from what is needed to what needs it).
18
+ *
19
+ * @param module - The ServiceModule to convert to DOT notation
20
+ * @param options - Optional configuration for the graph appearance
21
+ * @returns A string containing the DOT notation graph
22
+ */
23
+ function createDotGraph(module, { direction, title, highlightLeaves, highlightRoots } = {
24
+ direction: 'BT',
25
+ title: 'Service Dependency Graph',
26
+ highlightLeaves: true,
27
+ highlightRoots: true,
28
+ }) {
29
+ const factories = module.factories;
30
+ const lines = [];
31
+ // Start the digraph
32
+ lines.push('digraph ServiceDependencies {');
33
+ lines.push(` label="${title}";`);
34
+ lines.push(' labelloc="t";');
35
+ lines.push(' fontsize=16;');
36
+ lines.push(` rankdir=${direction};`);
37
+ lines.push('');
38
+ // Default node styling
39
+ lines.push(' node [');
40
+ lines.push(' shape=box,');
41
+ lines.push(' style="rounded,filled",');
42
+ lines.push(' fillcolor="#e1f5ff",');
43
+ lines.push(' color="#0288d1",');
44
+ lines.push(' fontname="Arial",');
45
+ lines.push(' fontsize=12');
46
+ lines.push(' ];');
47
+ lines.push('');
48
+ // Default edge styling
49
+ lines.push(' edge [');
50
+ lines.push(' color="#666666",');
51
+ lines.push(' arrowsize=0.8');
52
+ lines.push(' ];');
53
+ lines.push('');
54
+ // Build dependency maps to identify leaves and roots
55
+ const hasDependencies = new Set();
56
+ const hasDependents = new Set();
57
+ factories.forEach((factory) => {
58
+ const serviceName = factory.provides.name;
59
+ if (factory.dependsOn.length > 0) {
60
+ hasDependencies.add(serviceName);
61
+ }
62
+ factory.dependsOn.forEach((dependency) => {
63
+ hasDependents.add(dependency.name);
64
+ });
65
+ });
66
+ // Define nodes with special styling for leaves and roots
67
+ const nodeIds = new Map();
68
+ let nodeCounter = 0;
69
+ factories.forEach((factory) => {
70
+ const serviceName = factory.provides.name;
71
+ const nodeId = `node${nodeCounter++}`;
72
+ nodeIds.set(serviceName, nodeId);
73
+ const isLeaf = !hasDependencies.has(serviceName);
74
+ const isRoot = !hasDependents.has(serviceName);
75
+ let nodeStyle = '';
76
+ if (highlightLeaves && isLeaf) {
77
+ nodeStyle = ' [fillcolor="#c8e6c9", color="#388e3c"]';
78
+ }
79
+ else if (highlightRoots && isRoot) {
80
+ nodeStyle = ' [fillcolor="#ffccbc", color="#d84315"]';
81
+ }
82
+ lines.push(` ${nodeId} [label="${escapeDotString(serviceName)}"]${nodeStyle};`);
83
+ });
84
+ lines.push('');
85
+ // Define edges (dependencies)
86
+ factories.forEach((factory) => {
87
+ const serviceName = factory.provides.name;
88
+ const serviceNodeId = nodeIds.get(serviceName);
89
+ factory.dependsOn.forEach((dependency) => {
90
+ const depName = dependency.name;
91
+ const depNodeId = nodeIds.get(depName);
92
+ if (depNodeId) {
93
+ // Arrow points from dependency to dependent (what provides -> what needs it)
94
+ lines.push(` ${depNodeId} -> ${serviceNodeId};`);
95
+ }
96
+ });
97
+ });
98
+ // Close the digraph
99
+ lines.push('}');
100
+ return lines.join('\n');
101
+ }
102
+ function printDotGraph(module) {
103
+ console.log(createDotGraph(module));
104
+ console.log('\n\nCopy the DOT output above and paste it into:');
105
+ console.log('https://dreampuf.github.io/GraphvizOnline/');
106
+ }
107
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "composed-di",
3
3
  "private": false,
4
- "version": "0.0.4-alpha",
4
+ "version": "0.0.5-alpha",
5
5
  "author": "Juan Herrera juanhr454@gmail.com",
6
6
  "type": "commonjs",
7
7
  "main": "./dist/index.js",
@@ -0,0 +1,83 @@
1
+ import { ServiceKey } from './ServiceKey';
2
+
3
+ // Helper types to extract the type from ServiceKey
4
+ type ServiceType<T> = T extends ServiceKey<infer U> ? U : never;
5
+
6
+ // Helper types to convert an array/tuple of ServiceKey to tuple of their types
7
+ type DependencyTypes<T extends readonly ServiceKey<unknown>[]> = {
8
+ [K in keyof T]: ServiceType<T[K]>;
9
+ };
10
+
11
+ export abstract class ServiceFactory<
12
+ const T,
13
+ const D extends readonly ServiceKey<unknown>[] = [],
14
+ > {
15
+ abstract provides: ServiceKey<T>;
16
+ abstract dependsOn: D;
17
+
18
+ abstract initialize(...dependencies: DependencyTypes<D>): T | Promise<T>;
19
+
20
+ abstract dispose(instance: T): void;
21
+
22
+ /**
23
+ * Creates a singleton service factory that ensures a single instance of the provided service is initialized
24
+ * and used throughout its lifecycle.
25
+ */
26
+ static singleton<
27
+ const T,
28
+ const D extends readonly ServiceKey<unknown>[] = [],
29
+ >({
30
+ provides,
31
+ dependsOn = [] as unknown as D,
32
+ initialize,
33
+ dispose = () => {},
34
+ }: {
35
+ provides: ServiceKey<T>;
36
+ dependsOn?: D;
37
+ initialize: (...dependencies: DependencyTypes<D>) => T | Promise<T>;
38
+ dispose?: (instance: T) => void;
39
+ }): ServiceFactory<T, D> {
40
+ let instance: T | undefined;
41
+
42
+ return {
43
+ provides,
44
+ dependsOn,
45
+ async initialize(...dependencies: DependencyTypes<D>): Promise<T> {
46
+ if (instance) {
47
+ return instance;
48
+ }
49
+ instance = await initialize(...dependencies);
50
+ return instance;
51
+ },
52
+ dispose(serviceInstance: T): void {
53
+ if (instance === serviceInstance) {
54
+ dispose(serviceInstance);
55
+ instance = undefined;
56
+ }
57
+ },
58
+ };
59
+ }
60
+
61
+ /**
62
+ * Creates a one-shot service factory that initializes a new instance of the provided service
63
+ * every time it is requested.
64
+ */
65
+ static oneShot<const T, const D extends readonly ServiceKey<unknown>[] = []>({
66
+ provides,
67
+ dependsOn,
68
+ initialize,
69
+ dispose = () => {},
70
+ }: {
71
+ provides: ServiceKey<T>;
72
+ dependsOn: D;
73
+ initialize: (...dependencies: DependencyTypes<D>) => T | Promise<T>;
74
+ dispose?: (instance: T) => void;
75
+ }): ServiceFactory<T, D> {
76
+ return {
77
+ provides,
78
+ dependsOn,
79
+ initialize,
80
+ dispose,
81
+ };
82
+ }
83
+ }
@@ -1,103 +1,102 @@
1
- import { ServiceKey } from './serviceKey';
2
- import { ServiceFactory } from './serviceFactory';
3
- import { ServiceProvider } from './serviceProvider';
4
-
5
- export class ServiceModule implements ServiceProvider {
6
- private readonly factories: ServiceFactory<any, any>[] = [];
7
-
8
- constructor(
9
- factories: Set<ServiceFactory<unknown, readonly ServiceKey<unknown>[]>>,
10
- ) {
11
- this.factories = Array.from(factories);
12
- this.factories.forEach((factory) => {
13
- checkRecursiveDependencies(factory);
14
- checkMissingDependencies(factory, this.factories);
15
- });
16
- }
17
-
18
- public async get<T>(key: ServiceKey<T>): Promise<T> {
19
- const factory = this.factories.find((factory) => {
20
- return isSuitable(key, factory);
21
- });
22
-
23
- // Check if a factory to supply the requested key was not found
24
- if (!factory) {
25
- throw new Error(`Could not find a suitable factory for ${key.name}`);
26
- }
27
-
28
- // Resolve all dependencies first
29
- const dependencies = await Promise.all(
30
- factory.dependsOn.map((dependencyKey: ServiceKey<unknown>) => {
31
- return this.get(dependencyKey);
32
- }),
33
- );
34
-
35
- // Call the factory to retrieve the dependency
36
- return factory.initialize(...dependencies);
37
- }
38
-
39
- static from(
40
- entries: (ServiceModule | ServiceFactory<unknown, readonly ServiceKey<unknown>[]>)[],
41
- ): ServiceModule {
42
- return new ServiceModule(
43
- new Set(
44
- entries.flatMap((e) => {
45
- if (e instanceof ServiceModule) {
46
- return e.factories;
47
- } else {
48
- return [e];
49
- }
50
- }),
51
- ),
52
- );
53
- }
54
- }
55
-
56
- function checkRecursiveDependencies(
57
- factory: ServiceFactory<unknown, readonly ServiceKey<unknown>[]>,
58
- ) {
59
- const recursive = factory.dependsOn.some((dependencyKey) => {
60
- return dependencyKey === factory.provides;
61
- });
62
-
63
- if (recursive) {
64
- throw new Error(
65
- 'Recursive dependency detected on: ' + factory.provides.name,
66
- );
67
- }
68
- }
69
-
70
- function checkMissingDependencies(
71
- factory: ServiceFactory<unknown, readonly ServiceKey<unknown>[]>,
72
- factories: ServiceFactory<unknown>[],
73
- ) {
74
- const missingDependencies = factory.dependsOn.filter(
75
- (dependencyKey: ServiceKey<any>) => {
76
- return !isRegistered(dependencyKey, factories);
77
- },
78
- );
79
- if (missingDependencies.length === 0) {
80
- return;
81
- }
82
-
83
- const dependencyList = missingDependencies
84
- .map((dependencyKey) => ` -> ${dependencyKey.name}`)
85
- .join('\n');
86
- throw new Error(
87
- `${factory.provides.name} will fail because it depends on:\n ${dependencyList}`,
88
- );
89
- }
90
-
91
- function isRegistered(
92
- key: ServiceKey<unknown>,
93
- factories: ServiceFactory<unknown>[],
94
- ) {
95
- return factories.some((factory) => factory.provides === key);
96
- }
97
-
98
- function isSuitable<T, D extends readonly ServiceKey<unknown>[]>(
99
- key: ServiceKey<T>,
100
- factory: ServiceFactory<unknown, D>,
101
- ): factory is ServiceFactory<T, D> {
102
- return factory?.provides === key;
103
- }
1
+ import { ServiceKey } from './ServiceKey';
2
+ import { ServiceFactory } from './ServiceFactory';
3
+
4
+ export class ServiceModule {
5
+ readonly factories: ServiceFactory<any, any>[] = [];
6
+
7
+ constructor(
8
+ factories: Set<ServiceFactory<unknown, readonly ServiceKey<unknown>[]>>,
9
+ ) {
10
+ this.factories = Array.from(factories);
11
+ this.factories.forEach((factory) => {
12
+ checkRecursiveDependencies(factory);
13
+ checkMissingDependencies(factory, this.factories);
14
+ });
15
+ }
16
+
17
+ public async get<T>(key: ServiceKey<T>): Promise<T> {
18
+ const factory = this.factories.find((factory) => {
19
+ return isSuitable(key, factory);
20
+ });
21
+
22
+ // Check if a factory to supply the requested key was not found
23
+ if (!factory) {
24
+ throw new Error(`Could not find a suitable factory for ${key.name}`);
25
+ }
26
+
27
+ // Resolve all dependencies first
28
+ const dependencies = await Promise.all(
29
+ factory.dependsOn.map((dependencyKey: ServiceKey<unknown>) => {
30
+ return this.get(dependencyKey);
31
+ }),
32
+ );
33
+
34
+ // Call the factory to retrieve the dependency
35
+ return factory.initialize(...dependencies);
36
+ }
37
+
38
+ static from(
39
+ entries: (ServiceModule | ServiceFactory<unknown, readonly ServiceKey<unknown>[]>)[],
40
+ ): ServiceModule {
41
+ return new ServiceModule(
42
+ new Set(
43
+ entries.flatMap((e) => {
44
+ if (e instanceof ServiceModule) {
45
+ return e.factories;
46
+ } else {
47
+ return [e];
48
+ }
49
+ }),
50
+ ),
51
+ );
52
+ }
53
+ }
54
+
55
+ function checkRecursiveDependencies(
56
+ factory: ServiceFactory<unknown, readonly ServiceKey<unknown>[]>,
57
+ ) {
58
+ const recursive = factory.dependsOn.some((dependencyKey) => {
59
+ return dependencyKey === factory.provides;
60
+ });
61
+
62
+ if (recursive) {
63
+ throw new Error(
64
+ 'Recursive dependency detected on: ' + factory.provides.name,
65
+ );
66
+ }
67
+ }
68
+
69
+ function checkMissingDependencies(
70
+ factory: ServiceFactory<unknown, readonly ServiceKey<unknown>[]>,
71
+ factories: ServiceFactory<unknown>[],
72
+ ) {
73
+ const missingDependencies = factory.dependsOn.filter(
74
+ (dependencyKey: ServiceKey<any>) => {
75
+ return !isRegistered(dependencyKey, factories);
76
+ },
77
+ );
78
+ if (missingDependencies.length === 0) {
79
+ return;
80
+ }
81
+
82
+ const dependencyList = missingDependencies
83
+ .map((dependencyKey) => ` -> ${dependencyKey.name}`)
84
+ .join('\n');
85
+ throw new Error(
86
+ `${factory.provides.name} will fail because it depends on:\n ${dependencyList}`,
87
+ );
88
+ }
89
+
90
+ function isRegistered(
91
+ key: ServiceKey<unknown>,
92
+ factories: ServiceFactory<unknown>[],
93
+ ) {
94
+ return factories.some((factory) => factory.provides === key);
95
+ }
96
+
97
+ function isSuitable<T, D extends readonly ServiceKey<unknown>[]>(
98
+ key: ServiceKey<T>,
99
+ factory: ServiceFactory<unknown, D>,
100
+ ): factory is ServiceFactory<T, D> {
101
+ return factory?.provides === key;
102
+ }
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { ServiceKey } from './serviceKey';
2
- export { ServiceModule } from './serviceModule';
3
- export { type ServiceFactory, singletonFactory, oneShotFactory } from './serviceFactory';
4
- export { type ServiceProvider } from './serviceProvider';
1
+ export * from './ServiceKey';
2
+ export * from './ServiceModule';
3
+ export * from './ServiceFactory';
4
+ export * from './utils'
package/src/utils.ts ADDED
@@ -0,0 +1,141 @@
1
+ import { ServiceModule } from './ServiceModule';
2
+ import { ServiceKey } from './ServiceKey';
3
+
4
+ export interface DotGraphOptions {
5
+ /** Graph direction: 'TB' (top-bottom), 'LR' (left-right), 'BT' (bottom-top), 'RL' (right-left) */
6
+ direction?: 'TB' | 'LR' | 'BT' | 'RL';
7
+ /** Title for the graph */
8
+ title?: string;
9
+ /** Show nodes with no dependencies in a different color */
10
+ highlightLeaves?: boolean;
11
+ /** Show nodes with no dependents in a different color */
12
+ highlightRoots?: boolean;
13
+ }
14
+
15
+ /**
16
+ * Escapes special characters in strings for DOT notation
17
+ */
18
+ function escapeDotString(str: string): string {
19
+ return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
20
+ }
21
+
22
+ /**
23
+ * Generates a DOT notation graph from a ServiceModule.
24
+ * The output can be visualized using Graphviz tools or online viewers like:
25
+ * - https://dreampuf.github.io/GraphvizOnline/
26
+ * - https://edotor.net/
27
+ *
28
+ * Arrows point from dependencies to dependents (from what is needed to what needs it).
29
+ *
30
+ * @param module - The ServiceModule to convert to DOT notation
31
+ * @param options - Optional configuration for the graph appearance
32
+ * @returns A string containing the DOT notation graph
33
+ */
34
+ export function createDotGraph(
35
+ module: ServiceModule,
36
+ { direction, title, highlightLeaves, highlightRoots }: DotGraphOptions = {
37
+ direction: 'BT',
38
+ title: 'Service Dependency Graph',
39
+ highlightLeaves: true,
40
+ highlightRoots: true,
41
+ },
42
+ ): string {
43
+ const factories = module.factories;
44
+ const lines: string[] = [];
45
+
46
+ // Start the digraph
47
+ lines.push('digraph ServiceDependencies {');
48
+ lines.push(` label="${title}";`);
49
+ lines.push(' labelloc="t";');
50
+ lines.push(' fontsize=16;');
51
+ lines.push(` rankdir=${direction};`);
52
+ lines.push('');
53
+
54
+ // Default node styling
55
+ lines.push(' node [');
56
+ lines.push(' shape=box,');
57
+ lines.push(' style="rounded,filled",');
58
+ lines.push(' fillcolor="#e1f5ff",');
59
+ lines.push(' color="#0288d1",');
60
+ lines.push(' fontname="Arial",');
61
+ lines.push(' fontsize=12');
62
+ lines.push(' ];');
63
+ lines.push('');
64
+
65
+ // Default edge styling
66
+ lines.push(' edge [');
67
+ lines.push(' color="#666666",');
68
+ lines.push(' arrowsize=0.8');
69
+ lines.push(' ];');
70
+ lines.push('');
71
+
72
+ // Build dependency maps to identify leaves and roots
73
+ const hasDependencies = new Set<string>();
74
+ const hasDependents = new Set<string>();
75
+
76
+ factories.forEach((factory) => {
77
+ const serviceName = factory.provides.name;
78
+
79
+ if (factory.dependsOn.length > 0) {
80
+ hasDependencies.add(serviceName);
81
+ }
82
+
83
+ factory.dependsOn.forEach((dependency: ServiceKey<unknown>) => {
84
+ hasDependents.add(dependency.name);
85
+ });
86
+ });
87
+
88
+ // Define nodes with special styling for leaves and roots
89
+ const nodeIds = new Map<string, string>();
90
+ let nodeCounter = 0;
91
+
92
+ factories.forEach((factory) => {
93
+ const serviceName = factory.provides.name;
94
+ const nodeId = `node${nodeCounter++}`;
95
+ nodeIds.set(serviceName, nodeId);
96
+
97
+ const isLeaf = !hasDependencies.has(serviceName);
98
+ const isRoot = !hasDependents.has(serviceName);
99
+
100
+ let nodeStyle = '';
101
+
102
+ if (highlightLeaves && isLeaf) {
103
+ nodeStyle = ' [fillcolor="#c8e6c9", color="#388e3c"]';
104
+ } else if (highlightRoots && isRoot) {
105
+ nodeStyle = ' [fillcolor="#ffccbc", color="#d84315"]';
106
+ }
107
+
108
+ lines.push(
109
+ ` ${nodeId} [label="${escapeDotString(serviceName)}"]${nodeStyle};`,
110
+ );
111
+ });
112
+
113
+ lines.push('');
114
+
115
+ // Define edges (dependencies)
116
+ factories.forEach((factory) => {
117
+ const serviceName = factory.provides.name;
118
+ const serviceNodeId = nodeIds.get(serviceName)!;
119
+
120
+ factory.dependsOn.forEach((dependency: ServiceKey<unknown>) => {
121
+ const depName = dependency.name;
122
+ const depNodeId = nodeIds.get(depName);
123
+
124
+ if (depNodeId) {
125
+ // Arrow points from dependency to dependent (what provides -> what needs it)
126
+ lines.push(` ${depNodeId} -> ${serviceNodeId};`);
127
+ }
128
+ });
129
+ });
130
+
131
+ // Close the digraph
132
+ lines.push('}');
133
+
134
+ return lines.join('\n');
135
+ }
136
+
137
+ export function printDotGraph(module: ServiceModule) {
138
+ console.log(createDotGraph(module));
139
+ console.log('\n\nCopy the DOT output above and paste it into:');
140
+ console.log('https://dreampuf.github.io/GraphvizOnline/');
141
+ }
package/src/main.ts DELETED
@@ -1,19 +0,0 @@
1
- import { ServiceKey } from './serviceKey';
2
- import { singletonFactory } from './serviceFactory';
3
-
4
- const TEST_SERVICE_KEY = new ServiceKey<TestService>('testService');
5
- const FOO_KEY = new ServiceKey<string>('foo');
6
- const BAR_KEY = new ServiceKey<number>('bar');
7
-
8
- interface TestService {
9
- foo: string;
10
- bar: number;
11
- }
12
-
13
- const TestServiceFactory = singletonFactory({
14
- provides: TEST_SERVICE_KEY,
15
- dependsOn: [FOO_KEY, BAR_KEY] as const,
16
- initialize: (foo, bar) => {
17
- return { foo, bar };
18
- },
19
- });
@@ -1,78 +0,0 @@
1
- import { ServiceKey } from './serviceKey';
2
-
3
- // Helper types to extract the type from ServiceKey
4
- type ServiceType<T> = T extends ServiceKey<infer U> ? U : never;
5
-
6
- // Helper types to convert an array/tuple of ServiceKey to tuple of their types
7
- type DependencyTypes<T extends readonly ServiceKey<unknown>[]> = {
8
- [K in keyof T]: ServiceType<T[K]>;
9
- };
10
-
11
- export interface ServiceFactory<
12
- T,
13
- D extends readonly ServiceKey<unknown>[] = [],
14
- > {
15
- provides: ServiceKey<T>;
16
- dependsOn: D;
17
-
18
- initialize(...dependencies: DependencyTypes<D>): T | Promise<T>;
19
-
20
- dispose(instance: T): void;
21
- }
22
-
23
- export function singletonFactory<
24
- T,
25
- D extends readonly ServiceKey<unknown>[] = [],
26
- >({
27
- provides,
28
- dependsOn = [] as unknown as D,
29
- initialize,
30
- dispose = () => {},
31
- }: {
32
- provides: ServiceKey<T>;
33
- dependsOn?: D;
34
- initialize: (...dependencies: DependencyTypes<D>) => T | Promise<T>;
35
- dispose?: (instance: T) => void;
36
- }): ServiceFactory<T, D> {
37
- let instance: T | undefined;
38
-
39
- return {
40
- provides,
41
- dependsOn,
42
- async initialize(...dependencies: DependencyTypes<D>): Promise<T> {
43
- if (instance) {
44
- return instance;
45
- }
46
- instance = await initialize(...dependencies);
47
- return instance;
48
- },
49
- dispose(serviceInstance: T): void {
50
- if (instance === serviceInstance) {
51
- dispose(serviceInstance);
52
- instance = undefined;
53
- }
54
- },
55
- };
56
- }
57
-
58
- export function oneShotFactory<
59
- T,
60
- D extends readonly ServiceKey<unknown>[] = [],
61
- >({
62
- provides,
63
- dependsOn,
64
- initialize,
65
- dispose = () => {},
66
- }: {
67
- provides: ServiceKey<T>;
68
- dependsOn: D;
69
- initialize: (...dependencies: DependencyTypes<D>) => T | Promise<T>;
70
- dispose?: (instance: T) => void;
71
- }): ServiceFactory<T, D> {
72
- return {
73
- provides,
74
- dependsOn,
75
- initialize,
76
- dispose,
77
- };
78
- }
@@ -1,5 +0,0 @@
1
- import { ServiceKey } from './serviceKey';
2
-
3
- export interface ServiceProvider {
4
- get<T>(key: ServiceKey<T>): Promise<T>;
5
- }
File without changes