@tstdl/base 0.93.125 → 0.93.126

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.
Files changed (198) hide show
  1. package/ai/genkit/tests/multi-region.test.js +6 -6
  2. package/ai/index.d.ts +2 -6
  3. package/ai/index.js +2 -6
  4. package/ai/parser/index.d.ts +1 -0
  5. package/ai/parser/index.js +1 -0
  6. package/ai/parser/parser.d.ts +12 -0
  7. package/ai/parser/parser.js +28 -0
  8. package/ai/prompts/build.d.ts +21 -0
  9. package/ai/prompts/build.js +25 -0
  10. package/ai/prompts/index.d.ts +2 -0
  11. package/ai/prompts/index.js +2 -0
  12. package/ai/prompts/instructions-formatter.d.ts +9 -22
  13. package/ai/prompts/instructions-formatter.js +20 -7
  14. package/ai/prompts/instructions.js +1 -1
  15. package/ai/prompts/steering.d.ts +27 -0
  16. package/ai/prompts/steering.js +54 -0
  17. package/ai/tests/instructions-formatter.test.js +115 -0
  18. package/ai/tests/steering.test.js +37 -0
  19. package/application/application.d.ts +2 -1
  20. package/application/application.js +3 -0
  21. package/authentication/client/module.d.ts +1 -1
  22. package/authentication/client/module.js +4 -5
  23. package/authentication/tests/authentication-ancillary.service.test.js +1 -1
  24. package/authentication/tests/authentication.api-controller.test.js +3 -1
  25. package/authentication/tests/authentication.api-request-token.provider.test.js +1 -1
  26. package/authentication/tests/authentication.client-service.test.js +1 -1
  27. package/authentication/tests/authentication.service.test.js +1 -1
  28. package/authentication/tests/subject.service.test.js +1 -1
  29. package/circuit-breaker/tests/circuit-breaker.test.js +1 -1
  30. package/document-management/api/document-management.api.d.ts +16 -16
  31. package/document-management/api/document-management.api.js +12 -12
  32. package/document-management/models/ai-configuration.d.ts +59 -0
  33. package/document-management/models/ai-configuration.js +1 -0
  34. package/document-management/models/document-assignment-scope.model.js +2 -4
  35. package/document-management/models/document-assignment-task.model.js +2 -4
  36. package/document-management/models/document-collection-assignment.model.js +2 -4
  37. package/document-management/models/document-collection.model.js +2 -3
  38. package/document-management/models/document-content.model.d.ts +6 -0
  39. package/document-management/models/document-content.model.js +32 -0
  40. package/document-management/models/document-property-value.model.js +1 -2
  41. package/document-management/models/document-request-collection-assignment.model.js +2 -4
  42. package/document-management/models/document-request.model.js +2 -4
  43. package/document-management/models/document-tag-assignment.model.js +2 -3
  44. package/document-management/models/document-validation-execution-related-document.model.js +2 -4
  45. package/document-management/models/document-validation-execution.model.js +2 -5
  46. package/document-management/models/document-workflow.model.d.ts +2 -1
  47. package/document-management/models/document-workflow.model.js +4 -5
  48. package/document-management/models/document.model.js +2 -3
  49. package/document-management/models/index.d.ts +2 -0
  50. package/document-management/models/index.js +2 -0
  51. package/document-management/server/api/document-management.api.d.ts +7 -7
  52. package/document-management/server/api/document-management.api.js +9 -9
  53. package/document-management/server/configure.d.ts +4 -1
  54. package/document-management/server/configure.js +9 -4
  55. package/document-management/server/drizzle/{0000_complex_black_bird.sql → 0000_curious_nighthawk.sql} +7 -27
  56. package/document-management/server/drizzle/meta/0000_snapshot.json +12 -284
  57. package/document-management/server/drizzle/meta/_journal.json +2 -2
  58. package/document-management/server/module.d.ts +2 -0
  59. package/document-management/server/module.js +1 -0
  60. package/document-management/server/schemas.d.ts +2 -1
  61. package/document-management/server/services/document-file.service.d.ts +6 -6
  62. package/document-management/server/services/document-file.service.js +7 -81
  63. package/document-management/server/services/document-management-ai-provider.service.d.ts +66 -0
  64. package/document-management/server/services/document-management-ai-provider.service.js +2 -0
  65. package/document-management/server/services/document-management-ai.service.d.ts +44 -7
  66. package/document-management/server/services/document-management-ai.service.js +332 -329
  67. package/document-management/server/services/document-validation.service.d.ts +1 -1
  68. package/document-management/server/services/document-workflow.service.d.ts +4 -3
  69. package/document-management/server/services/document-workflow.service.js +26 -9
  70. package/document-management/server/services/document.service.d.ts +7 -3
  71. package/document-management/server/services/document.service.js +13 -4
  72. package/document-management/server/services/index.d.ts +1 -0
  73. package/document-management/server/services/index.js +1 -0
  74. package/document-management/server/validators/ai-validation-executor.d.ts +419 -12
  75. package/document-management/server/validators/ai-validation-executor.js +51 -46
  76. package/document-management/server/validators/single-document-validation-executor.d.ts +1 -3
  77. package/document-management/server/validators/single-document-validation-executor.js +2 -4
  78. package/document-management/service-models/document.service-model.d.ts +3 -3
  79. package/document-management/service-models/document.service-model.js +1 -1
  80. package/document-management/tests/ai-config-hierarchy.test.d.ts +1 -0
  81. package/document-management/tests/ai-config-hierarchy.test.js +64 -0
  82. package/document-management/tests/ai-config-integration.test.d.ts +1 -0
  83. package/document-management/tests/ai-config-integration.test.js +125 -0
  84. package/document-management/tests/ai-config-merge.test.d.ts +1 -0
  85. package/document-management/tests/ai-config-merge.test.js +38 -0
  86. package/document-management/tests/document-management-ai-overrides.test.d.ts +1 -0
  87. package/document-management/tests/document-management-ai-overrides.test.js +64 -0
  88. package/document-management/tests/document-management-core.test.js +6 -6
  89. package/document-management/tests/document-management.api.test.js +5 -5
  90. package/document-management/tests/document-statistics.service.test.js +10 -6
  91. package/document-management/tests/document-validation-ai-overrides.test.d.ts +1 -0
  92. package/document-management/tests/document-validation-ai-overrides.test.js +85 -0
  93. package/document-management/tests/document.service.test.js +15 -11
  94. package/document-management/tests/enum-helpers.test.js +5 -5
  95. package/examples/document-management/ai-provider.d.ts +20 -0
  96. package/examples/document-management/ai-provider.js +74 -0
  97. package/examples/document-management/main.js +9 -6
  98. package/examples/injector/graph-example.d.ts +1 -0
  99. package/examples/injector/graph-example.js +340 -0
  100. package/injector/decorators.d.ts +4 -4
  101. package/injector/decorators.js +5 -6
  102. package/injector/forward-ref.d.ts +15 -0
  103. package/injector/forward-ref.js +20 -0
  104. package/injector/graph.d.ts +113 -0
  105. package/injector/graph.js +631 -0
  106. package/injector/index.d.ts +2 -0
  107. package/injector/index.js +2 -0
  108. package/injector/inject.d.ts +15 -15
  109. package/injector/injector.d.ts +101 -13
  110. package/injector/injector.js +103 -59
  111. package/injector/resolve-chain.d.ts +20 -6
  112. package/injector/resolve-chain.js +39 -14
  113. package/injector/tests/advanced.test.d.ts +1 -0
  114. package/injector/tests/advanced.test.js +116 -0
  115. package/injector/tests/async-init.test.d.ts +1 -0
  116. package/injector/tests/async-init.test.js +77 -0
  117. package/injector/tests/basic.test.d.ts +1 -0
  118. package/injector/tests/basic.test.js +114 -0
  119. package/injector/tests/hierarchical.test.d.ts +1 -0
  120. package/injector/tests/hierarchical.test.js +59 -0
  121. package/injector/tests/lifecycles.test.d.ts +1 -0
  122. package/injector/tests/lifecycles.test.js +109 -0
  123. package/injector/token.d.ts +2 -1
  124. package/injector/token.js +4 -1
  125. package/injector/type-info.d.ts +1 -5
  126. package/injector/types.d.ts +4 -10
  127. package/logger/tests/pretty-print.test.d.ts +1 -0
  128. package/logger/{formatters → tests}/pretty-print.test.js +1 -1
  129. package/logger/transports/console.d.ts +3 -2
  130. package/logger/transports/console.js +4 -3
  131. package/notification/tests/notification-api.test.js +8 -5
  132. package/notification/tests/notification-client.test.d.ts +1 -0
  133. package/notification/tests/{unit/notification-client.test.js → notification-client.test.js} +5 -5
  134. package/notification/tests/notification-flow.test.js +6 -5
  135. package/notification/tests/notification-sse.service.test.js +1 -1
  136. package/notification/tests/notification-type.service.test.js +1 -1
  137. package/object-storage/s3/s3.object-storage.js +3 -0
  138. package/object-storage/s3/tests/s3.object-storage.integration.test.js +1 -1
  139. package/orm/tests/repository-attributes.test.js +10 -17
  140. package/orm/tests/repository-cti-mapping.test.js +2 -2
  141. package/orm/tests/repository-cti-soft-delete.test.js +1 -1
  142. package/orm/tests/repository-cti.test.js +19 -33
  143. package/orm/tests/repository-extra-coverage.test.js +1 -1
  144. package/orm/tests/repository-search.test.js +5 -2
  145. package/orm/tests/transaction-safety.test.js +1 -1
  146. package/package.json +7 -9
  147. package/rate-limit/tests/postgres-rate-limiter.test.js +6 -16
  148. package/renderer/d2.d.ts +77 -0
  149. package/renderer/d2.js +68 -0
  150. package/renderer/graphviz.d.ts +47 -0
  151. package/renderer/graphviz.js +58 -0
  152. package/renderer/index.d.ts +4 -0
  153. package/renderer/index.js +4 -0
  154. package/renderer/typst.d.ts +57 -0
  155. package/renderer/typst.js +62 -0
  156. package/rpc/adapters/readable-stream.adapter.d.ts +3 -0
  157. package/rpc/adapters/readable-stream.adapter.js +5 -1
  158. package/rpc/rpc.js +28 -3
  159. package/rpc/tests/rpc.integration.test.js +3 -1
  160. package/schema/schemas/nullable.js +1 -1
  161. package/task-queue/task-queue.d.ts +2 -0
  162. package/task-queue/task-queue.js +6 -2
  163. package/task-queue/tests/complex.test.js +1 -1
  164. package/task-queue/tests/dependencies.test.js +3 -3
  165. package/task-queue/tests/extensive-dependencies.test.js +1 -1
  166. package/task-queue/tests/queue.test.js +1 -1
  167. package/task-queue/tests/worker.test.js +4 -7
  168. package/test5.js +52 -8
  169. package/{unit-test → testing}/integration-setup.d.ts +1 -0
  170. package/{unit-test → testing}/integration-setup.js +13 -0
  171. package/utils/base64.d.ts +7 -0
  172. package/utils/base64.js +10 -1
  173. package/utils/noop.d.ts +7 -1
  174. package/utils/noop.js +7 -1
  175. package/ai/ai-file.service.d.ts +0 -57
  176. package/ai/ai-file.service.js +0 -233
  177. package/ai/ai-session.d.ts +0 -38
  178. package/ai/ai-session.js +0 -50
  179. package/ai/ai.service.d.ts +0 -126
  180. package/ai/ai.service.js +0 -481
  181. package/ai/functions.d.ts +0 -9
  182. package/ai/functions.js +0 -38
  183. package/ai/module.d.ts +0 -26
  184. package/ai/module.js +0 -25
  185. package/ai/types.d.ts +0 -229
  186. package/ai/types.js +0 -33
  187. package/latex/index.d.ts +0 -1
  188. package/latex/index.js +0 -1
  189. package/typst/index.d.ts +0 -1
  190. package/typst/index.js +0 -1
  191. package/typst/render.d.ts +0 -23
  192. package/typst/render.js +0 -32
  193. /package/{logger/formatters/pretty-print.test.d.ts → ai/tests/instructions-formatter.test.d.ts} +0 -0
  194. /package/{notification/tests/unit/notification-client.test.d.ts → ai/tests/steering.test.d.ts} +0 -0
  195. /package/{latex/render.d.ts → renderer/latex.d.ts} +0 -0
  196. /package/{latex/render.js → renderer/latex.js} +0 -0
  197. /package/{unit-test → testing}/index.d.ts +0 -0
  198. /package/{unit-test → testing}/index.js +0 -0
