返回 CodeWhale
group.ts
1 import { Context, Service } from '@deepseek-ai/cordis'
2 import { Entry, type EntryOptions } from './entry.ts'
3 import { EntryTree } from './tree.ts'
4
5 /** Runtime owner for a list of child loader entries. */
6 export class EntryGroup {
7 static readonly key = Symbol.for('cordis.group')
8
9 public data: EntryOptions[] = []
10
11 constructor(public ctx: Context, public tree: EntryTree) {
12 const entry = ctx.fiber.entry
13 if (entry) entry.subgroup = this
14 }
15
16 get context(): Context {
17 return this.ctx
18 }
19
20 async create(options: Omit<EntryOptions, 'id'>) {
21 const id = this.tree.ensureId(options)
22 const entry: Entry = this.tree.store[id] ??= new Entry(this.ctx.loader)
23 // Entry may be moved from another group,
24 // so we need to update the parent reference.
25 entry.parent = this
26 // Use `create: true` to replace existing entry.options.
27 await entry.update(options, true, true)
28 return entry.id
29 }
30
31 unlink(options: EntryOptions) {
32 const config = this.data
33 const index = config.indexOf(options)
34 if (index >= 0) config.splice(index, 1)
35 }
36
37 remove(id: string, isDispose = false) {
38 const entry = this.tree.store[id]
39 if (!entry) return
40 entry.fiber?.dispose()
41 if (!isDispose) {
42 this.unlink(entry.options)
43 }
44 delete this.tree.store[id]
45 this.context.emit('loader/partial-dispose', entry, entry.options, false)
46 }
47
48 async update(config: EntryOptions[]) {
49 const oldConfig = this.data as EntryOptions[]
50 this.data = config
51 const oldMap = Object.fromEntries(oldConfig.map(options => [options.id, options]))
52 const newMap = Object.fromEntries(config.map(options => [options.id ?? Symbol('anonymous'), options]))
53
54 // update inner plugins
55 const ids = Reflect.ownKeys({ ...oldMap, ...newMap }) as string[]
56 await Promise.all(ids.map(async (id) => {
57 if (newMap[id]) {
58 await this.create(newMap[id]).catch((error) => {
59 this.ctx.logger.error(error)
60 })
61 } else {
62 this.remove(id)
63 }
64 }))
65 }
66
67 stop() {
68 for (const options of this.data) {
69 this.remove(options.id, true)
70 }
71 }
72 }
73
74 /** Plugin that mounts a nested loader entry group. */
75 export class Group extends EntryGroup {
76 static initial: Omit<EntryOptions, 'id'>[] = []
77 static readonly [EntryGroup.key] = true
78
79 constructor(public ctx: Context, public config: EntryOptions[]) {
80 super(ctx, ctx.fiber.entry!.parent.tree)
81 ctx.on('internal/update', (config) => {
82 this.update(config)
83 })
84 }
85
86 async* [Service.init]() {
87 yield () => this.stop()
88 await this.update(this.config)
89 }
90 }
91
91 lines TYPESCRIPT