Skip to content

Commit f46774c

Browse files
committed
fix(injecta): incorrectly build
1 parent 4c26365 commit f46774c

File tree

5 files changed

+228
-175
lines changed

5 files changed

+228
-175
lines changed

packages/injecta/package.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,14 @@
1414
"url": "https://github.com/moeru-ai/airi.git",
1515
"directory": "packages/injecta"
1616
},
17+
"exports": {
18+
".": {
19+
"type": "./dist/index.d.mjs",
20+
"default": "./dist/index.mjs"
21+
}
22+
},
1723
"scripts": {
18-
"typecheck": "tsc --noEmit"
24+
"typecheck": "tsc --noEmit",
25+
"build": "tsdown"
1926
}
2027
}

packages/injecta/src/index.ts

Lines changed: 34 additions & 174 deletions
Original file line numberDiff line numberDiff line change
@@ -1,175 +1,35 @@
1-
import type { LifecycleTriggerable } from './builtin'
2-
3-
export type DependencyMap = Record<string, any>
4-
5-
export interface Container {
6-
providers: Map<string, ProvideOptionObject<any, any>>
7-
instances: Map<string, any>
8-
lifecycleHooks: Map<string, LifecycleTriggerable>
9-
dependencyGraph: Map<string, string[]>
10-
invocations: InvokeOptionObject<any>[]
11-
}
12-
13-
export type BuildContext<D extends DependencyMap | undefined = undefined> = {
14-
container: Container
15-
name: string
16-
} & (D extends undefined ? { dependsOn?: unknown } : { dependsOn: D })
17-
18-
export type ProvideOptionObject<T, D extends DependencyMap | undefined = undefined> = { build: (context: BuildContext<D>) => T | Promise<T> } & (D extends undefined ? { dependsOn?: Record<string, never> } : { dependsOn: { [K in keyof D]: string } })
19-
export type ProvideOptionFunc<T, D extends DependencyMap | undefined = undefined> = (context: BuildContext<D>) => T | Promise<T>
20-
export type ProvideOption<T, D extends DependencyMap | undefined = undefined> = ProvideOptionObject<T, D> | ProvideOptionFunc<T, D>
21-
22-
export type InvokeOptionObject<D extends DependencyMap | undefined = undefined> = { callback: (dependencies: D) => void | Promise<void> } & (D extends undefined ? { dependsOn?: Record<string, never> } : { dependsOn: { [K in keyof D]: string } })
23-
export type InvokeOptionFunc<D extends DependencyMap | undefined = undefined> = (dependencies: D) => void | Promise<void>
24-
export type InvokeOption<D extends DependencyMap | undefined = undefined> = InvokeOptionObject<D> | InvokeOptionFunc<D>
25-
26-
export function createContainer(): Container {
27-
return {
28-
providers: new Map(),
29-
instances: new Map(),
30-
lifecycleHooks: new Map(),
31-
dependencyGraph: new Map(),
32-
invocations: [],
33-
}
34-
}
35-
36-
export function provide<D extends DependencyMap | undefined, T = any>(
37-
container: Container,
38-
name: string,
39-
option: ProvideOption<T, D>,
40-
): void {
41-
const providerObject = typeof option === 'function'
42-
? { build: option } as ProvideOptionObject<any, any>
43-
: option as ProvideOptionObject<any, any>
44-
45-
container.providers.set(name, providerObject)
46-
47-
// Track dependencies for lifecycle ordering
48-
const dependencies = providerObject.dependsOn ? Object.values(providerObject.dependsOn) : []
49-
container.dependencyGraph.set(name, dependencies)
50-
}
51-
52-
export function invoke<D extends DependencyMap>(container: Container, option: InvokeOption<D>): void {
53-
if (typeof option === 'function') {
54-
container.invocations.push({ callback: option } as InvokeOptionObject<any>)
55-
}
56-
else {
57-
container.invocations.push(option as InvokeOptionObject<any>)
58-
}
59-
}
60-
61-
async function resolveInstance<T>(container: Container, name: string): Promise<T> {
62-
if (container.instances.has(name)) {
63-
return container.instances.get(name) as T
64-
}
65-
66-
const provider = container.providers.get(name)
67-
if (!provider) {
68-
throw new Error(`No provider found for '${name}'`)
69-
}
70-
71-
const resolvedDependencies: Record<string, any> = {}
72-
let serviceLifecycle: any = null
73-
74-
if (provider.dependsOn) {
75-
for (const [key, depName] of Object.entries(provider.dependsOn)) {
76-
if (depName === 'lifecycle') {
77-
// Create individual lifecycle instance for this service
78-
const { buildLifecycle } = await import('./builtin')
79-
serviceLifecycle = buildLifecycle()
80-
resolvedDependencies[key] = serviceLifecycle
81-
// Track this service's lifecycle
82-
container.lifecycleHooks.set(name, serviceLifecycle)
83-
}
84-
else {
85-
resolvedDependencies[key] = await resolveInstance(container, depName)
86-
}
87-
}
88-
}
89-
90-
const context: BuildContext<typeof provider.dependsOn> = {
91-
container,
92-
dependsOn: resolvedDependencies,
93-
name,
94-
}
95-
96-
const instance = await provider.build(context)
97-
container.instances.set(name, instance)
98-
99-
return instance as T
100-
}
101-
102-
function topologicalSort(dependencyGraph: Map<string, string[]>): string[] {
103-
const visited = new Set<string>()
104-
const visiting = new Set<string>()
105-
const result: string[] = []
106-
107-
function visit(node: string) {
108-
if (visiting.has(node)) {
109-
throw new Error(`Circular dependency detected involving '${node}'`)
110-
}
111-
if (visited.has(node)) {
112-
return
113-
}
114-
115-
visiting.add(node)
116-
const dependencies = dependencyGraph.get(node) || []
117-
for (const dep of dependencies) {
118-
visit(dep)
119-
}
120-
visiting.delete(node)
121-
visited.add(node)
122-
result.push(node)
123-
}
124-
125-
for (const node of dependencyGraph.keys()) {
126-
if (!visited.has(node)) {
127-
visit(node)
128-
}
129-
}
130-
131-
return result
132-
}
133-
134-
export async function startLifecycleHooks(container: Container): Promise<void> {
135-
const sortedServices = topologicalSort(container.dependencyGraph)
136-
137-
for (const serviceName of sortedServices) {
138-
const lifecycle = container.lifecycleHooks.get(serviceName)
139-
if (lifecycle?.emitOnStart) {
140-
await lifecycle.emitOnStart()
141-
}
142-
}
143-
}
144-
145-
export async function stopLifecycleHooks(container: Container): Promise<void> {
146-
const sortedServices = topologicalSort(container.dependencyGraph)
147-
148-
// Shutdown in reverse order
149-
for (const serviceName of sortedServices.reverse()) {
150-
const lifecycle = container.lifecycleHooks.get(serviceName)
151-
if (lifecycle?.emitOnStop) {
152-
await lifecycle.emitOnStop()
153-
}
154-
}
155-
}
156-
157-
export async function start(container: Container): Promise<void> {
158-
await startLifecycleHooks(container)
159-
160-
for (const invocation of container.invocations) {
161-
const resolvedDependencies: Record<string, any> = {}
162-
163-
if (invocation.dependsOn) {
164-
for (const [key, depName] of Object.entries(invocation.dependsOn)) {
165-
resolvedDependencies[key] = await resolveInstance(container, depName)
166-
}
167-
}
168-
169-
await invocation.callback(resolvedDependencies)
170-
}
171-
}
172-
173-
export async function stop(container: Container): Promise<void> {
174-
await stopLifecycleHooks(container)
1+
import {
2+
invoke as globalInvoke,
3+
provide as globalProvide,
4+
start as globalStart,
5+
stop as globalStop,
6+
} from './global'
7+
8+
export type { Lifecycle } from './builtin'
9+
10+
export {
11+
createContainer,
12+
invoke,
13+
provide,
14+
start,
15+
stop,
16+
} from './scoped'
17+
18+
export type {
19+
BuildContext,
20+
Container,
21+
DependencyMap,
22+
InvokeOption,
23+
InvokeOptionFunc,
24+
InvokeOptionObject,
25+
ProvideOption,
26+
ProvideOptionFunc,
27+
ProvideOptionObject,
28+
} from './scoped'
29+
30+
export const injecta = {
31+
provide: globalProvide,
32+
invoke: globalInvoke,
33+
start: globalStart,
34+
stop: globalStop,
17535
}
File renamed without changes.

0 commit comments

Comments
 (0)