@codefast/di 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (266) hide show
  1. package/CHANGELOG.md +712 -0
  2. package/README.md +119 -30
  3. package/dist/ambient/active-container.d.ts +37 -0
  4. package/dist/ambient/active-container.d.ts.map +1 -0
  5. package/dist/ambient/active-container.js +41 -0
  6. package/dist/ambient/active-container.js.map +1 -0
  7. package/dist/container/binding-builders.d.ts +12 -8
  8. package/dist/container/binding-builders.d.ts.map +1 -1
  9. package/dist/container/binding-builders.js +62 -31
  10. package/dist/container/binding-builders.js.map +1 -1
  11. package/dist/container/container.d.ts +33 -19
  12. package/dist/container/container.d.ts.map +1 -1
  13. package/dist/container/container.js +281 -118
  14. package/dist/container/container.js.map +1 -1
  15. package/dist/{resolution → core}/binding-scope.d.ts +2 -2
  16. package/dist/core/binding-scope.d.ts.map +1 -0
  17. package/dist/core/binding-scope.js.map +1 -0
  18. package/dist/{binding.d.ts → core/binding.d.ts} +70 -15
  19. package/dist/core/binding.d.ts.map +1 -0
  20. package/dist/{binding.js → core/binding.js} +27 -12
  21. package/dist/core/binding.js.map +1 -0
  22. package/dist/core/constraint-requirement.d.ts +53 -0
  23. package/dist/core/constraint-requirement.d.ts.map +1 -0
  24. package/dist/core/constraint-requirement.js +62 -0
  25. package/dist/core/constraint-requirement.js.map +1 -0
  26. package/dist/{constructor-type.d.ts → core/constructor-type.d.ts} +1 -1
  27. package/dist/core/constructor-type.d.ts.map +1 -0
  28. package/dist/core/constructor-type.js.map +1 -0
  29. package/dist/{module.d.ts → core/module.d.ts} +5 -5
  30. package/dist/core/module.d.ts.map +1 -0
  31. package/dist/{module.js → core/module.js} +4 -4
  32. package/dist/core/module.js.map +1 -0
  33. package/dist/{registry.d.ts → core/registry.d.ts} +27 -7
  34. package/dist/core/registry.d.ts.map +1 -0
  35. package/dist/{registry.js → core/registry.js} +125 -52
  36. package/dist/core/registry.js.map +1 -0
  37. package/dist/core/tag.d.ts +80 -0
  38. package/dist/core/tag.d.ts.map +1 -0
  39. package/dist/core/tag.js +96 -0
  40. package/dist/core/tag.js.map +1 -0
  41. package/dist/{token.d.ts → core/token.d.ts} +2 -6
  42. package/dist/core/token.d.ts.map +1 -0
  43. package/dist/core/token.js +13 -0
  44. package/dist/core/token.js.map +1 -0
  45. package/dist/{types.d.ts → core/types.d.ts} +27 -21
  46. package/dist/core/types.d.ts.map +1 -0
  47. package/dist/core/types.js.map +1 -0
  48. package/dist/decorators/inject.d.ts +5 -49
  49. package/dist/decorators/inject.d.ts.map +1 -1
  50. package/dist/decorators/inject.js +34 -104
  51. package/dist/decorators/inject.js.map +1 -1
  52. package/dist/decorators/injectable.d.ts +25 -5
  53. package/dist/decorators/injectable.d.ts.map +1 -1
  54. package/dist/decorators/injectable.js +2 -3
  55. package/dist/decorators/injectable.js.map +1 -1
  56. package/dist/decorators/lifecycle-decorators.d.ts +4 -2
  57. package/dist/decorators/lifecycle-decorators.d.ts.map +1 -1
  58. package/dist/decorators/lifecycle-decorators.js +19 -25
  59. package/dist/decorators/lifecycle-decorators.js.map +1 -1
  60. package/dist/{resolution → errors}/diagnostics.d.ts +2 -0
  61. package/dist/errors/diagnostics.d.ts.map +1 -0
  62. package/dist/errors/diagnostics.js.map +1 -0
  63. package/dist/{errors.d.ts → errors/errors.d.ts} +89 -4
  64. package/dist/errors/errors.d.ts.map +1 -0
  65. package/dist/{errors.js → errors/errors.js} +129 -7
  66. package/dist/errors/errors.js.map +1 -0
  67. package/dist/index.d.ts +21 -14
  68. package/dist/index.d.ts.map +1 -1
  69. package/dist/index.js +15 -7
  70. package/dist/index.js.map +1 -1
  71. package/dist/injection/descriptor.d.ts +79 -0
  72. package/dist/injection/descriptor.d.ts.map +1 -0
  73. package/dist/injection/descriptor.js +118 -0
  74. package/dist/injection/descriptor.js.map +1 -0
  75. package/dist/{resolution → injection}/resolve-options.d.ts +21 -11
  76. package/dist/injection/resolve-options.d.ts.map +1 -0
  77. package/dist/injection/resolve-options.js +101 -0
  78. package/dist/injection/resolve-options.js.map +1 -0
  79. package/dist/introspection/dependency-graph.d.ts +16 -5
  80. package/dist/introspection/dependency-graph.d.ts.map +1 -1
  81. package/dist/introspection/dependency-graph.js +138 -57
  82. package/dist/introspection/dependency-graph.js.map +1 -1
  83. package/dist/introspection/graph-adapters/cytoscape.d.ts +6 -3
  84. package/dist/introspection/graph-adapters/cytoscape.d.ts.map +1 -1
  85. package/dist/introspection/graph-adapters/cytoscape.js +3 -0
  86. package/dist/introspection/graph-adapters/cytoscape.js.map +1 -1
  87. package/dist/introspection/graph-adapters/dot.d.ts.map +1 -1
  88. package/dist/introspection/graph-adapters/dot.js +12 -5
  89. package/dist/introspection/graph-adapters/dot.js.map +1 -1
  90. package/dist/introspection/graph-adapters/mermaid.d.ts +9 -0
  91. package/dist/introspection/graph-adapters/mermaid.d.ts.map +1 -0
  92. package/dist/introspection/graph-adapters/mermaid.js +47 -0
  93. package/dist/introspection/graph-adapters/mermaid.js.map +1 -0
  94. package/dist/introspection/graph-adapters/reactflow.d.ts +6 -3
  95. package/dist/introspection/graph-adapters/reactflow.d.ts.map +1 -1
  96. package/dist/introspection/graph-adapters/reactflow.js +5 -1
  97. package/dist/introspection/graph-adapters/reactflow.js.map +1 -1
  98. package/dist/introspection/inspector.d.ts +4 -4
  99. package/dist/introspection/inspector.d.ts.map +1 -1
  100. package/dist/introspection/inspector.js +10 -6
  101. package/dist/introspection/inspector.js.map +1 -1
  102. package/dist/lifecycle/lifecycle-manager.d.ts +27 -0
  103. package/dist/lifecycle/lifecycle-manager.d.ts.map +1 -0
  104. package/dist/{resolution/lifecycle.js → lifecycle/lifecycle-manager.js} +39 -9
  105. package/dist/lifecycle/lifecycle-manager.js.map +1 -0
  106. package/dist/lifecycle/scope-manager.d.ts +48 -0
  107. package/dist/lifecycle/scope-manager.d.ts.map +1 -0
  108. package/dist/lifecycle/scope-manager.js +135 -0
  109. package/dist/lifecycle/scope-manager.js.map +1 -0
  110. package/dist/metadata/metadata-reader-token.d.ts +1 -1
  111. package/dist/metadata/metadata-reader-token.d.ts.map +1 -1
  112. package/dist/metadata/metadata-reader-token.js +1 -1
  113. package/dist/metadata/metadata-reader-token.js.map +1 -1
  114. package/dist/metadata/metadata-types.d.ts +9 -9
  115. package/dist/metadata/metadata-types.d.ts.map +1 -1
  116. package/dist/metadata/symbol-metadata-reader.d.ts +19 -6
  117. package/dist/metadata/symbol-metadata-reader.d.ts.map +1 -1
  118. package/dist/metadata/symbol-metadata-reader.js +89 -9
  119. package/dist/metadata/symbol-metadata-reader.js.map +1 -1
  120. package/dist/metadata/verifying-metadata-reader.d.ts +16 -0
  121. package/dist/metadata/verifying-metadata-reader.d.ts.map +1 -0
  122. package/dist/metadata/verifying-metadata-reader.js +32 -0
  123. package/dist/metadata/verifying-metadata-reader.js.map +1 -0
  124. package/dist/resolution/{activation-need.d.ts → cache/activation-need.d.ts} +5 -5
  125. package/dist/resolution/cache/activation-need.d.ts.map +1 -0
  126. package/dist/resolution/cache/activation-need.js.map +1 -0
  127. package/dist/resolution/{binding-lookup-cache.d.ts → cache/binding-lookup-cache.d.ts} +9 -5
  128. package/dist/resolution/cache/binding-lookup-cache.d.ts.map +1 -0
  129. package/dist/resolution/{binding-lookup-cache.js → cache/binding-lookup-cache.js} +55 -0
  130. package/dist/resolution/cache/binding-lookup-cache.js.map +1 -0
  131. package/dist/resolution/cache/class-introspector.d.ts +66 -0
  132. package/dist/resolution/cache/class-introspector.d.ts.map +1 -0
  133. package/dist/resolution/cache/class-introspector.js +194 -0
  134. package/dist/resolution/cache/class-introspector.js.map +1 -0
  135. package/dist/resolution/context.d.ts +84 -0
  136. package/dist/resolution/context.d.ts.map +1 -0
  137. package/dist/resolution/{environment.js → context.js} +54 -80
  138. package/dist/resolution/context.js.map +1 -0
  139. package/dist/resolution/path/resolution-path.d.ts +94 -0
  140. package/dist/resolution/path/resolution-path.d.ts.map +1 -0
  141. package/dist/resolution/path/resolution-path.js +117 -0
  142. package/dist/resolution/path/resolution-path.js.map +1 -0
  143. package/dist/resolution/{instantiation-plan.d.ts → plan/instantiation-plan.d.ts} +37 -7
  144. package/dist/resolution/plan/instantiation-plan.d.ts.map +1 -0
  145. package/dist/resolution/plan/instantiation-plan.js +419 -0
  146. package/dist/resolution/plan/instantiation-plan.js.map +1 -0
  147. package/dist/resolution/resolver.d.ts +28 -28
  148. package/dist/resolution/resolver.d.ts.map +1 -1
  149. package/dist/resolution/resolver.js +581 -239
  150. package/dist/resolution/resolver.js.map +1 -1
  151. package/dist/resolution/select/binding-select.d.ts +34 -0
  152. package/dist/resolution/select/binding-select.d.ts.map +1 -0
  153. package/dist/resolution/select/binding-select.js +175 -0
  154. package/dist/resolution/select/binding-select.js.map +1 -0
  155. package/dist/resolution/{constraints.d.ts → select/constraints.d.ts} +13 -12
  156. package/dist/resolution/select/constraints.d.ts.map +1 -0
  157. package/dist/resolution/{constraints.js → select/constraints.js} +33 -22
  158. package/dist/resolution/select/constraints.js.map +1 -0
  159. package/package.json +160 -20
  160. package/src/ambient/active-container.ts +65 -0
  161. package/src/container/binding-builders.ts +79 -44
  162. package/src/container/container.ts +357 -170
  163. package/src/{resolution → core}/binding-scope.ts +2 -2
  164. package/src/{binding.ts → core/binding.ts} +101 -39
  165. package/src/core/constraint-requirement.ts +94 -0
  166. package/src/{constructor-type.ts → core/constructor-type.ts} +1 -1
  167. package/src/{module.ts → core/module.ts} +10 -10
  168. package/src/core/registry.ts +365 -0
  169. package/src/core/tag.ts +158 -0
  170. package/src/{token.ts → core/token.ts} +2 -14
  171. package/src/{types.ts → core/types.ts} +37 -34
  172. package/src/decorators/inject.ts +39 -175
  173. package/src/decorators/injectable.ts +40 -10
  174. package/src/decorators/lifecycle-decorators.ts +22 -34
  175. package/src/{resolution → errors}/diagnostics.ts +2 -0
  176. package/src/{errors.ts → errors/errors.ts} +152 -8
  177. package/src/index.ts +43 -15
  178. package/src/injection/descriptor.ts +206 -0
  179. package/src/injection/resolve-options.ts +144 -0
  180. package/src/introspection/dependency-graph.ts +223 -63
  181. package/src/introspection/graph-adapters/cytoscape.ts +9 -3
  182. package/src/introspection/graph-adapters/dot.ts +13 -5
  183. package/src/introspection/graph-adapters/mermaid.ts +61 -0
  184. package/src/introspection/graph-adapters/reactflow.ts +11 -4
  185. package/src/introspection/inspector.ts +16 -12
  186. package/src/{resolution/lifecycle.ts → lifecycle/lifecycle-manager.ts} +68 -32
  187. package/src/lifecycle/scope-manager.ts +156 -0
  188. package/src/metadata/metadata-reader-token.ts +2 -2
  189. package/src/metadata/metadata-types.ts +9 -9
  190. package/src/metadata/symbol-metadata-reader.ts +95 -18
  191. package/src/metadata/verifying-metadata-reader.ts +41 -0
  192. package/src/resolution/{activation-need.ts → cache/activation-need.ts} +8 -8
  193. package/src/resolution/{binding-lookup-cache.ts → cache/binding-lookup-cache.ts} +63 -5
  194. package/src/resolution/cache/class-introspector.ts +251 -0
  195. package/src/resolution/{environment.ts → context.ts} +84 -145
  196. package/src/resolution/path/resolution-path.ts +167 -0
  197. package/src/resolution/plan/instantiation-plan.ts +615 -0
  198. package/src/resolution/resolver.ts +663 -313
  199. package/src/resolution/select/binding-select.ts +210 -0
  200. package/src/resolution/select/constraints.ts +130 -0
  201. package/dist/binding.d.ts.map +0 -1
  202. package/dist/binding.js.map +0 -1
  203. package/dist/constructor-type.d.ts.map +0 -1
  204. package/dist/constructor-type.js.map +0 -1
  205. package/dist/errors.d.ts.map +0 -1
  206. package/dist/errors.js.map +0 -1
  207. package/dist/module.d.ts.map +0 -1
  208. package/dist/module.js.map +0 -1
  209. package/dist/registry.d.ts.map +0 -1
  210. package/dist/registry.js.map +0 -1
  211. package/dist/resolution/activation-need.d.ts.map +0 -1
  212. package/dist/resolution/activation-need.js.map +0 -1
  213. package/dist/resolution/binding-lookup-cache.d.ts.map +0 -1
  214. package/dist/resolution/binding-lookup-cache.js.map +0 -1
  215. package/dist/resolution/binding-scope.d.ts.map +0 -1
  216. package/dist/resolution/binding-scope.js.map +0 -1
  217. package/dist/resolution/binding-select.d.ts +0 -23
  218. package/dist/resolution/binding-select.d.ts.map +0 -1
  219. package/dist/resolution/binding-select.js +0 -121
  220. package/dist/resolution/binding-select.js.map +0 -1
  221. package/dist/resolution/class-introspector.d.ts +0 -27
  222. package/dist/resolution/class-introspector.d.ts.map +0 -1
  223. package/dist/resolution/class-introspector.js +0 -60
  224. package/dist/resolution/class-introspector.js.map +0 -1
  225. package/dist/resolution/constraints.d.ts.map +0 -1
  226. package/dist/resolution/constraints.js.map +0 -1
  227. package/dist/resolution/diagnostics.d.ts.map +0 -1
  228. package/dist/resolution/diagnostics.js.map +0 -1
  229. package/dist/resolution/environment.d.ts +0 -94
  230. package/dist/resolution/environment.d.ts.map +0 -1
  231. package/dist/resolution/environment.js.map +0 -1
  232. package/dist/resolution/instantiation-plan.d.ts.map +0 -1
  233. package/dist/resolution/instantiation-plan.js +0 -183
  234. package/dist/resolution/instantiation-plan.js.map +0 -1
  235. package/dist/resolution/lifecycle.d.ts +0 -23
  236. package/dist/resolution/lifecycle.d.ts.map +0 -1
  237. package/dist/resolution/lifecycle.js.map +0 -1
  238. package/dist/resolution/resolution-path.d.ts +0 -98
  239. package/dist/resolution/resolution-path.d.ts.map +0 -1
  240. package/dist/resolution/resolution-path.js +0 -98
  241. package/dist/resolution/resolution-path.js.map +0 -1
  242. package/dist/resolution/resolve-options.d.ts.map +0 -1
  243. package/dist/resolution/resolve-options.js +0 -56
  244. package/dist/resolution/resolve-options.js.map +0 -1
  245. package/dist/resolution/scope.d.ts +0 -28
  246. package/dist/resolution/scope.d.ts.map +0 -1
  247. package/dist/resolution/scope.js +0 -88
  248. package/dist/resolution/scope.js.map +0 -1
  249. package/dist/token.d.ts.map +0 -1
  250. package/dist/token.js +0 -22
  251. package/dist/token.js.map +0 -1
  252. package/dist/types.d.ts.map +0 -1
  253. package/dist/types.js.map +0 -1
  254. package/src/registry.ts +0 -282
  255. package/src/resolution/binding-select.ts +0 -154
  256. package/src/resolution/class-introspector.ts +0 -74
  257. package/src/resolution/constraints.ts +0 -121
  258. package/src/resolution/instantiation-plan.ts +0 -292
  259. package/src/resolution/resolution-path.ts +0 -156
  260. package/src/resolution/resolve-options.ts +0 -89
  261. package/src/resolution/scope.ts +0 -105
  262. /package/dist/{resolution → core}/binding-scope.js +0 -0
  263. /package/dist/{constructor-type.js → core/constructor-type.js} +0 -0
  264. /package/dist/{types.js → core/types.js} +0 -0
  265. /package/dist/{resolution → errors}/diagnostics.js +0 -0
  266. /package/dist/resolution/{activation-need.js → cache/activation-need.js} +0 -0
