fix(cordis): make config reload transactional
This commit is contained in:
Vendored
+51
-21
@@ -19,12 +19,23 @@ export class EntryGroup {
|
||||
|
||||
async create(options: Omit<EntryOptions, 'id'>) {
|
||||
const id = this.tree.ensureId(options)
|
||||
const entry: Entry = this.tree.store[id] ??= new Entry(this.ctx.loader)
|
||||
const existing = this.tree.store[id]
|
||||
const entry: Entry = existing ?? (this.tree.store[id] = new Entry(this.ctx.loader))
|
||||
const previousParent = entry.parent
|
||||
// Entry may be moved from another group,
|
||||
// so we need to update the parent reference.
|
||||
entry.parent = this
|
||||
// Use `create: true` to replace existing entry.options.
|
||||
await entry.update(options, true, true)
|
||||
try {
|
||||
await entry.update(options, true, true)
|
||||
} catch (error) {
|
||||
if (existing) {
|
||||
entry.parent = previousParent
|
||||
} else {
|
||||
delete this.tree.store[id]
|
||||
}
|
||||
throw error
|
||||
}
|
||||
return entry.id
|
||||
}
|
||||
|
||||
@@ -34,10 +45,10 @@ export class EntryGroup {
|
||||
if (index >= 0) config.splice(index, 1)
|
||||
}
|
||||
|
||||
remove(id: string, isDispose = false) {
|
||||
async remove(id: string, isDispose = false) {
|
||||
const entry = this.tree.store[id]
|
||||
if (!entry) return
|
||||
entry.fiber?.dispose()
|
||||
await entry._dispose()
|
||||
if (!isDispose) {
|
||||
this.unlink(entry.options)
|
||||
}
|
||||
@@ -47,26 +58,47 @@ export class EntryGroup {
|
||||
|
||||
async update(config: EntryOptions[]) {
|
||||
const oldConfig = this.data as EntryOptions[]
|
||||
this.data = config
|
||||
const seen = new Set<string>()
|
||||
for (const options of config) {
|
||||
const id = this.tree.ensureId(options)
|
||||
if (seen.has(id)) throw new TypeError(`duplicate loader entry id: ${id}`)
|
||||
seen.add(id)
|
||||
}
|
||||
const oldMap = Object.fromEntries(oldConfig.map(options => [options.id, options]))
|
||||
const newMap = Object.fromEntries(config.map(options => [options.id ?? Symbol('anonymous'), options]))
|
||||
const newMap = Object.fromEntries(config.map(options => [options.id, options]))
|
||||
|
||||
// update inner plugins
|
||||
const ids = Reflect.ownKeys({ ...oldMap, ...newMap }) as string[]
|
||||
await Promise.all(ids.map(async (id) => {
|
||||
if (newMap[id]) {
|
||||
await this.create(newMap[id]).catch((error) => {
|
||||
this.ctx.logger.error(error)
|
||||
})
|
||||
} else {
|
||||
this.remove(id)
|
||||
try {
|
||||
for (const options of config) await this.create(options)
|
||||
for (const id of Object.keys(oldMap)) {
|
||||
if (!newMap[id]) await this.remove(id, true)
|
||||
}
|
||||
}))
|
||||
this.data = config
|
||||
} catch (error) {
|
||||
const rollbackErrors: unknown[] = []
|
||||
for (const id of Object.keys(newMap).reverse()) {
|
||||
if (oldMap[id]) continue
|
||||
try {
|
||||
await this.remove(id, true)
|
||||
} catch (rollbackError) {
|
||||
rollbackErrors.push(rollbackError)
|
||||
}
|
||||
}
|
||||
for (const options of oldConfig) {
|
||||
try {
|
||||
await this.create(options)
|
||||
} catch (rollbackError) {
|
||||
rollbackErrors.push(rollbackError)
|
||||
}
|
||||
}
|
||||
this.data = oldConfig
|
||||
if (rollbackErrors.length) throw new AggregateError([error, ...rollbackErrors], 'loader entry rollback failed')
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
stop() {
|
||||
async stop() {
|
||||
for (const options of this.data) {
|
||||
this.remove(options.id, true)
|
||||
await this.remove(options.id, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -78,9 +110,7 @@ export class Group extends EntryGroup {
|
||||
|
||||
constructor(public ctx: Context, public config: EntryOptions[]) {
|
||||
super(ctx, ctx.fiber.entry!.parent.tree)
|
||||
ctx.on('internal/update', (config) => {
|
||||
this.update(config)
|
||||
})
|
||||
ctx.on('internal/update', config => this.update(config))
|
||||
}
|
||||
|
||||
async* [Service.init]() {
|
||||
|
||||
Reference in New Issue
Block a user