@@ -0,0 +1,631 @@
1
+ import { toArray } from '../utils/array/array.js';
2
+ import { isArray, isDefined, isFunction } from '../utils/type-guards.js';
3
+ import { Injector } from './injector.js';
4
+ import { afterResolve } from './interfaces.js';
5
+ import { isClassProvider, isFactoryProvider, isProviderWithInitializer, isTokenProvider, isValueProvider } from './provider.js';
6
+ import { getTokenName } from './token.js';
7
+ /**
8
+ * Extracts the dependency graph structure from an injector and optional resolution accesses.
9
+ * @param injector The injector to analyze.
10
+ * @param options Options for graph extraction.
11
+ * @returns A structured representation of the dependency graph.
12
+ */
13
+ export function getDependencyGraph(injector, options = {}) {
14
+ const { highlightCycles = true, includeTokens, excludeTokens, maxDepth, excludeLoggers = true } = options;
15
+ const capturedAccesses = options.accesses ?? injector.accesses;
16
+ const state = {
17
+ nextId: 0,
18
+ tokenToId: new Map(),
19
+ tokenToInjector: new Map(),
20
+ injectorNames: new Map(),
21
+ processedTokens: new Set(),
22
+ edges: new Map(),
23
+ metadata: new Map(),
24
+ dynamicResolutions: new Map(), // injectorId -> ownerToken -> tokens
25
+ cycleEdges: new Set(),
26
+ };
27
+ const getTokenId = (token) => {
28
+ if (!state.tokenToId.has(token)) {
29
+ state.tokenToId.set(token, `n${state.nextId++}`);
30
+ }
31
+ return state.tokenToId.get(token);
32
+ };
33
+ const getEffectiveTokenName = (token) => (typeof token == 'function' && (token.name != '')) ? token.name : getTokenName(token);
34
+ const recordMetadata = (token, registration) => {
35
+ if (state.metadata.has(token)) {
36
+ return;
37
+ }
38
+ const allRegistrations = toArray(registration);
39
+ const lifecycles = new Set();
40
+ const types = new Set();
41
+ let hasAfterResolve = false;
42
+ for (const registrationEntry of allRegistrations) {
43
+ lifecycles.add(registrationEntry.options.lifecycle ?? 'transient');
44
+ hasAfterResolve = hasAfterResolve || isDefined(registrationEntry.options.afterResolve);
45
+ if (isClassProvider(registrationEntry.provider)) {
46
+ types.add('class');
47
+ hasAfterResolve = hasAfterResolve || isProviderWithInitializer(registrationEntry.provider) || isFunction(registrationEntry.provider.useClass.prototype[afterResolve]);
48
+ }
49
+ else if (isValueProvider(registrationEntry.provider)) {
50
+ types.add('value');
51
+ }
52
+ else if (isTokenProvider(registrationEntry.provider)) {
53
+ types.add('token');
54
+ hasAfterResolve = hasAfterResolve || isProviderWithInitializer(registrationEntry.provider);
55
+ }
56
+ else if (isFactoryProvider(registrationEntry.provider)) {
57
+ types.add('factory');
58
+ hasAfterResolve = hasAfterResolve || isProviderWithInitializer(registrationEntry.provider);
59
+ }
60
+ }
61
+ state.metadata.set(token, {
62
+ lifecycle: Array.from(lifecycles).join('|'),
63
+ type: Array.from(types).join('|') || 'unknown',
64
+ hasAfterResolve,
65
+ });
66
+ };
67
+ const addEdge = (from, to, label, color, style, penwidth) => {
68
+ const key = `${getTokenId(from)}->${getTokenId(to)}:${label}:${style}:${color}`;
69
+ if (!state.edges.has(key)) {
70
+ state.edges.set(key, { from, to, label, color, style, penwidth, isCycle: false });
71
+ }
72
+ };
73
+ // 1. Initial traversal for registrations
74
+ const traverse = (injector) => {
75
+ if (injector == null) {
76
+ return;
77
+ }
78
+ traverse(injector.parent);
79
+ const injectorName = (injector.name != '') ? injector.name : (injector.parent == null ? 'RootInjector' : `Injector ${injector.id}`);
80
+ state.injectorNames.set(injector.id, injectorName);
81
+ const registrations = new Map(injector.getRegistrations());
82
+ if (injector.parent == null) {
83
+ for (const [token, globalRegistration] of Injector.getGlobalRegistrations()) {
84
+ if (!registrations.has(token)) {
85
+ registrations.set(token, (isArray(globalRegistration) ? globalRegistration.map((r) => ({ ...r, injector, resolutions: new Map() })) : { ...globalRegistration, injector, resolutions: new Map() }));
86
+ }
87
+ }
88
+ }
89
+ for (const [token, registration] of registrations) {
90
+ state.processedTokens.add(token);
91
+ if (!state.tokenToInjector.has(token)) {
92
+ state.tokenToInjector.set(token, injector.id);
93
+ recordMetadata(token, registration);
94
+ }
95
+ for (const entry of toArray(registration)) {
96
+ const target = isTokenProvider(entry.provider) ? (entry.provider.useToken ?? entry.provider.useTokenProvider?.()) : undefined;
97
+ if (isDefined(target)) {
98
+ addEdge(token, target, 'alias', '#7f8c8d', 'dashed');
99
+ }
100
+ }
101
+ }
102
+ };
103
+ traverse(injector);
104
+ // 2. Process accesses for edges and cycles
105
+ for (const access of capturedAccesses) {
106
+ const nodes = access.chain.nodes;
107
+ const chainTokens = new Map();
108
+ for (let i = 0; i < nodes.length; i++) {
109
+ const node = nodes[i];
110
+ if (node.type == 'ellipsis') {
111
+ continue;
112
+ }
113
+ const owner = node.injector.ownerToken;
114
+ if (isDefined(owner) && !state.processedTokens.has(owner)) {
115
+ state.processedTokens.add(owner);
116
+ let current = node.injector;
117
+ while (current && !current.getRegistrations().has(owner)) {
118
+ current = current.parent;
119
+ }
120
+ const ownerInjectorId = current?.id ?? node.injector.id;
121
+ state.tokenToInjector.set(owner, ownerInjectorId);
122
+ if (!state.injectorNames.has(ownerInjectorId)) {
123
+ state.injectorNames.set(ownerInjectorId, current?.name || `Injector ${ownerInjectorId}`);
124
+ }
125
+ }
126
+ if (highlightCycles && chainTokens.has(node.token)) {
127
+ for (let j = chainTokens.get(node.token); j < i; j++) {
128
+ const node1 = nodes[j];
129
+ const node2 = nodes[j + 1];
130
+ if (node1.type != 'ellipsis' && node2.type != 'ellipsis') {
131
+ state.cycleEdges.add(`${getTokenId(node1.token)}->${getTokenId(node2.token)}`);
132
+ }
133
+ }
134
+ }
135
+ chainTokens.set(node.token, i);
136
+ if (!state.processedTokens.has(node.token)) {
137
+ state.processedTokens.add(node.token);
138
+ state.tokenToInjector.set(node.token, node.resolvedBy?.id ?? node.injector.id);
139
+ state.injectorNames.set(node.injector.id, node.injector.name);
140
+ if (node.resolvedBy) {
141
+ state.injectorNames.set(node.resolvedBy.id, node.resolvedBy.name);
142
+ }
143
+ if (node.registration) {
144
+ recordMetadata(node.token, node.registration);
145
+ }
146
+ }
147
+ if (i == 0 && isDefined(owner) && owner != node.token) {
148
+ addEdge(owner, node.token, 'dynamic resolve', '#9b59b6', 'dotted');
149
+ const registrationInjectorId = node.resolvedBy?.id ?? node.injector.id;
150
+ if (!state.dynamicResolutions.has(registrationInjectorId)) {
151
+ state.dynamicResolutions.set(registrationInjectorId, new Map());
152
+ }
153
+ const map = state.dynamicResolutions.get(registrationInjectorId);
154
+ if (!map.has(owner)) {
155
+ map.set(owner, new Set());
156
+ }
157
+ map.get(owner).add(node.token);
158
+ }
159
+ if (i > 0 && nodes[i - 1].type != 'ellipsis') {
160
+ const previous = nodes[i - 1];
161
+ let label;
162
+ switch (node.type) {
163
+ case 'parameter':
164
+ label = `constructor[${node.index}]`;
165
+ break;
166
+ case 'property':
167
+ label = `property[${String(node.property)}]`;
168
+ break;
169
+ case 'inject':
170
+ label = 'inject()';
171
+ break;
172
+ case 'token':
173
+ label = 'token';
174
+ break;
175
+ default:
176
+ throw new Error(`Unknown chain node type ${node.type}`);
177
+ }
178
+ if (node.forwardRef) {
179
+ label += ' (forwardRef)';
180
+ }
181
+ if (node.optional) {
182
+ label += ' (optional)';
183
+ }
184
+ if (node.multi) {
185
+ label += ' (multi)';
186
+ }
187
+ if (isDefined(node.argument)) {
188
+ const s = JSON.stringify(node.argument);
189
+ label += `\narg: ${s.length > 20 ? `${s.substring(0, 17)}...` : s}`;
190
+ }
191
+ if (node.isCached) {
192
+ label += '\n(cached)';
193
+ }
194
+ if (node.resolvedBy && node.resolvedBy.id != node.injector.id) {
195
+ label += `\nvia ${node.injector.name}`;
196
+ }
197
+ addEdge(previous.token, node.token, label, node.forwardRef ? '#e67e22' : undefined, node.isCached ? (node.optional ? 'dashed,bold' : 'bold') : (node.optional ? 'dashed' : undefined));
198
+ }
199
+ }
200
+ }
201
+ // 3. Cycle Marking
202
+ for (const edge of state.edges.values()) {
203
+ if (state.cycleEdges.has(`${getTokenId(edge.from)}->${getTokenId(edge.to)}`)) {
204
+ edge.isCycle = true;
205
+ }
206
+ }
207
+ // 4. Filtering
208
+ const includeSet = includeTokens ? new Set(includeTokens) : undefined;
209
+ const excludeSet = excludeTokens ? new Set(excludeTokens) : undefined;
210
+ const isExcludedToken = (token) => excludeLoggers && ['Logger', 'LogManager', 'Injector'].includes(getEffectiveTokenName(token));
211
+ const reachable = new Map();
212
+ const collect = (token, depth) => {
213
+ if ((reachable.has(token) && reachable.get(token) <= depth) || excludeSet?.has(token) || isExcludedToken(token) || (isDefined(maxDepth) && depth > maxDepth)) {
214
+ return;
215
+ }
216
+ reachable.set(token, depth);
217
+ for (const edge of state.edges.values()) {
218
+ if (edge.from == token) {
219
+ collect(edge.to, depth + 1);
220
+ }
221
+ }
222
+ };
223
+ if (includeSet) {
224
+ for (const token of includeSet) {
225
+ collect(token, 0);
226
+ }
227
+ }
228
+ else if (capturedAccesses.length > 0) {
229
+ const touched = new Set();
230
+ for (const access of capturedAccesses) {
231
+ for (const node of access.chain.nodes) {
232
+ if (node.type != 'ellipsis') {
233
+ touched.add(node.token);
234
+ }
235
+ }
236
+ }
237
+ for (const token of touched) {
238
+ collect(token, 0);
239
+ }
240
+ }
241
+ else {
242
+ for (const token of state.processedTokens) {
243
+ if (!excludeSet?.has(token) && !isExcludedToken(token)) {
244
+ collect(token, 0);
245
+ }
246
+ }
247
+ }
248
+ const nodes = new Map();
249
+ for (const token of state.processedTokens) {
250
+ if (reachable.has(token)) {
251
+ nodes.set(token, {
252
+ token,
253
+ name: getEffectiveTokenName(token),
254
+ injectorId: state.tokenToInjector.get(token) ?? 0,
255
+ metadata: state.metadata.get(token),
256
+ });
257
+ }
258
+ }
259
+ const edges = Array.from(state.edges.values()).filter((edge) => reachable.has(edge.from) && reachable.has(edge.to));
260
+ return {
261
+ nodes,
262
+ edges,
263
+ injectorNames: state.injectorNames,
264
+ dynamicResolutions: state.dynamicResolutions,
265
+ };
266
+ }
267
+ /**
268
+ * Renders a dependency graph structure into a Graphviz DOT representation.
269
+ * @param graph The dependency graph to render.
270
+ * @param options Rendering options.
271
+ * @returns A string in DOT format.
272
+ */
273
+ export function renderDependencyGraphToDot(graph, options = {}) {
274
+ const { groupByInjector = true, showLegend = true, showMetadata = true, rankdir = 'LR', fontname = 'Helvetica', fontsize = 11, bgcolor, nodesep, ranksep } = options;
275
+ const nextIdState = { nextId: 0 };
276
+ const tokenToId = new Map();
277
+ const getTokenId = (token) => {
278
+ if (!tokenToId.has(token)) {
279
+ tokenToId.set(token, `n${nextIdState.nextId++}`);
280
+ }
281
+ return tokenToId.get(token);
282
+ };
283
+ const lines = ['strict digraph G {'];
284
+ if (bgcolor) {
285
+ lines.push(` bgcolor=${JSON.stringify(bgcolor)};`);
286
+ }
287
+ if (nodesep) {
288
+ lines.push(` nodesep=${nodesep};`);
289
+ }
290
+ if (ranksep) {
291
+ lines.push(` ranksep=${ranksep};`);
292
+ }
293
+ lines.push(` node [shape=box, fontname=${JSON.stringify(fontname)}, style=filled, fillcolor="#f9f9f9", fontsize=${fontsize}];`);
294
+ lines.push(` edge [fontname=${JSON.stringify(fontname)}, fontsize=${fontsize - 1}];`);
295
+ lines.push(` rankdir=${rankdir}; compound=true;`);
296
+ for (const edge of graph.edges) {
297
+ const color = edge.isCycle ? '#e74c3c' : edge.color;
298
+ const penwidth = edge.isCycle ? 2.0 : edge.penwidth;
299
+ const attributes = [
300
+ `label=${JSON.stringify(edge.label)}`,
301
+ color && `color=${JSON.stringify(color)}, fontcolor=${JSON.stringify(color)}`,
302
+ edge.style && `style=${JSON.stringify(edge.style)}`,
303
+ penwidth && `penwidth=${penwidth}`,
304
+ ].filter(Boolean).join(', ');
305
+ lines.push(` ${getTokenId(edge.from)} -> ${getTokenId(edge.to)} [${attributes}];`);
306
+ }
307
+ const renderNode = (node) => {
308
+ const metadata = node.metadata;
309
+ const lifecycle = metadata.lifecycle;
310
+ const first = lifecycle.split('|')[0];
311
+ const colors = { singleton: '#d1e7dd', injector: '#fff3cd', resolution: '#cfe2ff', transient: '#f8d7da' };
312
+ const shapes = { singleton: 'doubleoctagon', injector: 'component', resolution: 'ellipse', transient: 'box' };
313
+ const name = node.name + (metadata.hasAfterResolve ? ' *' : '');
314
+ const label = showMetadata ? `${name}\n[${lifecycle}, ${metadata.type}]` : name;
315
+ const fillcolor = colors[first] ?? '#f9f9f9';
316
+ const shape = shapes[first] ?? 'box';
317
+ return ` ${getTokenId(node.token)} [label=${JSON.stringify(label)}, fillcolor=${JSON.stringify(fillcolor)}, shape=${JSON.stringify(shape)}];`;
318
+ };
319
+ const injectors = groupByInjector ? Array.from(graph.injectorNames.keys()) : [0];
320
+ for (const injectorId of injectors) {
321
+ if (groupByInjector) {
322
+ lines.push(` subgraph cluster_${injectorId} {`);
323
+ lines.push(` label = ${JSON.stringify(graph.injectorNames.get(injectorId) ?? `Injector ${injectorId}`)}; style = dotted; fontname = ${JSON.stringify(`${fontname}-Bold`)}; fontsize = ${fontsize + 1};`);
324
+ }
325
+ const injectorTokens = Array.from(graph.nodes.values()).filter((node) => !groupByInjector || node.injectorId == injectorId);
326
+ const dynamic = graph.dynamicResolutions.get(injectorId);
327
+ const inDynamic = new Set();
328
+ if (dynamic) {
329
+ for (const [owner, tokens] of dynamic) {
330
+ if (!graph.nodes.has(owner)) {
331
+ continue;
332
+ }
333
+ const subNodes = Array.from(tokens).filter((token) => graph.nodes.has(token) && (!groupByInjector || graph.nodes.get(token).injectorId == injectorId)).map((token) => graph.nodes.get(token));
334
+ if (subNodes.length == 0) {
335
+ continue;
336
+ }
337
+ lines.push(` subgraph cluster_${injectorId}_${getTokenId(owner)} { label = ${JSON.stringify(`Dynamic Resolutions of ${graph.nodes.get(owner).name}`)}; style = dashed; color = "#9b59b6"; fontname = ${JSON.stringify(fontname)}; fontsize = ${fontsize};`);
338
+ for (const node of subNodes) {
339
+ lines.push(renderNode(node));
340
+ inDynamic.add(node.token);
341
+ }
342
+ lines.push(' }');
343
+ }
344
+ }
345
+ for (const node of injectorTokens) {
346
+ if (!inDynamic.has(node.token)) {
347
+ lines.push(renderNode(node));
348
+ }
349
+ }
350
+ if (groupByInjector) {
351
+ lines.push(' }');
352
+ }
353
+ }
354
+ if (showLegend) {
355
+ const legendFontSize = fontsize - 1;
356
+ lines.push(` subgraph cluster_legend { label = "Legend"; style = solid; color = gray; fontname = ${JSON.stringify(`${fontname}-Bold`)}; fontsize = ${fontsize + 1};`);
357
+ lines.push(' // Column 1: Scoping');
358
+ lines.push(' node [fixedsize=true, width=1.6, height=0.4];');
359
+ lines.push(` l_singleton [label="Singleton", fillcolor="#d1e7dd", shape=doubleoctagon, fontsize=${legendFontSize}];`);
360
+ lines.push(` l_injector [label="Injector Scoped", fillcolor="#fff3cd", shape=component, fontsize=${legendFontSize}];`);
361
+ lines.push(` l_resolution [label="Resolution Scoped", fillcolor="#cfe2ff", shape=ellipse, fontsize=${legendFontSize}];`);
362
+ lines.push(` l_transient [label="Transient", fillcolor="#f8d7da", shape=box, fontsize=${legendFontSize}];`);
363
+ lines.push(' // Column 2 & 3: Dependency Types (Part 1)');
364
+ lines.push(' node [shape=point, width=0.05, height=0.05, label=""];');
365
+ lines.push(' l_circular_from; l_cached_from; l_optional_from; l_cross_from;');
366
+ lines.push(' node [shape=plaintext, width=1.6, height=0.4];');
367
+ lines.push(` l_circular_to [label="Circular Dependency", fontsize=${legendFontSize}];`);
368
+ lines.push(` l_cached_to [label="Cached Resolution", fontsize=${legendFontSize}];`);
369
+ lines.push(` l_optional_to [label="Optional Dependency", fontsize=${legendFontSize}];`);
370
+ lines.push(` l_cross_to [label="Cross-Injector", fontsize=${legendFontSize}];`);
371
+ lines.push(' // Column 4 & 5: Dependency Types (Part 2)');
372
+ lines.push(' node [shape=point, width=0.05, height=0.05, label=""];');
373
+ lines.push(' l_alias_from; l_forward_from; l_dynamic_from; l_spacer_from;');
374
+ lines.push(' node [shape=plaintext, width=1.6, height=0.4];');
375
+ lines.push(` l_alias_to [label="Alias", fontsize=${legendFontSize}];`);
376
+ lines.push(` l_forward_to [label="Forward Reference", fontsize=${legendFontSize}];`);
377
+ lines.push(` l_dynamic_to [label="Dynamic Resolve", fontsize=${legendFontSize}];`);
378
+ lines.push(' l_spacer_to [label="", style=invis];');
379
+ lines.push(' // Column Ranks (Vertical Stacking)');
380
+ lines.push(' { rank=same; l_singleton l_injector l_resolution l_transient }');
381
+ lines.push(' { rank=same; l_circular_from l_cached_from l_optional_from l_cross_from }');
382
+ lines.push(' { rank=same; l_circular_to l_cached_to l_optional_to l_cross_to }');
383
+ lines.push(' { rank=same; l_alias_from l_forward_from l_dynamic_from l_spacer_from }');
384
+ lines.push(' { rank=same; l_alias_to l_forward_to l_dynamic_to l_spacer_to }');
385
+ lines.push(' // Horizontal Row Alignment & Arrows');
386
+ lines.push(' l_singleton -> l_circular_from [style=invis, minlen=1];');
387
+ lines.push(' l_circular_from -> l_circular_to [color="#e74c3c", penwidth=2, minlen=1];');
388
+ lines.push(' l_circular_to -> l_alias_from [style=invis, minlen=1];');
389
+ lines.push(' l_alias_from -> l_alias_to [color="#7f8c8d", style=dashed, minlen=1];');
390
+ lines.push(' l_injector -> l_cached_from [style=invis, minlen=1];');
391
+ lines.push(' l_cached_from -> l_cached_to [style=bold, minlen=1];');
392
+ lines.push(' l_cached_to -> l_forward_from [style=invis, minlen=1];');
393
+ lines.push(' l_forward_from -> l_forward_to [color="#e67e22", minlen=1];');
394
+ lines.push(' l_resolution -> l_optional_from [style=invis, minlen=1];');
395
+ lines.push(' l_optional_from -> l_optional_to [style=dashed, minlen=1];');
396
+ lines.push(' l_optional_to -> l_dynamic_from [style=invis, minlen=1];');
397
+ lines.push(' l_dynamic_from -> l_dynamic_to [color="#9b59b6", style=dotted, minlen=1];');
398
+ lines.push(' l_transient -> l_cross_from [style=invis, minlen=1];');
399
+ lines.push(' l_cross_from -> l_cross_to [minlen=1];');
400
+ lines.push(' l_cross_to -> l_spacer_from [style=invis, minlen=1];');
401
+ lines.push(' l_spacer_from -> l_spacer_to [style=invis, minlen=1];');
402
+ lines.push(' // Vertical Reinforcement');
403
+ lines.push(' l_singleton -> l_injector -> l_resolution -> l_transient [style=invis];');
404
+ lines.push(' l_circular_from -> l_cached_from -> l_optional_from -> l_cross_from [style=invis];');
405
+ lines.push(' l_circular_to -> l_cached_to -> l_optional_to -> l_cross_to [style=invis];');
406
+ lines.push(' l_alias_from -> l_forward_from -> l_dynamic_from -> l_spacer_from [style=invis];');
407
+ }
408
+ return lines.join('\n');
409
+ }
410
+ /**
411
+ * Renders a dependency graph structure into a D2 representation.
412
+ * @param graph The dependency graph to render.
413
+ * @param options Rendering options.
414
+ * @returns A string in D2 format.
415
+ */
416
+ export function renderDependencyGraphToD2(graph, options = {}) {
417
+ const { groupByInjector = true, showLegend = true, showMetadata = true, rankdir = 'LR', bgcolor } = options;
418
+ const nextIdState = { nextId: 0 };
419
+ const tokenToId = new Map();
420
+ const getTokenId = (token) => {
421
+ if (!tokenToId.has(token)) {
422
+ tokenToId.set(token, `n${nextIdState.nextId++}`);
423
+ }
424
+ return tokenToId.get(token);
425
+ };
426
+ const getTokenPath = (token) => {
427
+ const node = graph.nodes.get(token);
428
+ const id = getTokenId(token);
429
+ if (!node || !groupByInjector) {
430
+ return id;
431
+ }
432
+ const injectorName = graph.injectorNames.get(node.injectorId) ?? `Injector ${node.injectorId}`;
433
+ let path = `${JSON.stringify(injectorName)}.${id}`;
434
+ const dynamic = graph.dynamicResolutions.get(node.injectorId);
435
+ if (dynamic) {
436
+ for (const [owner, tokens] of dynamic) {
437
+ if (tokens.has(token) && graph.nodes.has(owner)) {
438
+ const ownerName = graph.nodes.get(owner).name;
439
+ path = `${JSON.stringify(injectorName)}.${JSON.stringify(`Dynamic Resolutions of ${ownerName}`)}.${id}`;
440
+ break;
441
+ }
442
+ }
443
+ }
444
+ return path;
445
+ };
446
+ const lines = [];
447
+ if (rankdir == 'LR') {
448
+ lines.push('direction: right');
449
+ }
450
+ else if (rankdir == 'TB') {
451
+ lines.push('direction: down');
452
+ }
453
+ else if (rankdir == 'BT') {
454
+ lines.push('direction: up');
455
+ }
456
+ else if (rankdir == 'RL') {
457
+ lines.push('direction: left');
458
+ }
459
+ if (bgcolor) {
460
+ lines.push(`style: {\n fill: ${JSON.stringify(bgcolor)}\n}`);
461
+ }
462
+ lines.push('classes: {');
463
+ lines.push(' singleton: {');
464
+ lines.push(' shape: hexagon');
465
+ lines.push(' style: { fill: "#d1e7dd" }');
466
+ lines.push(' }');
467
+ lines.push(' injector_scoped: {');
468
+ lines.push(' shape: package');
469
+ lines.push(' style: { fill: "#fff3cd" }');
470
+ lines.push(' }');
471
+ lines.push(' resolution_scoped: {');
472
+ lines.push(' shape: oval');
473
+ lines.push(' style: { fill: "#cfe2ff" }');
474
+ lines.push(' }');
475
+ lines.push(' transient: {');
476
+ lines.push(' shape: rectangle');
477
+ lines.push(' style: { fill: "#f8d7da" }');
478
+ lines.push(' }');
479
+ lines.push('}');
480
+ const renderNode = (node, indent) => {
481
+ const nodeLines = [];
482
+ const metadata = node.metadata;
483
+ const lifecycle = metadata.lifecycle;
484
+ const first = lifecycle.split('|')[0];
485
+ const classMap = { singleton: 'singleton', injector: 'injector_scoped', resolution: 'resolution_scoped', transient: 'transient' };
486
+ const className = classMap[first] ?? 'transient';
487
+ const name = node.name + (metadata.hasAfterResolve ? ' *' : '');
488
+ const label = showMetadata ? `${name}\n[${lifecycle}, ${metadata.type}]` : name;
489
+ nodeLines.push(`${indent}${getTokenId(node.token)}: ${JSON.stringify(label)} {`);
490
+ nodeLines.push(`${indent} class: ${className}`);
491
+ nodeLines.push(`${indent}}`);
492
+ return nodeLines;
493
+ };
494
+ for (const edge of graph.edges) {
495
+ const color = edge.isCycle ? '#e74c3c' : edge.color;
496
+ let penwidth = edge.isCycle ? 2.0 : edge.penwidth;
497
+ if (edge.style?.includes('bold')) {
498
+ penwidth = (penwidth ?? 1) + 2;
499
+ }
500
+ const from = getTokenPath(edge.from);
501
+ const to = getTokenPath(edge.to);
502
+ let edgeLine = `${from} -> ${to}`;
503
+ if (edge.label || color || edge.style || penwidth) {
504
+ const label = edge.label?.split('\n')[0];
505
+ const tooltip = edge.label;
506
+ edgeLine += `: ${JSON.stringify(label)} {`;
507
+ lines.push(edgeLine);
508
+ if (tooltip && tooltip !== label) {
509
+ lines.push(` tooltip: ${JSON.stringify(tooltip)}`);
510
+ }
511
+ if (color) {
512
+ lines.push(` style.stroke: ${JSON.stringify(color)}`);
513
+ lines.push(` style.font-color: ${JSON.stringify(color)}`);
514
+ }
515
+ if (edge.style?.includes('dashed')) {
516
+ lines.push(' style.stroke-dash: 5');
517
+ }
518
+ else if (edge.style?.includes('dotted')) {
519
+ lines.push(' style.stroke-dash: 2');
520
+ }
521
+ if (penwidth) {
522
+ lines.push(` style.stroke-width: ${penwidth}`);
523
+ }
524
+ lines.push('}');
525
+ }
526
+ else {
527
+ lines.push(edgeLine);
528
+ }
529
+ }
530
+ const injectors = groupByInjector ? Array.from(graph.injectorNames.keys()) : [0];
531
+ for (const injectorId of injectors) {
532
+ let indent = '';
533
+ if (groupByInjector) {
534
+ const name = graph.injectorNames.get(injectorId) ?? `Injector ${injectorId}`;
535
+ lines.push(`${JSON.stringify(name)}: {`);
536
+ lines.push(' style: {');
537
+ lines.push(' stroke-dash: 5');
538
+ lines.push(' border-radius: 10');
539
+ lines.push(' }');
540
+ indent = ' ';
541
+ }
542
+ const injectorTokens = Array.from(graph.nodes.values()).filter((node) => !groupByInjector || node.injectorId == injectorId);
543
+ const dynamic = graph.dynamicResolutions.get(injectorId);
544
+ const inDynamic = new Set();
545
+ if (dynamic) {
546
+ for (const [owner, tokens] of dynamic) {
547
+ if (!graph.nodes.has(owner)) {
548
+ continue;
549
+ }
550
+ const subNodes = Array.from(tokens).filter((token) => graph.nodes.has(token) && (!groupByInjector || graph.nodes.get(token).injectorId == injectorId)).map((token) => graph.nodes.get(token));
551
+ if (subNodes.length == 0) {
552
+ continue;
553
+ }
554
+ const ownerName = graph.nodes.get(owner).name;
555
+ lines.push(`${indent}${JSON.stringify(`Dynamic Resolutions of ${ownerName}`)}: {`);
556
+ lines.push(`${indent} style: {`);
557
+ lines.push(`${indent} stroke: "#9b59b6"`);
558
+ lines.push(`${indent} stroke-dash: 5`);
559
+ lines.push(`${indent} border-radius: 10`);
560
+ lines.push(`${indent} }`);
561
+ for (const node of subNodes) {
562
+ lines.push(...renderNode(node, `${indent} `));
563
+ inDynamic.add(node.token);
564
+ }
565
+ lines.push(`${indent}}`);
566
+ }
567
+ }
568
+ for (const node of injectorTokens) {
569
+ if (!inDynamic.has(node.token)) {
570
+ lines.push(...renderNode(node, indent));
571
+ }
572
+ }
573
+ if (groupByInjector) {
574
+ lines.push('}');
575
+ }
576
+ }
577
+ if (showLegend) {
578
+ lines.push('Legend: {');
579
+ lines.push(' shape: package');
580
+ lines.push(' near: top-right');
581
+ lines.push(' grid-columns: 2');
582
+ lines.push(' style: {');
583
+ lines.push(' stroke-dash: 0');
584
+ lines.push(' border-radius: 10');
585
+ lines.push(' }');
586
+ lines.push(' Singleton: { class: singleton }');
587
+ lines.push(' Injector Scoped: { class: injector_scoped }');
588
+ lines.push(' Resolution Scoped: { class: resolution_scoped }');
589
+ lines.push(' Transient: { class: transient }');
590
+ lines.push('}');
591
+ }
592
+ return lines.join('\n');
593
+ }
594
+ /**
595
+ * Generates a Graphviz DOT representation of the dependency injection graph.
596
+ * @param injector The injector to analyze.
597
+ * @param options Options for graph generation.
598
+ * @returns A string in DOT format.
599
+ */
600
+ export function generateDependencyGraph(injector, options = {}) {
601
+ const graph = getDependencyGraph(injector, options);
602
+ return renderDependencyGraphToDot(graph, options);
603
+ }
604
+ /**
605
+ * Generates a D2 representation of the dependency injection graph.
606
+ * @param injector The injector to analyze.
607
+ * @param options Options for graph generation.
608
+ * @returns A string in D2 format.
609
+ */
610
+ export function generateDependencyGraphD2(injector, options = {}) {
611
+ const graph = getDependencyGraph(injector, options);
612
+ return renderDependencyGraphToD2(graph, options);
613
+ }
614
+ /**
615
+ * Generates a Graphviz DOT representation of the application's dependency injection graph.
616
+ * @param application The application to analyze.
617
+ * @param options Options for graph generation.
618
+ * @returns A string in DOT format.
619
+ */
620
+ export function generateApplicationDependencyGraph(application, options = {}) {
621
+ return generateDependencyGraph(application.injector, options);
622
+ }
623
+ /**
624
+ * Generates a D2 representation of the application's dependency injection graph.
625
+ * @param application The application to analyze.
626
+ * @param options Options for graph generation.
627
+ * @returns A string in D2 format.
628
+ */
629
+ export function generateApplicationDependencyGraphD2(application, options = {}) {
630
+ return generateDependencyGraphD2(application.injector, options);
631
+ }
@@ -4,6 +4,8 @@
4
4
  * @module Injector
5
5
  */
6
6
  export * from './decorators.js';
7
+ export * from './forward-ref.js';
8
+ export * from './graph.js';
7
9
  export * from './inject.js';
8
10
  export * from './injector.js';
9
11
  export * from './interfaces.js';
package/injector/index.js CHANGED
@@ -4,6 +4,8 @@
4
4
  * @module Injector
5
5
  */
6
6
  export * from './decorators.js';
7
+ export * from './forward-ref.js';
8
+ export * from './graph.js';
7
9
  export * from './inject.js';
8
10
  export * from './injector.js';
9
11
  export * from './interfaces.js';