@@ -1,7 +1,5 @@
1
- import type { Container } from "#/container/container";
2
- import type { BranchDepth, OwnedBranchPath, OwnedBranchStack } from "#/resolution/resolution-path";
3
- import { UNOWNED_BRANCH } from "#/resolution/resolution-path";
4
- import type { Token } from "#/token";
1
+ /** The `ResolutionContext` a factory is handed, and the callbacks the resolver answers it with. */
2
+ import type { Token } from "#/core/token";
5
3
  import type {
6
4
  BindingIdentifier,
7
5
  BindingKind,
@@ -11,85 +9,52 @@ import type {
11
9
  ResolutionFrame,
12
10
  ResolutionContext,
13
11
  ResolveOptions,
14
- } from "#/types";
12
+ } from "#/core/types";
13
+ import type { BranchDepth, OwnedBranchStack } from "#/resolution/path/resolution-path";
14
+ import { UNOWNED_BRANCH } from "#/resolution/path/resolution-path";
15
15
 
16
- // ── Active container ──────────────────────────────────────────────────────────
17
-
18
- let activeContainer: Container | undefined;
19
-
20
- /**
21
- * @since 0.3.16-canary.0
22
- */
23
- export function runWithContainer<Result>(container: Container, fn: () => Result): Result {
24
- const prev = activeContainer;
25
- activeContainer = container;
26
- try {
27
- return fn();
28
- } finally {
29
- activeContainer = prev;
30
- }
31
- }
32
-
33
- /**
34
- * @since 0.3.16-canary.0
35
- */
36
- export function getActiveContainer(): Container | undefined {
37
- return activeContainer;
38
- }
39
-
40
- // ── ResolutionContext implementation ──────────────────────────────────────────
16
+ // ── ResolutionContext implementation ─────────────────────────────────────────────────────────────────────────────────
41
17
 
