@decohere/crucible 0.0.1-dev.119 → 0.0.1-dev.12

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 (47) hide show
  1. package/commonjs/src/const.js +7 -5
  2. package/commonjs/src/decorators/index.js +2 -0
  3. package/commonjs/src/decorators/inject.decorator.js +15 -28
  4. package/commonjs/src/decorators/injectable.dectorator.js +2 -0
  5. package/commonjs/src/index.js +2 -0
  6. package/commonjs/src/models/registration-key.model.js +31 -0
  7. package/commonjs/src/models/service-registration.model.js +6 -1
  8. package/commonjs/src/services/crucible-builder.js +68 -0
  9. package/commonjs/src/services/crucible.js +165 -0
  10. package/commonjs/src/services/index.js +4 -2
  11. package/commonjs/src/services/service-registry.js +22 -29
  12. package/commonjs/src/types/errors.js +2 -0
  13. package/commonjs/src/types/index.js +2 -1
  14. package/commonjs/src/types/types.js +2 -0
  15. package/es6/src/const.js +5 -3
  16. package/es6/src/decorators/index.js +2 -0
  17. package/es6/src/decorators/inject.decorator.js +14 -27
  18. package/es6/src/decorators/injectable.dectorator.js +2 -0
  19. package/es6/src/index.js +2 -0
  20. package/es6/src/models/registration-key.model.js +21 -0
  21. package/es6/src/models/service-registration.model.js +6 -1
  22. package/es6/src/services/crucible-builder.js +58 -0
  23. package/es6/src/services/crucible.js +155 -0
  24. package/es6/src/services/index.js +4 -2
  25. package/es6/src/services/service-registry.js +23 -34
  26. package/es6/src/types/errors.js +2 -0
  27. package/es6/src/types/index.js +2 -1
  28. package/es6/src/types/types.js +2 -0
  29. package/package.json +3 -2
  30. package/types/src/const.d.ts +1 -1
  31. package/types/src/decorators/inject.decorator.d.ts +2 -17
  32. package/types/src/models/registration-key.model.d.ts +7 -0
  33. package/types/src/models/service-registration.model.d.ts +4 -5
  34. package/types/src/services/{service-container-builder.d.ts → crucible-builder.d.ts} +5 -9
  35. package/types/src/services/{container.d.ts → crucible.d.ts} +5 -16
  36. package/types/src/services/index.d.ts +2 -2
  37. package/types/src/services/service-registry.d.ts +6 -12
  38. package/types/src/types/index.d.ts +0 -1
  39. package/types/src/types/types.d.ts +1 -1
  40. package/README.md +0 -354
  41. package/commonjs/src/models/service-key.js +0 -20
  42. package/commonjs/src/services/container.js +0 -207
  43. package/commonjs/src/services/service-container-builder.js +0 -67
  44. package/es6/src/models/service-key.js +0 -10
  45. package/es6/src/services/container.js +0 -197
  46. package/es6/src/services/service-container-builder.js +0 -57
  47. package/types/src/models/service-key.d.ts +0 -9
