@raubjo/architect 0.5.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.
- package/README.md +860 -0
- package/package.json +121 -0
- package/src/cache/cache.ts +46 -0
- package/src/cache/contract.ts +9 -0
- package/src/cache/manager.ts +110 -0
- package/src/cache/provider.ts +11 -0
- package/src/config/contract.ts +63 -0
- package/src/config/discovery.ts +99 -0
- package/src/config/env.global.d.ts +6 -0
- package/src/config/env.ts +68 -0
- package/src/config/index.ts +5 -0
- package/src/config/provider.ts +17 -0
- package/src/config/repository.ts +164 -0
- package/src/container/adapters/builtin.ts +323 -0
- package/src/container/contract.ts +43 -0
- package/src/container/runtime.ts +29 -0
- package/src/events/bus.ts +174 -0
- package/src/events/concerns/dispatchable.ts +10 -0
- package/src/events/provider.ts +9 -0
- package/src/events/types.ts +9 -0
- package/src/foundation/application.ts +136 -0
- package/src/foundation/current-application.ts +20 -0
- package/src/index.ts +58 -0
- package/src/log/contract.ts +21 -0
- package/src/log/drivers/console.ts +54 -0
- package/src/log/drivers/null.ts +23 -0
- package/src/log/drivers/stack.ts +46 -0
- package/src/log/manager.ts +76 -0
- package/src/log/provider.ts +11 -0
- package/src/react.ts +2 -0
- package/src/renderers/adapters/react.tsx +25 -0
- package/src/renderers/adapters/solid.tsx +26 -0
- package/src/renderers/adapters/svelte.ts +73 -0
- package/src/renderers/adapters/vue.ts +22 -0
- package/src/renderers/contract.ts +12 -0
- package/src/runtimes/react.tsx +81 -0
- package/src/runtimes/solid.tsx +47 -0
- package/src/runtimes/svelte.ts +17 -0
- package/src/runtimes/vue.ts +34 -0
- package/src/solid.ts +2 -0
- package/src/store/adapters/contract.ts +11 -0
- package/src/store/adapters/indexed-db.ts +187 -0
- package/src/store/adapters/local-storage.ts +48 -0
- package/src/store/adapters/memory.ts +35 -0
- package/src/store/manager.ts +68 -0
- package/src/store/provider.ts +10 -0
- package/src/store/store.ts +1 -0
- package/src/support/arr.ts +372 -0
- package/src/support/collection.ts +889 -0
- package/src/support/facades/cache.ts +6 -0
- package/src/support/facades/config.ts +6 -0
- package/src/support/facades/event.ts +6 -0
- package/src/support/facades/facade.ts +146 -0
- package/src/support/facades/index.ts +5 -0
- package/src/support/facades/log.ts +6 -0
- package/src/support/facades/store.ts +6 -0
- package/src/support/fluent.ts +56 -0
- package/src/support/globals.ts +8 -0
- package/src/support/lazy-collection.ts +341 -0
- package/src/support/manager.ts +53 -0
- package/src/support/num.ts +50 -0
- package/src/support/pipeline.ts +29 -0
- package/src/support/service-provider.ts +19 -0
- package/src/support/str.ts +682 -0
- package/src/svelte.ts +2 -0
- package/src/types/peer-deps.d.ts +10 -0
- package/src/vue.ts +2 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import type { Contract } from "./contract"
|
|
2
|
+
|
|
3
|
+
export type ConfigItems = Record<string, unknown>
|
|
4
|
+
export type ConfigDefaults = Record<string | number, unknown>
|
|
5
|
+
|
|
6
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
7
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function resolveDefault<T>(defaultValue: T | (() => T)): T {
|
|
11
|
+
return typeof defaultValue === "function" ? (defaultValue as () => T)() : defaultValue
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function dataGet(source: unknown, path: string, defaultValue: unknown = null): unknown {
|
|
15
|
+
if (!path) {
|
|
16
|
+
return source
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const segments = path.split(".")
|
|
20
|
+
let cursor: unknown = source
|
|
21
|
+
|
|
22
|
+
for (const segment of segments) {
|
|
23
|
+
if (!hasDataKey(cursor, segment)) {
|
|
24
|
+
return resolveDefault(defaultValue)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
cursor = cursor[segment]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return cursor
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function hasDataKey(value: unknown, key: string): value is Record<string, unknown> {
|
|
34
|
+
return isPlainObject(value) && key in value
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function dataTraverse(
|
|
38
|
+
target: Record<string, unknown>,
|
|
39
|
+
path: string,
|
|
40
|
+
onCreate: boolean,
|
|
41
|
+
): [Record<string, unknown>, string] | null {
|
|
42
|
+
const segments = path.split(".")
|
|
43
|
+
let cursor: Record<string, unknown> = target
|
|
44
|
+
|
|
45
|
+
for (let i = 0; i < segments.length - 1; i += 1) {
|
|
46
|
+
const segment = segments[i]
|
|
47
|
+
|
|
48
|
+
if (!isPlainObject(cursor[segment])) {
|
|
49
|
+
if (!onCreate) return null
|
|
50
|
+
cursor[segment] = {}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
cursor = cursor[segment] as Record<string, unknown>
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return [cursor, segments[segments.length - 1]]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function dataSet(target: Record<string, unknown>, path: string, value: unknown): void {
|
|
60
|
+
const result = dataTraverse(target, path, true)
|
|
61
|
+
if (result) result[0][result[1]] = value
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function dataForget(target: Record<string, unknown>, path: string): void {
|
|
65
|
+
const result = dataTraverse(target, path, false)
|
|
66
|
+
if (result) delete result[0][result[1]]
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
class ConfigRepository implements Contract {
|
|
70
|
+
protected items: ConfigItems
|
|
71
|
+
|
|
72
|
+
constructor(items: ConfigItems = {}) {
|
|
73
|
+
this.items = items
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
has(key: string | string[]): boolean {
|
|
77
|
+
const keys = Array.isArray(key) ? key : [key]
|
|
78
|
+
for (const configKey of keys) {
|
|
79
|
+
if (this.get(configKey) == null) {
|
|
80
|
+
return false
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return true
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
get(key: string): unknown
|
|
88
|
+
get<T>(key: string): T | null
|
|
89
|
+
get<T>(key: string, defaultValue: T | (() => T)): T
|
|
90
|
+
get<T>(key: string, defaultValue: T | (() => T) | null): T | null
|
|
91
|
+
get(key: string[]): Record<string, unknown>
|
|
92
|
+
get<T = unknown>(
|
|
93
|
+
key: string | string[],
|
|
94
|
+
defaultValue: T | (() => T) | null = null,
|
|
95
|
+
): T | Record<string, unknown> | null {
|
|
96
|
+
if (Array.isArray(key)) {
|
|
97
|
+
return this.getMany(key)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return dataGet(this.items, key, defaultValue) as T | null
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
getMany(keys: string[] | ConfigDefaults): Record<string, unknown> {
|
|
104
|
+
const results: Record<string, unknown> = {}
|
|
105
|
+
|
|
106
|
+
if (Array.isArray(keys)) {
|
|
107
|
+
for (const key of keys) {
|
|
108
|
+
results[key] = this.get(key)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return results
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
for (const [key, defaultValue] of Object.entries(keys)) {
|
|
115
|
+
results[key] = this.get(key, defaultValue)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return results
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
set(key: string | ConfigItems, value: unknown = null): void {
|
|
122
|
+
const payload = isPlainObject(key) ? key : { [key]: value }
|
|
123
|
+
|
|
124
|
+
for (const [configKey, configValue] of Object.entries(payload)) {
|
|
125
|
+
dataSet(this.items, configKey, configValue)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
prepend(key: string, value: unknown): void {
|
|
130
|
+
const values = this.get<unknown[]>(key, [])
|
|
131
|
+
this.set(key, [value, ...values])
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
push(key: string, value: unknown): void {
|
|
135
|
+
const values = this.get<unknown[]>(key, [])
|
|
136
|
+
values.push(value)
|
|
137
|
+
this.set(key, values)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
all(): ConfigItems {
|
|
141
|
+
return this.items
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
offsetExists(key: string): boolean {
|
|
145
|
+
return this.has(key)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
offsetGet(key: string): unknown
|
|
149
|
+
offsetGet<T>(key: string): T | null
|
|
150
|
+
offsetGet<T = unknown>(key: string): T | null {
|
|
151
|
+
return this.get<T>(key) as T | null
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
offsetSet(key: string, value: unknown): void {
|
|
155
|
+
this.set(key, value)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
offsetUnset(key: string): void {
|
|
159
|
+
dataForget(this.items, key)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export { ConfigRepository }
|
|
164
|
+
export default ConfigRepository
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ContainerBindToSyntax,
|
|
3
|
+
ContainerClass,
|
|
4
|
+
ContainerConcrete,
|
|
5
|
+
ContainerFactory,
|
|
6
|
+
ContainerIdentifier,
|
|
7
|
+
} from "../contract"
|
|
8
|
+
|
|
9
|
+
const INJECT_TOKENS_METADATA_KEY = "ioc:inject.tokens"
|
|
10
|
+
|
|
11
|
+
type Scope = "singleton" | "transient"
|
|
12
|
+
|
|
13
|
+
type BindingRecord<T = unknown> =
|
|
14
|
+
| {
|
|
15
|
+
kind: "constant"
|
|
16
|
+
value: T
|
|
17
|
+
}
|
|
18
|
+
| {
|
|
19
|
+
kind: "class"
|
|
20
|
+
concrete: ContainerClass<T>
|
|
21
|
+
scope: Scope
|
|
22
|
+
cached?: T
|
|
23
|
+
}
|
|
24
|
+
| {
|
|
25
|
+
kind: "factory"
|
|
26
|
+
concrete: ContainerFactory<T>
|
|
27
|
+
scope: Scope
|
|
28
|
+
cached?: T
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type MetadataReflect = typeof Reflect & {
|
|
32
|
+
defineMetadata?: (key: string, value: unknown, target: object) => void
|
|
33
|
+
getMetadata?: (key: string, target: object) => unknown
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const metadataReflect = Reflect as MetadataReflect
|
|
37
|
+
|
|
38
|
+
function isClassConcrete<T>(value: ContainerConcrete<T>): value is ContainerClass<T> {
|
|
39
|
+
return typeof value === "function" && Object.hasOwn(value, "prototype")
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isFactoryConcrete<T>(value: ContainerConcrete<T>): value is ContainerFactory<T> {
|
|
43
|
+
return typeof value === "function" && !isClassConcrete(value)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function readInjectTokens(target: object): Map<number, ContainerIdentifier> {
|
|
47
|
+
const metadata = metadataReflect.getMetadata?.(INJECT_TOKENS_METADATA_KEY, target) as
|
|
48
|
+
| Record<number, ContainerIdentifier>
|
|
49
|
+
| undefined
|
|
50
|
+
|
|
51
|
+
const tokens = new Map<number, ContainerIdentifier>()
|
|
52
|
+
if (!metadata) {
|
|
53
|
+
return tokens
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
for (const [index, identifier] of Object.entries(metadata)) {
|
|
57
|
+
tokens.set(Number(index), identifier)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return tokens
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function readConstructorParamTypes(target: object): Array<ContainerIdentifier | undefined> {
|
|
64
|
+
return (
|
|
65
|
+
(metadataReflect.getMetadata?.("design:paramtypes", target) as
|
|
66
|
+
| Array<ContainerIdentifier | undefined>
|
|
67
|
+
| undefined) ?? []
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
class BuiltinBindingFluent<T> {
|
|
72
|
+
protected record: BindingRecord<T> | null = null
|
|
73
|
+
|
|
74
|
+
constructor(
|
|
75
|
+
protected readonly container: BuiltinContainer,
|
|
76
|
+
protected readonly identifier: ContainerIdentifier<T>,
|
|
77
|
+
) {}
|
|
78
|
+
|
|
79
|
+
to(concrete: ContainerConcrete<T>): {
|
|
80
|
+
inSingletonScope: () => void
|
|
81
|
+
inTransientScope: () => void
|
|
82
|
+
} {
|
|
83
|
+
if (isClassConcrete(concrete)) {
|
|
84
|
+
this.record = { kind: "class", concrete, scope: "singleton" }
|
|
85
|
+
} else if (isFactoryConcrete(concrete)) {
|
|
86
|
+
this.record = { kind: "factory", concrete, scope: "singleton" }
|
|
87
|
+
} else {
|
|
88
|
+
this.record = { kind: "constant", value: concrete as T }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
this.container.setRecord(this.identifier, this.record)
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
inSingletonScope: () => {
|
|
95
|
+
if (this.record && this.record.kind !== "constant") {
|
|
96
|
+
this.record.scope = "singleton"
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
inTransientScope: () => {
|
|
100
|
+
if (this.record && this.record.kind !== "constant") {
|
|
101
|
+
this.record.scope = "transient"
|
|
102
|
+
delete this.record.cached
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
toConstantValue(value: T): void {
|
|
109
|
+
this.record = { kind: "constant", value }
|
|
110
|
+
this.container.setRecord(this.identifier, this.record)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function inject(identifier: ContainerIdentifier): ParameterDecorator {
|
|
115
|
+
return (target, _propertyKey, parameterIndex) => {
|
|
116
|
+
const existing =
|
|
117
|
+
(metadataReflect.getMetadata?.(INJECT_TOKENS_METADATA_KEY, target) as
|
|
118
|
+
| Record<number, ContainerIdentifier>
|
|
119
|
+
| undefined) ?? {}
|
|
120
|
+
existing[parameterIndex] = identifier
|
|
121
|
+
metadataReflect.defineMetadata?.(INJECT_TOKENS_METADATA_KEY, existing, target)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export default class BuiltinContainer implements ContainerContract {
|
|
126
|
+
protected bindings = new Map<ContainerIdentifier, BindingRecord>()
|
|
127
|
+
protected resolving = new Set<ContainerIdentifier>()
|
|
128
|
+
|
|
129
|
+
bind<T>(identifier: ContainerIdentifier<T>): ContainerBindToSyntax<T>
|
|
130
|
+
bind<T>(identifier: ContainerIdentifier<T>, concrete: ContainerConcrete<T>): this
|
|
131
|
+
bind<T>(identifier: ContainerIdentifier<T>, concrete?: ContainerConcrete<T>): ContainerBindToSyntax<T> | this {
|
|
132
|
+
if (typeof concrete !== "undefined") {
|
|
133
|
+
this.removeIfBound(identifier)
|
|
134
|
+
|
|
135
|
+
if (isClassConcrete(concrete)) {
|
|
136
|
+
this.bindings.set(identifier, {
|
|
137
|
+
kind: "class",
|
|
138
|
+
concrete,
|
|
139
|
+
scope: "transient",
|
|
140
|
+
})
|
|
141
|
+
return this
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (isFactoryConcrete(concrete)) {
|
|
145
|
+
this.bindings.set(identifier, {
|
|
146
|
+
kind: "factory",
|
|
147
|
+
concrete,
|
|
148
|
+
scope: "transient",
|
|
149
|
+
})
|
|
150
|
+
return this
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
this.bindings.set(identifier, {
|
|
154
|
+
kind: "factory",
|
|
155
|
+
concrete: () => concrete as T,
|
|
156
|
+
scope: "transient",
|
|
157
|
+
})
|
|
158
|
+
return this
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (this.bindings.has(identifier)) {
|
|
162
|
+
throw new Error(`Cannot bind [${String(identifier)}] because it is already bound.`)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return new BuiltinBindingFluent<T>(this, identifier)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
singleton<T>(identifier: ContainerIdentifier<T>, concrete: ContainerConcrete<T>): this {
|
|
169
|
+
this.removeIfBound(identifier)
|
|
170
|
+
|
|
171
|
+
if (isClassConcrete(concrete)) {
|
|
172
|
+
this.bindings.set(identifier, {
|
|
173
|
+
kind: "class",
|
|
174
|
+
concrete,
|
|
175
|
+
scope: "singleton",
|
|
176
|
+
})
|
|
177
|
+
return this
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (isFactoryConcrete(concrete)) {
|
|
181
|
+
this.bindings.set(identifier, {
|
|
182
|
+
kind: "factory",
|
|
183
|
+
concrete,
|
|
184
|
+
scope: "singleton",
|
|
185
|
+
})
|
|
186
|
+
return this
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
this.bindings.set(identifier, {
|
|
190
|
+
kind: "constant",
|
|
191
|
+
value: concrete as T,
|
|
192
|
+
})
|
|
193
|
+
return this
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
instance<T>(identifier: ContainerIdentifier<T>, value: T): this {
|
|
197
|
+
this.removeIfBound(identifier)
|
|
198
|
+
this.bindings.set(identifier, { kind: "constant", value })
|
|
199
|
+
return this
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
make<T>(identifier: ContainerIdentifier<T>): T {
|
|
203
|
+
const record = this.bindings.get(identifier)
|
|
204
|
+
if (record) {
|
|
205
|
+
return this.resolveRecord(record, identifier) as T
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (typeof identifier === "function") {
|
|
209
|
+
return this.resolveClass(identifier as ContainerClass<T>)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
throw new Error(`Container binding [${String(identifier)}] is not registered.`)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
get<T>(identifier: ContainerIdentifier<T>): T {
|
|
216
|
+
return this.make(identifier)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
bound(identifier: ContainerIdentifier): boolean {
|
|
220
|
+
return this.bindings.has(identifier)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
has(identifier: ContainerIdentifier): boolean {
|
|
224
|
+
return this.bound(identifier)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
unbind(identifier: ContainerIdentifier): void {
|
|
228
|
+
this.bindings.delete(identifier)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
unbindAll(): void {
|
|
232
|
+
this.bindings.clear()
|
|
233
|
+
this.resolving.clear()
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
flush(): void {
|
|
237
|
+
this.unbindAll()
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
getRawContainer(): unknown {
|
|
241
|
+
return this.bindings
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
setRecord<T>(identifier: ContainerIdentifier<T>, record: BindingRecord<T>): void {
|
|
245
|
+
this.bindings.set(identifier, record as BindingRecord)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
protected removeIfBound(identifier: ContainerIdentifier): void {
|
|
249
|
+
this.bindings.delete(identifier)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
protected resolveRecord<T>(record: BindingRecord<T>, _identifier: ContainerIdentifier): T {
|
|
253
|
+
if (record.kind === "constant") {
|
|
254
|
+
return record.value
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (this.hasCachedSingleton(record)) {
|
|
258
|
+
return record.cached
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const resolved = this.resolveConcrete(record)
|
|
262
|
+
|
|
263
|
+
if (record.scope === "singleton") {
|
|
264
|
+
record.cached = resolved
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return resolved
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
protected resolveConcrete<T>(record: Exclude<BindingRecord<T>, { kind: "constant" }>): T {
|
|
271
|
+
if (record.kind === "class") {
|
|
272
|
+
return this.resolveClass(record.concrete)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return record.concrete(this)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
protected hasCachedSingleton<T>(record: BindingRecord<T>): record is BindingRecord<T> & { cached: T } {
|
|
279
|
+
return (
|
|
280
|
+
record.kind !== "constant" &&
|
|
281
|
+
record.scope === "singleton" &&
|
|
282
|
+
"cached" in record &&
|
|
283
|
+
typeof record.cached !== "undefined"
|
|
284
|
+
)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
protected resolveClass<T>(concrete: ContainerClass<T>): T {
|
|
288
|
+
this.assertNotResolving(concrete)
|
|
289
|
+
this.resolving.add(concrete)
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
const paramTypes = readConstructorParamTypes(concrete)
|
|
293
|
+
const injectTokens = readInjectTokens(concrete)
|
|
294
|
+
const args = paramTypes.map((designType, index) =>
|
|
295
|
+
this.resolveParameter(concrete, injectTokens, designType, index),
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
return new concrete(...args)
|
|
299
|
+
} finally {
|
|
300
|
+
this.resolving.delete(concrete)
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
protected assertNotResolving(concrete: ContainerClass<unknown>): void {
|
|
305
|
+
if (this.resolving.has(concrete)) {
|
|
306
|
+
throw new Error(`Circular dependency detected while resolving [${concrete.name || "anonymous"}].`)
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
protected resolveParameter(
|
|
311
|
+
concrete: ContainerClass<unknown>,
|
|
312
|
+
injectTokens: Map<number, ContainerIdentifier>,
|
|
313
|
+
designType: ContainerIdentifier | undefined,
|
|
314
|
+
index: number,
|
|
315
|
+
): unknown {
|
|
316
|
+
const token = injectTokens.get(index) ?? designType
|
|
317
|
+
if (!token) {
|
|
318
|
+
throw new Error(`Cannot resolve parameter #${index} for [${concrete.name || "anonymous"}].`)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return this.make(token)
|
|
322
|
+
}
|
|
323
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export type ContainerFactory<T> = (container: ContainerContract) => T
|
|
2
|
+
export type ContainerClass<T> = new (...args: any[]) => T
|
|
3
|
+
export type ContainerIdentifier<T = unknown> = string | symbol | ContainerClass<T>
|
|
4
|
+
export type ContainerConcrete<T> = ContainerClass<T> | ContainerFactory<T> | T
|
|
5
|
+
|
|
6
|
+
export interface ContainerScopeSyntax {
|
|
7
|
+
/** Set singleton scope for this binding. */
|
|
8
|
+
inSingletonScope(): void
|
|
9
|
+
/** Set transient scope for this binding. */
|
|
10
|
+
inTransientScope(): void
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ContainerBindToSyntax<T> {
|
|
14
|
+
/** Bind an identifier to a class, factory, or value concrete. */
|
|
15
|
+
to(concrete: ContainerConcrete<T>): ContainerScopeSyntax
|
|
16
|
+
/** Bind an identifier to a constant shared value. */
|
|
17
|
+
toConstantValue(value: T): void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ContainerContract {
|
|
21
|
+
/** Register a singleton binding using a class, factory, or value concrete. */
|
|
22
|
+
singleton<T>(identifier: ContainerIdentifier<T>, concrete: ContainerConcrete<T>): this
|
|
23
|
+
/** Register a binding using a class, factory, or value concrete. */
|
|
24
|
+
bind<T>(identifier: ContainerIdentifier<T>, concrete: ContainerConcrete<T>): this
|
|
25
|
+
/** Register an existing instance as a shared binding. */
|
|
26
|
+
instance<T>(identifier: ContainerIdentifier<T>, value: T): this
|
|
27
|
+
/** Resolve an instance from the container. */
|
|
28
|
+
make<T>(identifier: ContainerIdentifier<T>): T
|
|
29
|
+
/** Compatibility alias for make(). */
|
|
30
|
+
get<T>(identifier: ContainerIdentifier<T>): T
|
|
31
|
+
/** Determine if an identifier is currently bound. */
|
|
32
|
+
bound(identifier: ContainerIdentifier): boolean
|
|
33
|
+
/** Alias for bound(). */
|
|
34
|
+
has(identifier: ContainerIdentifier): boolean
|
|
35
|
+
/** Remove a specific binding by identifier. */
|
|
36
|
+
unbind(identifier: ContainerIdentifier): void
|
|
37
|
+
/** Remove all bindings from the container. */
|
|
38
|
+
unbindAll(): void
|
|
39
|
+
/** Clear all bindings. */
|
|
40
|
+
flush(): void
|
|
41
|
+
/** Expose the underlying container for advanced use. */
|
|
42
|
+
getRawContainer(): unknown
|
|
43
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import BuiltinContainer from "./adapters/builtin"
|
|
2
|
+
import type { ContainerContract } from "./contract"
|
|
3
|
+
|
|
4
|
+
export type ContainerAdapter = "builtin"
|
|
5
|
+
|
|
6
|
+
export type ContainerRuntimeOptions = {
|
|
7
|
+
adapter?: ContainerAdapter
|
|
8
|
+
factory?: (() => ContainerContract) | null
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type ResolvedContainerRuntimeOptions = {
|
|
12
|
+
adapter: ContainerAdapter
|
|
13
|
+
factory: (() => ContainerContract) | null
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function mergeContainerRuntimeOptions(options: ContainerRuntimeOptions = {}): ResolvedContainerRuntimeOptions {
|
|
17
|
+
return {
|
|
18
|
+
adapter: options.adapter ?? "builtin",
|
|
19
|
+
factory: options.factory ?? null,
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function createRuntimeContainer(options: ResolvedContainerRuntimeOptions): ContainerContract {
|
|
24
|
+
if (typeof options.factory === "function") {
|
|
25
|
+
return options.factory()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return new BuiltinContainer()
|
|
29
|
+
}
|