42
18
  /**
43
19
  * @since 0.3.16-canary.0
44
20
  */
45
21
  export interface ResolverCallbacks {
46
- resolveFromContext<const Value>(
47
- token: Token<Value> | Constructor<Value>,
48
- resolutionPath: Array<string>,
49
- resolutionStack: Array<ResolutionFrame>,
50
- ): Value;
51
- resolve<const Value>(
22
+ resolveFromContext<Value>(token: Token<Value> | Constructor<Value>, resolutionStack: Array<ResolutionFrame>): Value;
23
+ resolve<Value>(
52
24
  token: Token<Value> | Constructor<Value>,
53
25
  options: ResolveOptions | undefined,
54
- resolutionPath: Array<string>,
55
26
  resolutionStack: Array<ResolutionFrame>,
56
27
  ): Value;
57
- resolveAsyncFromContext<const Value>(
28
+ resolveAsyncFromContext<Value>(
58
29
  token: Token<Value> | Constructor<Value>,
59
- resolutionPath: Array<string>,
60
30
  resolutionStack: Array<ResolutionFrame>,
61
31
  branchDepth: BranchDepth,
62
32
  ): Promise<Value>;
63
33
  /** Not one of the eight `Value`-naming entry points: its caller is, and casts once. */
64
34
  resolveAsyncFromCascade(token: Token<unknown> | Constructor): Promise<unknown>;
65
- resolveAsync<const Value>(
35
+ resolveAsync<Value>(
66
36
  token: Token<Value> | Constructor<Value>,
67
37
  options: ResolveOptions | undefined,
68
- resolutionPath: Array<string>,
69
38
  resolutionStack: Array<ResolutionFrame>,
70
39
  ): Promise<Value>;
71
- resolveOptional<const Value>(
40
+ resolveOptional<Value>(
72
41
  token: Token<Value> | Constructor<Value>,
73
42
  options: ResolveOptions | undefined,
74
- resolutionPath: Array<string>,
75
43
  resolutionStack: Array<ResolutionFrame>,
76
44
  ): Value | undefined;
77
- resolveOptionalAsync<const Value>(
45
+ resolveOptionalAsync<Value>(
78
46
  token: Token<Value> | Constructor<Value>,
79
47
  options: ResolveOptions | undefined,
80
- resolutionPath: Array<string>,
81
48
  resolutionStack: Array<ResolutionFrame>,
82
49
  ): Promise<Value | undefined>;
83
- resolveAll<const Value>(
50
+ resolveAll<Value>(
84
51
  token: Token<Value> | Constructor<Value>,
85
52
  options: ResolveOptions | undefined,
86
- resolutionPath: Array<string>,
87
53
  resolutionStack: Array<ResolutionFrame>,
88
54
  ): Array<Value>;
89
- resolveAllAsync<const Value>(
55
+ resolveAllAsync<Value>(
90
56
  token: Token<Value> | Constructor<Value>,
91
57
  options: ResolveOptions | undefined,
92
- resolutionPath: Array<string>,
93
58
  resolutionStack: Array<ResolutionFrame>,
94
59
  ): Promise<Array<Value>>;
95
60
  }
@@ -99,18 +64,15 @@ export interface ResolverCallbacks {
99
64
  */
100
65
  export class DefaultResolutionContext implements ResolutionContext {
101
66
  #resolver: ResolverCallbacks;
102
- #resolutionPath: Array<string>;
103
67
  #resolutionStack: Array<ResolutionFrame>;
104
68
  #currentOptions: ResolveOptions | undefined;
105
69
 
106
70
  constructor(
107
71
  resolver: ResolverCallbacks,
108
- resolutionPath: Array<string>,
109
72
  resolutionStack: Array<ResolutionFrame>,
110
73
  currentOptions: ResolveOptions | undefined,
111
74
  ) {
112
75
  this.#resolver = resolver;
113
- this.#resolutionPath = resolutionPath;
114
76
  this.#resolutionStack = resolutionStack;
115
77
  this.#currentOptions = currentOptions;
116
78
  }
@@ -119,25 +81,21 @@ export class DefaultResolutionContext implements ResolutionContext {
119
81
 
120
82
  get graph(): ConstraintContext {
121
83
  if (this.#graph === undefined) {
122
- this.#graph = new DefaultConstraintContext(this.#resolutionPath, this.#resolutionStack, this.#currentOptions);
84
+ this.#graph = new DefaultConstraintContext(this.#resolutionStack, this.#currentOptions);
123
85
  }
124
86
  return this.#graph;
125
87
  }
126
88
 
127
89
  reset(
128
90
  resolver: ResolverCallbacks,
129
- resolutionPath: Array<string>,
130
91
  resolutionStack: Array<ResolutionFrame>,
131
92
  currentOptions: ResolveOptions | undefined,
132
93
  ): void {
133
94
  // Compared before storing: a pooled context lives long enough to be in old space, so storing a
134
- // pointer costs a write barrier, and a sync resolve hands every depth the same two arrays.
95
+ // pointer costs a write barrier, and a sync resolve hands every depth the same stack.
135
96
  if (this.#resolver !== resolver) {
136
97
  this.#resolver = resolver;
137
98
  }
138
- if (this.#resolutionPath !== resolutionPath) {
139
- this.#resolutionPath = resolutionPath;
140
- }
141
99
  if (this.#resolutionStack !== resolutionStack) {
142
100
  this.#resolutionStack = resolutionStack;
143
101
  }
@@ -147,221 +105,202 @@ export class DefaultResolutionContext implements ResolutionContext {
147
105
  }
148
106
  }
149
107
 
150
- resolve<const Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Value {
108
+ resolve<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Value {
151
109
  if (options === undefined) {
152
- return this.#resolver.resolveFromContext(token, this.#resolutionPath, this.#resolutionStack);
110
+ return this.#resolver.resolveFromContext(token, this.#resolutionStack);
153
111
  }
154
- return this.#resolver.resolve(token, options, this.#resolutionPath, this.#resolutionStack);
112
+ return this.#resolver.resolve(token, options, this.#resolutionStack);
155
113
  }
156
114
 
157
- resolveAsync<const Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Promise<Value> {
115
+ resolveAsync<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Promise<Value> {
158
116
  if (options === undefined) {
159
117
  // UNOWNED_BRANCH: this frame's array is a sync stack it will pop, so the lane must copy it.
160
- return this.#resolver.resolveAsyncFromContext(token, this.#resolutionPath, this.#resolutionStack, UNOWNED_BRANCH);
118
+ return this.#resolver.resolveAsyncFromContext(token, this.#resolutionStack, UNOWNED_BRANCH);
161
119
  }
162
- return this.#resolver.resolveAsync(token, options, this.#resolutionPath, this.#resolutionStack);
120
+ return this.#resolver.resolveAsync(token, options, this.#resolutionStack);
163
121
  }
164
122
 
165
- resolveOptional<const Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Value | undefined {
166
- return this.#resolver.resolveOptional(token, options, this.#resolutionPath, this.#resolutionStack);
123
+ resolveOptional<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Value | undefined {
124
+ return this.#resolver.resolveOptional(token, options, this.#resolutionStack);
167
125
  }
168
126
 
169
- resolveOptionalAsync<const Value>(
127
+ resolveOptionalAsync<Value>(
170
128
  token: Token<Value> | Constructor<Value>,
171
129
  options?: ResolveOptions,
172
130
  ): Promise<Value | undefined> {
173
- return this.#resolver.resolveOptionalAsync(token, options, this.#resolutionPath, this.#resolutionStack);
131
+ return this.#resolver.resolveOptionalAsync(token, options, this.#resolutionStack);
174
132
  }
175
133
 
176
- resolveAll<const Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Array<Value> {
177
- return this.#resolver.resolveAll(token, options, this.#resolutionPath, this.#resolutionStack);
134
+ resolveAll<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Array<Value> {
135
+ return this.#resolver.resolveAll(token, options, this.#resolutionStack);
178
136
  }
179
137
 
180
- resolveAllAsync<const Value>(
181
- token: Token<Value> | Constructor<Value>,
182
- options?: ResolveOptions,
183
- ): Promise<Array<Value>> {
184
- return this.#resolver.resolveAllAsync(token, options, this.#resolutionPath, this.#resolutionStack);
138
+ resolveAllAsync<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Promise<Array<Value>> {
139
+ return this.#resolver.resolveAllAsync(token, options, this.#resolutionStack);
185
140
  }
186
141
  }
187
142
 
188
143
  /**
189
- * One async level's resolution context, which is also its branch of the resolution path.
144
+ * One async level's resolution context, which is also its branch of the resolution stack.
190
145
  *
191
146
  * @remarks Separate from {@link DefaultResolutionContext} so the sync lane's pooled context keeps
192
- * reading its arrays as plain fields: only an async branch has a prefix to take. See
193
- * `ARCHITECTURE.md` — an async level owns its branch of the path.
147
+ * reading its stack as a plain field: only an async branch has a prefix to take, because an async
148
+ * level owns its branch of the stack.
194
149
  *
195
150
  * @since 0.5.0-canary.9
196
151
  */
197
152
  export class AsyncLevelContext implements ResolutionContext {
198
153
  readonly #resolver: ResolverCallbacks;
199
- readonly #resolutionPath: OwnedBranchPath;
200
154
  readonly #resolutionStack: OwnedBranchStack;
201
155
  readonly #currentOptions: ResolveOptions | undefined;
202
156
  readonly #branchDepth: BranchDepth;
203
157
 
204
158
  /**
205
- * @param resolutionPath - this level's own branch; the depth is read off it rather than passed,
159
+ * @param resolver - the engine callbacks every request on this level goes through
160
+ * @param resolutionStack - this level's own branch; the depth is read off it rather than passed,
206
161
  * so the two cannot disagree about where this level sits
162
+ * @param currentOptions - the options of the request that opened this level, if any
207
163
  */
208
164
  constructor(
209
165
  resolver: ResolverCallbacks,
210
- resolutionPath: OwnedBranchPath,
211
166
  resolutionStack: OwnedBranchStack,
212
167
  currentOptions: ResolveOptions | undefined,
213
168
  ) {
214
169
  this.#resolver = resolver;
215
- this.#resolutionPath = resolutionPath;
216
170
  this.#resolutionStack = resolutionStack;
217
171
  this.#currentOptions = currentOptions;
218
- this.#branchDepth = resolutionPath.length as BranchDepth;
172
+ this.#branchDepth = resolutionStack.length as BranchDepth;
219
173
  }
220
174
 
221
175
  #graph: ConstraintContext | undefined;
222
- #exactPathCache: Array<string> | undefined;
223
176
  #exactStackCache: Array<ResolutionFrame> | undefined;
224
177
 
225
178
  get graph(): ConstraintContext {
226
179
  if (this.#graph === undefined) {
227
- this.#graph = new DefaultConstraintContext(this.#exactPath(), this.#exactStack(), this.#currentOptions);
180
+ this.#graph = new DefaultConstraintContext(this.#exactStack(), this.#currentOptions);
228
181
  }
229
182
  return this.#graph;
230
183
  }
231
184
 
232
- // The path is append-only and a descendant may already have grown it past this level, so every
185
+ // The stack is append-only and a descendant may already have grown it past this level, so every
233
186
  // caller but the async lane is handed this branch's prefix. It is fixed for the level's lifetime.
234
- #exactPath(): Array<string> {
235
- return (this.#exactPathCache ??= this.#resolutionPath.slice(0, this.#branchDepth));
236
- }
237
-
238
187
  #exactStack(): Array<ResolutionFrame> {
239
188
  return (this.#exactStackCache ??= this.#resolutionStack.slice(0, this.#branchDepth));
240
189
  }
241
190
 
242
- resolve<const Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Value {
191
+ resolve<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Value {
243
192
  if (options === undefined) {
244
- return this.#resolver.resolveFromContext(token, this.#exactPath(), this.#exactStack());
193
+ return this.#resolver.resolveFromContext(token, this.#exactStack());
245
194
  }
246
- return this.#resolver.resolve(token, options, this.#exactPath(), this.#exactStack());
195
+ return this.#resolver.resolve(token, options, this.#exactStack());
247
196
  }
248
197
 
249
- resolveAsync<const Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Promise<Value> {
198
+ resolveAsync<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Promise<Value> {
250
199
  if (options === undefined) {
251
200
  // The hot lane: the resolver reads this branch by depth, so nothing is materialized.
252
- return this.#resolver.resolveAsyncFromContext(
253
- token,
254
- this.#resolutionPath,
255
- this.#resolutionStack,
256
- this.#branchDepth,
257
- );
201
+ return this.#resolver.resolveAsyncFromContext(token, this.#resolutionStack, this.#branchDepth);
258
202
  }
259
- return this.#resolver.resolveAsync(token, options, this.#exactPath(), this.#exactStack());
203
+ return this.#resolver.resolveAsync(token, options, this.#exactStack());
260
204
  }
261
205
 
262
- resolveOptional<const Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Value | undefined {
263
- return this.#resolver.resolveOptional(token, options, this.#exactPath(), this.#exactStack());
206
+ resolveOptional<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Value | undefined {
207
+ return this.#resolver.resolveOptional(token, options, this.#exactStack());
264
208
  }
265
209
 
266
- resolveOptionalAsync<const Value>(
210
+ resolveOptionalAsync<Value>(
267
211
  token: Token<Value> | Constructor<Value>,
268
212
  options?: ResolveOptions,
269
213
  ): Promise<Value | undefined> {
270
- return this.#resolver.resolveOptionalAsync(token, options, this.#exactPath(), this.#exactStack());
214
+ return this.#resolver.resolveOptionalAsync(token, options, this.#exactStack());
271
215
  }
272
216
 
273
- resolveAll<const Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Array<Value> {
274
- return this.#resolver.resolveAll(token, options, this.#exactPath(), this.#exactStack());
217
+ resolveAll<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Array<Value> {
218
+ return this.#resolver.resolveAll(token, options, this.#exactStack());
275
219
  }
276
220
 
277
- resolveAllAsync<const Value>(
278
- token: Token<Value> | Constructor<Value>,
279
- options?: ResolveOptions,
280
- ): Promise<Array<Value>> {
281
- return this.#resolver.resolveAllAsync(token, options, this.#exactPath(), this.#exactStack());
221
+ resolveAllAsync<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Promise<Array<Value>> {
222
+ return this.#resolver.resolveAllAsync(token, options, this.#exactStack());
282
223
  }
283
224
  }
284
225
 
285
226
  /**
286
227
  * The one context every level of an open synchronous factory cascade shares.
287
228
  *
288
- * @remarks It carries no per-level state at all: while the cascade is open, the resolver's arrays
289
- * *are* this level's ancestor chain, so nothing has to be allocated per level. See
290
- * `ARCHITECTURE.md` — the cascade lane.
229
+ * @remarks It carries no per-level state at all: while the cascade is open, the resolver's stack
230
+ * *is* this level's ancestor chain, so nothing has to be allocated per level.
291
231
  *
292
232
  * @since 0.5.0-canary.9
293
233
  */
294
234
  export class AsyncCascadeContext implements ResolutionContext {
295
235
  readonly #resolver: ResolverCallbacks;
296
- readonly #cascadePath: Array<string>;
297
236
  readonly #cascadeStack: Array<ResolutionFrame>;
298
237
 
299
- constructor(resolver: ResolverCallbacks, cascadePath: Array<string>, cascadeStack: Array<ResolutionFrame>) {
238
+ constructor(resolver: ResolverCallbacks, cascadeStack: Array<ResolutionFrame>) {
300
239
  this.#resolver = resolver;
301
- this.#cascadePath = cascadePath;
302
240
  this.#cascadeStack = cascadeStack;
303
241
  }
304
242
 
305
243
  get graph(): ConstraintContext {
306
244
  // Not memoized: this context outlives every level, so a cached graph would describe whichever
307
- // level asked first. The cascade arrays are only this level's ancestors while it is open.
308
- return new DefaultConstraintContext(this.#cascadePath, this.#cascadeStack, undefined);
245
+ // level asked first. The cascade stack is only this level's ancestors while it is open.
246
+ return new DefaultConstraintContext(this.#cascadeStack, undefined);
309
247
  }
310
248
 
311
- resolve<const Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Value {
249
+ resolve<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Value {
312
250
  if (options === undefined) {
313
- return this.#resolver.resolveFromContext(token, this.#cascadePath, this.#cascadeStack);
251
+ return this.#resolver.resolveFromContext(token, this.#cascadeStack);
314
252
  }
315
- return this.#resolver.resolve(token, options, this.#cascadePath, this.#cascadeStack);
253
+ return this.#resolver.resolve(token, options, this.#cascadeStack);
316
254
  }
317
255
 
318
- resolveAsync<const Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Promise<Value> {
256
+ resolveAsync<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Promise<Value> {
319
257
  if (options === undefined) {
320
258
  return this.#resolver.resolveAsyncFromCascade(token) as Promise<Value>;
321
259
  }
322
- return this.#resolver.resolveAsync(token, options, [...this.#cascadePath], [...this.#cascadeStack]);
260
+ return this.#resolver.resolveAsync(token, options, [...this.#cascadeStack]);
323
261
  }
324
262
 
325
- resolveOptional<const Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Value | undefined {
326
- return this.#resolver.resolveOptional(token, options, this.#cascadePath, this.#cascadeStack);
263
+ resolveOptional<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Value | undefined {
264
+ return this.#resolver.resolveOptional(token, options, this.#cascadeStack);
327
265
  }
328
266
 
329
- resolveOptionalAsync<const Value>(
267
+ resolveOptionalAsync<Value>(
330
268
  token: Token<Value> | Constructor<Value>,
331
269
  options?: ResolveOptions,
332
270
  ): Promise<Value | undefined> {
333
- return this.#resolver.resolveOptionalAsync(token, options, [...this.#cascadePath], [...this.#cascadeStack]);
271
+ return this.#resolver.resolveOptionalAsync(token, options, [...this.#cascadeStack]);
334
272
  }
335
273
 
336
- resolveAll<const Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Array<Value> {
337
- return this.#resolver.resolveAll(token, options, this.#cascadePath, this.#cascadeStack);
274
+ resolveAll<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Array<Value> {
275
+ return this.#resolver.resolveAll(token, options, this.#cascadeStack);
338
276
  }
339
277
 
340
- resolveAllAsync<const Value>(
341
- token: Token<Value> | Constructor<Value>,
342
- options?: ResolveOptions,
343
- ): Promise<Array<Value>> {
344
- return this.#resolver.resolveAllAsync(token, options, [...this.#cascadePath], [...this.#cascadeStack]);
278
+ resolveAllAsync<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Promise<Array<Value>> {
279
+ return this.#resolver.resolveAllAsync(token, options, [...this.#cascadeStack]);
345
280
  }
346
281
  }
347
282
 
348
283
  class DefaultConstraintContext implements ConstraintContext {
349
- readonly resolutionPath: ReadonlyArray<string>;
350
284
  readonly resolutionStack: ReadonlyArray<ResolutionFrame>;
351
285
  readonly parent: ResolutionFrame | undefined;
352
- readonly currentResolveOptions: ResolveOptions | undefined;
286
+ readonly currentResolveOptions: Readonly<ResolveOptions> | undefined;
353
287
 
354
- constructor(
355
- resolutionPath: ReadonlyArray<string>,
356
- resolutionStack: ReadonlyArray<ResolutionFrame>,
357
- currentResolveOptions: ResolveOptions | undefined,
358
- ) {
359
- this.resolutionPath = resolutionPath;
288
+ constructor(resolutionStack: ReadonlyArray<ResolutionFrame>, currentResolveOptions: ResolveOptions | undefined) {
360
289
  this.resolutionStack = resolutionStack;
361
290
  this.parent = resolutionStack.at(-1);
362
291
  this.currentResolveOptions = currentResolveOptions;
363
292
  }
364
293
 
294
+ // Derived per read, never cached: the stack this context aliases may be live, and the names must
295
+ // report it as it stands — exactly as the dedicated name array did.
296
+ get resolutionPath(): ReadonlyArray<string> {
297
+ const names = new Array<string>(this.resolutionStack.length);
298
+ for (let index = 0; index < this.resolutionStack.length; index += 1) {
299
+ names[index] = this.resolutionStack[index]!.tokenName;
300
+ }
301
+ return names;
302
+ }
303
+
365
304
  #ancestors: ReadonlyArray<ResolutionFrame> | undefined;
366
305
 
367
306
  get ancestors(): ReadonlyArray<ResolutionFrame> {
@@ -380,7 +319,7 @@ export function buildResolutionFrame(
380
319
  scope: BindingScope,
381
320
  bindingId: BindingIdentifier,
382
321
  kind: BindingKind,
383
- slot: { name: string | undefined; tags: ReadonlyArray<readonly [string, unknown]> },
322
+ slot: ResolutionFrame["slot"],
384
323
  ): ResolutionFrame {
385
324
  return { tokenName, scope, bindingId, kind, slot };
386
325
  }
@@ -0,0 +1,167 @@
1
+ import type { BindingIdentifier, ResolutionFrame } from "#/core/types";
2
+ /** Cycle-detection bookkeeping carried on the resolution stack itself. */
3
+ import { CircularDependencyError } from "#/errors/errors";
4
+
5
+ const RESOLUTION_SET_KEY: unique symbol = Symbol("di:resolution-set");
6
+ /**
7
+ * Where the cycle check switches from a linear frame scan to an attached Set.
8
+ *
9
+ * @remarks Measured rather than guessed: below this depth the linear scan wins, above it the Set does.
10
+ *
11
+ * @since 0.5.0-canary.7
12
+ */
13
+ export const RESOLUTION_SET_THRESHOLD = 32;
14
+ type ResolutionStackWithSet = Array<ResolutionFrame> & { [RESOLUTION_SET_KEY]?: Set<BindingIdentifier> | undefined };
15
+
16
+ /**
17
+ * The token names along a stack plus the frame that closed the cycle — built only to throw.
18
+ *
19
+ * @since 0.6.0
20
+ */
21
+ export function cycleNamesOf(resolutionStack: ReadonlyArray<ResolutionFrame>, closingName: string): Array<string> {
22
+ const names = new Array<string>(resolutionStack.length + 1);
23
+ for (let index = 0; index < resolutionStack.length; index += 1) {
24
+ names[index] = resolutionStack[index]!.tokenName;
25
+ }
26
+ names[resolutionStack.length] = closingName;
27
+ return names;
28
+ }
29
+
30
+ /**
31
+ * Marks a level as in-flight on this stack, throwing if its binding is already an ancestor.
32
+ *
33
+ * @remarks The check keys on binding identity — two distinct tokens may share a display name — and
34
+ * the names an error prints are derived from the frames at the throw site. Unmark by popping the
35
+ * stack plus `set?.delete(frame.bindingId)`. Sync only — the async lane never removes an entry, so
36
+ * it extends a branch instead; see {@link extendResolutionBranch}.
37
+ *
38
+ * @returns the membership set once the stack is deep enough to carry one, else `undefined`.
39
+ *
40
+ * @since 0.5.0-canary.7
41
+ */
42
+ export function enterResolutionPath(
43
+ resolutionStack: Array<ResolutionFrame>,
44
+ frame: ResolutionFrame,
45
+ ): Set<BindingIdentifier> | undefined {
46
+ const stackWithSet = resolutionStack as ResolutionStackWithSet;
47
+ let resolutionSet = stackWithSet[RESOLUTION_SET_KEY];
48
+ // A live set mirrors the stack exactly, so a size that disagrees means it is holding ids of
49
+ // frames that unwound: the ones already on the stack when it attached were handed no set to
50
+ // delete from. Dropped rather than repaired, because the next deep frame rebuilds it.
51
+ if (resolutionSet !== undefined && resolutionSet.size !== resolutionStack.length) {
52
+ resolutionSet = undefined;
53
+ stackWithSet[RESOLUTION_SET_KEY] = undefined;
54
+ }
55
+ if (resolutionSet === undefined && resolutionStack.length >= RESOLUTION_SET_THRESHOLD) {
56
+ resolutionSet = new Set<BindingIdentifier>();
57
+ for (let index = 0; index < resolutionStack.length; index += 1) {
58
+ resolutionSet.add(resolutionStack[index]!.bindingId);
59
+ }
60
+ stackWithSet[RESOLUTION_SET_KEY] = resolutionSet;
61
+ }
62
+ if (resolutionSet === undefined) {
63
+ for (let index = 0; index < resolutionStack.length; index += 1) {
64
+ if (resolutionStack[index]!.bindingId === frame.bindingId) {
65
+ throw new CircularDependencyError(cycleNamesOf(resolutionStack, frame.tokenName));
66
+ }
67
+ }
68
+ } else if (resolutionSet.has(frame.bindingId)) {
69
+ throw new CircularDependencyError(cycleNamesOf(resolutionStack, frame.tokenName));
70
+ }
71
+ resolutionStack.push(frame);
72
+ resolutionSet?.add(frame.bindingId);
73
+ return resolutionSet;
74
+ }
75
+
76
+ declare const BRANCH_BRAND: unique symbol;
77
+
78
+ /**
79
+ * A resolution stack one async branch owns, so appending to it cannot disturb another branch.
80
+ *
81
+ * @remarks Only {@link extendResolutionBranch} mints one. That is what makes "may this lane append
82
+ * to this array" a question the compiler answers instead of a rule in a doc — a sync frame's stack,
83
+ * which that frame will pop, is a plain `Array<ResolutionFrame>` and cannot reach a level that owns
84
+ * its own.
85
+ *
86
+ * @since 0.5.0-canary.9
87
+ */
88
+ export type OwnedBranchStack = Array<ResolutionFrame> & { readonly [BRANCH_BRAND]: true };
89
+
90
+ declare const BRANCH_DEPTH_BRAND: unique symbol;
91
+
92
+ /**
93
+ * How many leading entries of a stack belong to one async branch.
94
+ *
95
+ * @remarks Branded so a bare number cannot be passed: a depth from anywhere but this branch silently
96
+ * re-parents a level.
97
+ *
98
+ * @since 0.5.0-canary.9
99
+ */
100
+ export type OwnedBranchDepth = number & { readonly [BRANCH_DEPTH_BRAND]: true };
101
+
102
+ /**
103
+ * A stack no async branch owns yet, so its first extension must copy rather than append.
104
+ *
105
+ * @since 0.5.0-canary.9
106
+ */
107
+ export const UNOWNED_BRANCH = -1;
108
+
109
+ /**
110
+ * How far into a stack one extension may reach: this branch's own depth, or nobody's.
111
+ *
112
+ * @remarks A union rather than a sentinel hidden inside the branded number, so the two cases are
113
+ * visible at every signature that takes one and `=== UNOWNED_BRANCH` narrows to the owned case.
114
+ *
115
+ * @since 0.5.0-canary.9
116
+ */
117
+ export type BranchDepth = OwnedBranchDepth | typeof UNOWNED_BRANCH;
118
+
119
+ /**
120
+ * The depth a chain's first level extends from, over an array its caller just minted.
121
+ *
122
+ * @since 0.5.0-canary.9
123
+ */
124
+ export const ROOT_BRANCH = 0 as OwnedBranchDepth;
125
+
126
+ /**
127
+ * A branch's own depth: the length its stack had when this level took it.
128
+ *
129
+ * @since 0.5.0-canary.9
130
+ */
131
+ export function branchDepthOf(branch: OwnedBranchStack): OwnedBranchDepth {
132
+ return branch.length as OwnedBranchDepth;
133
+ }
134
+
135
+ /**
136
+ * Extends one branch of an append-only stack, throwing if the binding is already an ancestor.
137
+ *
138
+ * @remarks The check compares binding ids on the frames; the names an error prints are derived at
139
+ * the throw site. Appends in place while this branch still owns the next slot, and copies its own
140
+ * prefix once a sibling has claimed it. Nothing is ever removed, so no async level has to observe
141
+ * its own settlement to unwind.
142
+ *
143
+ * @since 0.5.0-canary.9
144
+ */
145
+ export function extendResolutionBranch(
146
+ resolutionStack: Array<ResolutionFrame>,
147
+ branchDepth: BranchDepth,
148
+ frame: ResolutionFrame,
149
+ ): OwnedBranchStack {
150
+ const depth = branchDepth === UNOWNED_BRANCH ? resolutionStack.length : branchDepth;
151
+ for (let index = 0; index < depth; index += 1) {
152
+ if (resolutionStack[index]!.bindingId === frame.bindingId) {
153
+ throw new CircularDependencyError(cycleNamesOf(resolutionStack.slice(0, depth), frame.tokenName));
154
+ }
155
+ }
156
+ // An unowned array belongs to a sync frame that will pop it, or carries a membership Set this
157
+ // lane cannot keep true; copying is what makes the branch's own appends safe.
158
+ if (branchDepth === resolutionStack.length) {
159
+ // The sole mint: appending in place needs a depth that came from a branch already owned, or
160
+ // ROOT_BRANCH over an array its caller minted for this chain alone.
161
+ resolutionStack.push(frame);
162
+ return resolutionStack as OwnedBranchStack;
163
+ }
164
+ const branch = resolutionStack.slice(0, depth);
165
+ branch.push(frame);
166
+ return branch as OwnedBranchStack;
167
+ }