@@ -1,197 +0,0 @@
1
- import { getMeta, isArrayType, isConstructable, ReflectionMeta } from "@decohere/core/reflection";
2
- import { ContainerMeta, LifetimeScopeToken, Scopes } from "../const.js";
3
- import { describeKey } from "../models/service-key.js";
4
- import { DuplicateScopeError, ResolveError } from "../types/errors.js";
5
- import { ServiceRegistry } from "./service-registry.js";
6
- function describePath(path, last) {
7
- const keys = path.map((registration)=>registration.key);
8
- return [
9
- ...keys,
10
- ...last == null ? [] : [
11
- last
12
- ]
13
- ].map(describeKey).join(" → ");
14
- }
15
- export class Container {
16
- key;
17
- instances = new Map();
18
- serviceRegistry;
19
- parent;
20
- rootScope;
21
- scopes = new Map();
22
- isDisposed = false;
23
- constructor(options){
24
- this.key = options?.key;
25
- this.serviceRegistry = new ServiceRegistry(options?.serviceRegistry);
26
- this.registerLifetimeScope();
27
- this.parent = options?.parent;
28
- this.rootScope = options?.parent?.rootScope ?? this;
29
- }
30
- resolveMany(resolveRequest) {
31
- return this.resolveAll(resolveRequest, []);
32
- }
33
- /** Throws {@link ResolveError} when nothing is registered for the key. */ resolve(resolveRequest) {
34
- return this.resolveOne(resolveRequest, [], false);
35
- }
36
- resolveAll(key, path) {
37
- const registrations = this.serviceRegistry.getRegistration(key) ?? [];
38
- return registrations.map((registration)=>this.resolveRegistration(registration, path));
39
- }
40
- resolveOne(key, path, optional) {
41
- const registrations = this.serviceRegistry.getRegistration(key);
42
- if (registrations == null || registrations.length === 0) {
43
- if (optional) return undefined;
44
- const requiredBy = path.length === 0 ? "" : ` (required by ${describePath(path, key)})`;
45
- throw new ResolveError(`Nothing is registered for ${describeKey(key)}${requiredBy}. Register it, or mark the parameter @optional().`);
46
- }
47
- if (registrations.length > 1) {
48
- throw new ResolveError(`There are multiple services registered with key ${describeKey(key)}. Use resolveMany to resolve all implementations.`);
49
- }
50
- return this.resolveRegistration(registrations[0], path);
51
- }
52
- beginScope(name) {
53
- if (this.isDisposed) {
54
- throw new Error("This container has been disposed");
55
- }
56
- if (name != null && this.scopes.has(name)) {
57
- throw new DuplicateScopeError(`Cannot create duplicate scope. Scope with ${name} is already created and has not been disposed.`);
58
- }
59
- const scope = new Container({
60
- key: name,
61
- serviceRegistry: this.serviceRegistry,
62
- parent: this
63
- });
64
- if (name != null) {
65
- this.scopes.set(name, scope);
66
- }
67
- return scope;
68
- }
69
- [Symbol.dispose]() {
70
- if (this.isDisposed) {
71
- return;
72
- }
73
- this.disposeSync();
74
- }
75
- async [Symbol.asyncDispose]() {
76
- if (this.isDisposed) {
77
- return;
78
- }
79
- await this.disposeAsync();
80
- }
81
- disposeSync() {
82
- if (this.isDisposed) {
83
- return;
84
- }
85
- this.isDisposed = true;
86
- this.parent?.releaseScope(this.key);
87
- this.scopes.clear();
88
- // Reverse creation order: dependents are cleaned up before what they depend on.
89
- for (const inst of [
90
- ...this.instances.values()
91
- ].reverse()){
92
- if (inst === this) {
93
- continue;
94
- }
95
- inst[Symbol.dispose]?.();
96
- }
97
- }
98
- async disposeAsync() {
99
- if (this.isDisposed) {
100
- return;
101
- }
102
- this.isDisposed = true;
103
- this.parent?.releaseScope(this.key);
104
- this.scopes.clear();
105
- // Reverse creation order: dependents are cleaned up before what they depend on.
106
- for (const inst of [
107
- ...this.instances.values()
108
- ].reverse()){
109
- if (inst === this) {
110
- continue;
111
- }
112
- if (Symbol.asyncDispose in inst) {
113
- await inst[Symbol.asyncDispose]();
114
- } else {
115
- inst[Symbol.dispose]?.();
116
- }
117
- }
118
- }
119
- resolveRegistration(registration, path) {
120
- this.assertNoCycle(registration, path);
121
- this.assertNotCaptive(registration, path);
122
- const { scope } = registration;
123
- switch(scope){
124
- case Scopes.transient:
125
- return this.createInstance(registration, path);
126
- case Scopes.singleton:
127
- return this.rootScope.getOrCreateInstance(registration, path);
128
- case Scopes.request:
129
- return this.getOrCreateInstance(registration, path);
130
- }
131
- }
132
- assertNoCycle(registration, path) {
133
- if (!path.includes(registration)) return;
134
- throw new ResolveError(`Circular dependency: ${describePath(path, registration.key)}. Break the cycle, e.g. by resolving one side lazily from the LifetimeScope.`);
135
- }
136
- /**
137
- * A singleton lives for the whole process, so a request-scoped service it depends on (directly
138
- * or through transients) would be created once and then shared by every request, e.g. one
139
- * QueryRunner for the life of the process. The container's own lifetime scope is exempt: a
140
- * singleton asking for it gets the root, which is the scope it lives in.
141
- */ assertNotCaptive(registration, path) {
142
- if (registration.scope !== Scopes.request || registration.key === LifetimeScopeToken) return;
143
- const singleton = path.find((owner)=>owner.scope === Scopes.singleton);
144
- if (singleton == null) return;
145
- throw new ResolveError(`Singleton ${describeKey(singleton.key)} depends on request-scoped ${describeKey(registration.key)} (${describePath(path, registration.key)}). A singleton would keep the first request's instance forever: make ${describeKey(singleton.key)} request-scoped, or resolve ${describeKey(registration.key)} per request from the LifetimeScope.`);
146
- }
147
- getOrCreateInstance(registration, path) {
148
- if (this.isDisposed) {
149
- throw new Error("This container has been disposed");
150
- }
151
- const { type, isInstance } = registration;
152
- // One cache entry per registration: an instance registration is its own entry.
153
- const cacheKey = type;
154
- const existingInstance = this.instances.get(cacheKey);
155
- if (existingInstance != null) {
156
- return existingInstance;
157
- }
158
- const instance = isInstance ? type : this.createInstance(registration, path);
159
- this.instances.set(cacheKey, instance);
160
- return instance;
161
- }
162
- createInstance(registration, path) {
163
- const type = registration.type;
164
- const canConstruct = isConstructable(type);
165
- const deps = this.resolveDependencies(type, [
166
- ...path,
167
- registration
168
- ]);
169
- const inst = canConstruct ? new type(...deps) : type();
170
- return inst;
171
- }
172
- resolveDependencies(type, path) {
173
- const params = getMeta(ReflectionMeta.paramtypes, type) ?? [];
174
- const injectTokens = getMeta(ContainerMeta.injecttoken, type) ?? new Map();
175
- return params.map((param, index)=>{
176
- const token = injectTokens.get(index);
177
- const key = token?.key ?? param;
178
- if (key == null || key === Object || token?.key == null && isArrayType(param)) {
179
- if (token?.optional) return undefined;
180
- throw new ResolveError(`Cannot tell what to inject for constructor parameter ${index} of ${describePath(path)}: its type is not a class at run time (an interface, an array, or a type-only import). Add @inject(token) to the parameter.`);
181
- }
182
- return isArrayType(param) ? this.resolveAll(key, path) : this.resolveOne(key, path, token?.optional === true);
183
- });
184
- }
185
- releaseScope(name) {
186
- if (name == null) {
187
- return;
188
- }
189
- this.scopes.delete(name);
190
- }
191
- registerLifetimeScope() {
192
- this.serviceRegistry.replaceType(Scopes.request, {
193
- typeOrKey: LifetimeScopeToken,
194
- typeOrInstance: this
195
- });
196
- }
197
- }
@@ -1,57 +0,0 @@
1
- import { Scopes } from "../const.js";
2
- import { Container } from "./container.js";
3
- import { ServiceRegistry } from "./service-registry.js";
4
- export class ServiceContainerBuilder {
5
- serviceRegistry;
6
- constructor(options){
7
- this.serviceRegistry = options?.serviceRegistry ?? new ServiceRegistry();
8
- }
9
- get override() {
10
- return this.createRegistrar(true);
11
- }
12
- registerType(type, scope = Scopes.transient) {
13
- return this.register(false)(scope)(type, type);
14
- }
15
- registerTypes(scopeOrType, ...types) {
16
- return this.registerMany(false, scopeOrType, types);
17
- }
18
- registerTransient(typeOrKey, type) {
19
- return this.register(false)(Scopes.transient)(typeOrKey, type);
20
- }
21
- registerSingleton(typeOrKey, typeOrInstance) {
22
- return this.register(false)(Scopes.singleton)(typeOrKey, typeOrInstance);
23
- }
24
- registerRequestScoped(typeOrKey, type) {
25
- return this.register(false)(Scopes.request)(typeOrKey, type);
26
- }
27
- build() {
28
- return new Container({
29
- serviceRegistry: this.serviceRegistry
30
- });
31
- }
32
- register = (override)=>(scope)=>(typeOrKey, typeOrInstance)=>this.serviceRegistry.registerType(scope, {
33
- typeOrKey,
34
- typeOrInstance,
35
- override
36
- });
37
- registerMany(override, scopeOrType, types) {
38
- const bind = this.register(override);
39
- const isScope = typeof scopeOrType !== "function";
40
- const scope = isScope ? scopeOrType : Scopes.transient;
41
- const allTypes = isScope ? types : [
42
- scopeOrType,
43
- ...types
44
- ];
45
- return allTypes.map((type)=>bind(scope)(type, type));
46
- }
47
- createRegistrar(override) {
48
- const bind = this.register(override);
49
- return {
50
- registerType: (type, scope = Scopes.transient)=>bind(scope)(type, type),
51
- registerTypes: (scopeOrType, ...types)=>this.registerMany(override, scopeOrType, types),
52
- registerTransient: (typeOrKey, type)=>bind(Scopes.transient)(typeOrKey, type),
53
- registerSingleton: (typeOrKey, typeOrInstance)=>bind(Scopes.singleton)(typeOrKey, typeOrInstance),
54
- registerRequestScoped: (typeOrKey, type)=>bind(Scopes.request)(typeOrKey, type)
55
- };
56
- }
57
- }
@@ -1,9 +0,0 @@
1
- import type { Constructable, FunctionType } from "@decohere/core/reflection";
2
- /**
3
- * What a service is registered and resolved under, compared by identity: the class (or factory)
4
- * itself, a symbol, or a string. A string never matches a class with that name, and two symbols
5
- * with the same description are different keys.
6
- */
7
- export type ServiceKey = string | symbol | Constructable | FunctionType;
8
- /** A readable name for a key, for error messages. */
9
- export declare function describeKey(key: ServiceKey): string;