Skip to content

Commit b438495

Browse files
committed
feat(injecta): supported typed use and auto name use
1 parent b0af3cd commit b438495

File tree

11 files changed

+517
-52
lines changed

11 files changed

+517
-52
lines changed

apps/stage-tamagotchi/src/main/index.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,24 @@ async function setupProjectAIRIServerRuntime() {
108108
app.whenReady().then(async () => {
109109
await setupProjectAIRIServerRuntime()
110110

111-
injecta.setLogger(createLoggLogger())
112-
injecta.provide('windows:settings', () => setupSettingsWindowReusableFunc())
113-
injecta.provide<{ settingsWindow: () => Promise<BrowserWindow> }>('windows:main', { dependsOn: { settingsWindow: 'windows:settings' }, build: async ({ dependsOn }) => setupMainWindow(dependsOn) })
114-
injecta.provide<{ mainWindow: BrowserWindow, settingsWindow: () => Promise<BrowserWindow> }>('tray', { dependsOn: { mainWindow: 'windows:main', settingsWindow: 'windows:settings' }, build: async ({ dependsOn }) => setupTray(dependsOn) })
115-
injecta.invoke({ dependsOn: { mainWindow: 'windows:main', tray: 'tray' }, callback: noop })
111+
injecta.setLogger(createLoggLogger(useLogg('injecta').useGlobalConfig()))
112+
113+
const settingsWindow = injecta.provide('windows:settings', {
114+
build: () => setupSettingsWindowReusableFunc(),
115+
})
116+
const mainWindow = injecta.provide('windows:main', {
117+
dependsOn: { settingsWindow },
118+
build: async ({ dependsOn }) => setupMainWindow(dependsOn),
119+
})
120+
const tray = injecta.provide('app:tray', {
121+
dependsOn: { mainWindow, settingsWindow },
122+
build: async ({ dependsOn }) => setupTray(dependsOn),
123+
})
124+
injecta.invoke({
125+
dependsOn: { mainWindow, tray },
126+
callback: noop,
127+
})
128+
116129
injecta.start()
117130

118131
// Lifecycle

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
"@parcel/watcher",
100100
"bufferutil",
101101
"electron",
102+
"electron-click-drag-plugin",
102103
"es5-ext",
103104
"esbuild",
104105
"ffmpeg-static",

packages/injecta/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@
2424
"typecheck": "tsc --noEmit",
2525
"build": "tsdown"
2626
},
27-
"dependencies": {
28-
"@guiiai/logg": "catalog:"
27+
"devDependencies": {
28+
"@guiiai/logg": "catalog:",
29+
"error-stack-parser": "^2.1.4",
30+
"nanoid": "^5.1.6"
2931
}
3032
}
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
import type { Lifecycle } from './builtin'
2+
3+
import { beforeEach, describe, expect, it, vi } from 'vitest'
4+
5+
import { invoke, provide, resetContainer, start, stop } from './global'
6+
import { lifecycle } from './scoped'
7+
8+
beforeEach(() => {
9+
resetContainer()
10+
})
11+
12+
describe('workflow with named', () => {
13+
it('should work with named pattern', async () => {
14+
interface Database {
15+
connect: () => Promise<void>
16+
close: () => Promise<void>
17+
}
18+
19+
const databaseConnectSpy = vi.fn()
20+
const databaseCloseSpy = vi.fn()
21+
22+
function createDatabase(params: { lifecycle?: Lifecycle }): Database {
23+
const database: Database = { connect: databaseConnectSpy, close: databaseCloseSpy }
24+
params.lifecycle?.appHooks.onStop(async () => await database.close())
25+
return database
26+
}
27+
28+
interface WebSocketServer {
29+
start: () => Promise<void>
30+
stop: () => Promise<void>
31+
}
32+
33+
const webSocketServerStartSpy = vi.fn()
34+
const webSocketServerStopSpy = vi.fn()
35+
36+
async function createWebSocketServer(params: { database: Database, lifecycle?: Lifecycle }): Promise<WebSocketServer> {
37+
await params.database.connect()
38+
const server: WebSocketServer = { start: webSocketServerStartSpy, stop: webSocketServerStopSpy }
39+
params.lifecycle?.appHooks.onStop(async () => await server.stop())
40+
return server
41+
}
42+
43+
provide<{ lifecycle: Lifecycle }>('db', {
44+
dependsOn: { lifecycle: 'lifecycle' },
45+
build: async ({ dependsOn }) => createDatabase({ lifecycle: dependsOn.lifecycle }),
46+
})
47+
48+
provide<{ database: Database, lifecycle: Lifecycle }>('ws', {
49+
dependsOn: { database: 'db', lifecycle: 'lifecycle' },
50+
build: async ({ dependsOn }) => createWebSocketServer({ database: dependsOn.database, lifecycle: dependsOn.lifecycle }),
51+
})
52+
53+
invoke<{ webSocketServer: WebSocketServer }>({
54+
dependsOn: { webSocketServer: 'ws' },
55+
callback: async ({ webSocketServer }) => await webSocketServer.start(),
56+
})
57+
58+
await start()
59+
await stop()
60+
61+
// eslint-disable-next-line no-lone-blocks
62+
{
63+
expect(databaseConnectSpy).toHaveBeenCalledTimes(1)
64+
expect(databaseCloseSpy).toHaveBeenCalledTimes(1)
65+
expect(webSocketServerStartSpy).toHaveBeenCalledTimes(1)
66+
expect(webSocketServerStopSpy).toHaveBeenCalledTimes(1)
67+
}
68+
})
69+
})
70+
71+
describe('workflow with typed', () => {
72+
it('should work with typed named pattern', async () => {
73+
interface Database {
74+
connect: () => Promise<void>
75+
close: () => Promise<void>
76+
}
77+
78+
const databaseConnectSpy = vi.fn()
79+
const databaseCloseSpy = vi.fn()
80+
81+
function createDatabase(params: { lifecycle?: Lifecycle }): Database {
82+
const database: Database = { connect: databaseConnectSpy, close: databaseCloseSpy }
83+
params.lifecycle?.appHooks.onStop(async () => await database.close())
84+
return database
85+
}
86+
87+
interface WebSocketServer {
88+
start: () => Promise<void>
89+
stop: () => Promise<void>
90+
}
91+
92+
const webSocketServerStartSpy = vi.fn()
93+
const webSocketServerStopSpy = vi.fn()
94+
95+
async function createWebSocketServer(params: { database: Database, lifecycle?: Lifecycle }): Promise<WebSocketServer> {
96+
await params.database.connect()
97+
const server: WebSocketServer = { start: webSocketServerStartSpy, stop: webSocketServerStopSpy }
98+
params.lifecycle?.appHooks.onStop(async () => await server.stop())
99+
return server
100+
}
101+
102+
const database = provide('db', {
103+
dependsOn: { lifecycle },
104+
build: async ({ dependsOn }) => createDatabase(dependsOn),
105+
})
106+
107+
const webSocketServer = provide('ws', {
108+
dependsOn: { database, lifecycle },
109+
build: async ({ dependsOn }) => createWebSocketServer(dependsOn),
110+
})
111+
112+
invoke({
113+
dependsOn: { webSocketServer },
114+
callback: async ({ webSocketServer }) => await webSocketServer.start(),
115+
})
116+
117+
await start()
118+
await stop()
119+
120+
// eslint-disable-next-line no-lone-blocks
121+
{
122+
expect(databaseConnectSpy).toHaveBeenCalledTimes(1)
123+
expect(databaseCloseSpy).toHaveBeenCalledTimes(1)
124+
expect(webSocketServerStartSpy).toHaveBeenCalledTimes(1)
125+
expect(webSocketServerStopSpy).toHaveBeenCalledTimes(1)
126+
}
127+
})
128+
})
129+
130+
describe('workflow with auto name', () => {
131+
it('should work with auto name pattern', async () => {
132+
interface Database {
133+
connect: () => Promise<void>
134+
close: () => Promise<void>
135+
}
136+
137+
const databaseConnectSpy = vi.fn()
138+
const databaseCloseSpy = vi.fn()
139+
140+
function createDatabase(params: { lifecycle?: Lifecycle }): Database {
141+
const database: Database = { connect: databaseConnectSpy, close: databaseCloseSpy }
142+
params.lifecycle?.appHooks.onStop(async () => await database.close())
143+
return database
144+
}
145+
146+
interface WebSocketServer {
147+
start: () => Promise<void>
148+
stop: () => Promise<void>
149+
}
150+
151+
const webSocketServerStartSpy = vi.fn()
152+
const webSocketServerStopSpy = vi.fn()
153+
154+
async function createWebSocketServer(params: { database: Database, lifecycle?: Lifecycle }): Promise<WebSocketServer> {
155+
await params.database.connect()
156+
const server: WebSocketServer = { start: webSocketServerStartSpy, stop: webSocketServerStopSpy }
157+
params.lifecycle?.appHooks.onStop(async () => await server.stop())
158+
return server
159+
}
160+
161+
const database = provide({
162+
dependsOn: { lifecycle },
163+
build: async ({ dependsOn }) => createDatabase(dependsOn),
164+
})
165+
166+
const webSocketServer = provide({
167+
dependsOn: { database, lifecycle },
168+
build: async ({ dependsOn }) => createWebSocketServer(dependsOn),
169+
})
170+
171+
invoke({
172+
dependsOn: { webSocketServer },
173+
callback: async ({ webSocketServer }) => await webSocketServer.start(),
174+
})
175+
176+
await start()
177+
await stop()
178+
179+
// eslint-disable-next-line no-lone-blocks
180+
{
181+
expect(databaseConnectSpy).toHaveBeenCalledTimes(1)
182+
expect(databaseCloseSpy).toHaveBeenCalledTimes(1)
183+
expect(webSocketServerStartSpy).toHaveBeenCalledTimes(1)
184+
expect(webSocketServerStopSpy).toHaveBeenCalledTimes(1)
185+
}
186+
})
187+
})

packages/injecta/src/global.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,33 @@
1-
import type { DependencyMap, InvokeOption, Logger, ProvideOption } from '.'
1+
import type { DependencyMap, InvokeOption, InvokeOptionWithKeys, Logger, ProvideOption, ProvideOptionWithKeys, ResolveDependencyDeclaration } from '.'
2+
import type { ProvidedKey } from './scoped'
23

34
import { createContainer, provide as indexProvide, start as indexStart, stop as indexStop } from '.'
45

5-
const globalContainer = createContainer()
6+
let globalContainer = createContainer()
67

78
export function setLogger(logger: Logger) {
89
globalContainer.logger = logger
910
}
1011

11-
export function provide<D extends DependencyMap | undefined, T = any>(
12-
name: string,
13-
option: ProvideOption<T, D>,
14-
): void {
15-
indexProvide(globalContainer, name, option)
12+
export function resetContainer() {
13+
globalContainer = createContainer()
1614
}
1715

18-
export function invoke<D extends DependencyMap>(option: InvokeOption<D>): void {
16+
export function provide<T, Key extends string, Deps extends Record<string, string | ProvidedKey<any, any, any>>>(name: Key, option: ProvideOptionWithKeys<T, Deps>,): ProvidedKey<Key, T, ResolveDependencyDeclaration<Deps>>
17+
export function provide<D extends DependencyMap | undefined, T = any, Key extends string = string>(name: Key, option: ProvideOption<T, D>,): ProvidedKey<Key, T, D>
18+
export function provide<T, Key extends string, Deps extends Record<string, string | ProvidedKey<any, any, any>>>(option: ProvideOptionWithKeys<T, Deps>,): ProvidedKey<Key, T, ResolveDependencyDeclaration<Deps>>
19+
export function provide<D extends DependencyMap | undefined, T = any, Key extends string = string>(option: ProvideOption<T, D>,): ProvidedKey<Key, T, D>
20+
export function provide<D extends DependencyMap | undefined, T = any, Key extends string = string>(nameOrOption: Key | ProvideOption<T, D> | ProvideOptionWithKeys<T, any>, option?: ProvideOption<T, D> | ProvideOptionWithKeys<T, any>): ProvidedKey<Key, T, D> {
21+
if (option != null && typeof option === 'function') {
22+
return indexProvide(globalContainer, nameOrOption as any, { build: option, autoNameStackIndex: 2 } as any)
23+
}
24+
25+
return indexProvide(globalContainer, nameOrOption as any, { ...option, autoNameStackIndex: 2 } as any)
26+
}
27+
28+
export function invoke<Deps extends Record<string, string | ProvidedKey<any, any, any>>>(option: InvokeOptionWithKeys<Deps>): void
29+
export function invoke<D extends DependencyMap>(option: InvokeOption<D>): void
30+
export function invoke<D extends DependencyMap>(option: InvokeOption<D> | InvokeOptionWithKeys<any>): void {
1931
if (typeof option === 'function') {
2032
globalContainer.invocations.push({ callback: option } as any)
2133
}

packages/injecta/src/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export {
1919
export {
2020
createContainer,
2121
invoke,
22+
lifecycle,
2223
provide,
2324
start,
2425
stop,
@@ -30,9 +31,15 @@ export type {
3031
InvokeOption,
3132
InvokeOptionFunc,
3233
InvokeOptionObject,
34+
InvokeOptionObjectWithKeys,
35+
InvokeOptionWithKeys,
36+
ProvidedKey,
3337
ProvideOption,
3438
ProvideOptionFunc,
3539
ProvideOptionObject,
40+
ProvideOptionObjectWithKeys,
41+
ProvideOptionWithKeys,
42+
ResolveDependencyDeclaration,
3643
} from './scoped'
3744

3845
export const injecta = {

packages/injecta/src/logger.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useLogg } from '@guiiai/logg'
1+
import type { Logg as LoggLogger } from '@guiiai/logg'
22

33
import { name } from '../package.json'
44

@@ -80,9 +80,7 @@ export function createDefaultLogger(): Logger {
8080
}
8181
}
8282

83-
export function createLoggLogger(): Logger {
84-
const logg = useLogg(name).useGlobalConfig()
85-
83+
export function createLoggLogger(logg: LoggLogger): Logger {
8684
return {
8785
provide: (name: string, dependencies: string[]) => {
8886
const depsStr = dependencies.length > 0 ? ` (depends on: ${dependencies.join(', ')})` : ''

0 commit comments

Comments
 (0)