Vendor Cordis framework packages as source
cordis 4.0.0-rc.6, plugin-loader, -include, -group, -timer, -hmr, -logger-console, cosmokit 1.8.1, schemastery 3.18.0 — copied from the cordis-workspace checkout, flattened under vendor/, original npm names, private: true. vendor/README.md is the manifest: upstream repos + commit SHAs, local-modification log, sync procedure. Local modification: hmr's locale YAML imports and .i18n() call removed (avoids a runtime YAML import hook we don't vendor).
This commit is contained in:
133
vendor/loader/src/config/tree.ts
vendored
Normal file
133
vendor/loader/src/config/tree.ts
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
import { composeError, Context } from 'cordis'
|
||||
import { Dict, isNonNullable } from 'cosmokit'
|
||||
import { Entry, EntryOptions } from './entry.ts'
|
||||
import { EntryGroup } from './group.ts'
|
||||
|
||||
/** Mutable tree of loader entries. Persistence is supplied by subclasses. */
|
||||
export abstract class EntryTree {
|
||||
static readonly sep = ':'
|
||||
|
||||
public ctx: Context
|
||||
public enableLogs?: boolean
|
||||
public root: EntryGroup
|
||||
public store: Dict<Entry> = Object.create(null)
|
||||
|
||||
constructor(ctx: Context) {
|
||||
this.ctx = ctx.extend({ baseUrl: ctx.baseUrl })
|
||||
this.root = new EntryGroup(this.ctx, this)
|
||||
const entry = this.ctx.fiber.entry
|
||||
if (entry) entry.subtree = this
|
||||
}
|
||||
|
||||
get context(): Context {
|
||||
return this.ctx
|
||||
}
|
||||
|
||||
/** Iterate entries in this tree and any nested subtrees. */
|
||||
* entries(): Generator<Entry, void, void> {
|
||||
for (const entry of Object.values(this.store)) {
|
||||
yield entry
|
||||
if (!entry.subtree) continue
|
||||
yield* entry.subtree.entries()
|
||||
}
|
||||
}
|
||||
|
||||
/** Return pending import and lifecycle tasks owned by this tree. */
|
||||
getTasks() {
|
||||
return [...this.entries()]
|
||||
.map(entry => entry._initTask || entry.fiber?.inertia)
|
||||
.filter(isNonNullable)
|
||||
}
|
||||
|
||||
/** Wait until this tree has no pending import or lifecycle tasks. */
|
||||
async await() {
|
||||
while (true) {
|
||||
const tasks = this.getTasks()
|
||||
if (!tasks.length) return
|
||||
await Promise.allSettled(tasks)
|
||||
}
|
||||
}
|
||||
|
||||
ensureId(options: Partial<EntryOptions>) {
|
||||
if (!options.id) {
|
||||
do {
|
||||
options.id = Math.random().toString(16).slice(2, 10)
|
||||
} while (this.store[options.id])
|
||||
}
|
||||
return options.id!
|
||||
}
|
||||
|
||||
/** Resolve an entry by id, including nested ids separated by `EntryTree.sep`. */
|
||||
resolve(id: string) {
|
||||
const parts = id.split(EntryTree.sep)
|
||||
let tree: EntryTree | undefined = this
|
||||
const final = parts.pop()!
|
||||
for (const part of parts) {
|
||||
tree = tree.store[part]?.subtree
|
||||
if (!tree) throw new Error(`cannot resolve entry ${id}`)
|
||||
}
|
||||
const entry = tree.store[final]
|
||||
if (!entry) throw new Error(`cannot resolve entry ${id}`)
|
||||
return entry
|
||||
}
|
||||
|
||||
resolveGroup(id: string | null) {
|
||||
if (!id) return this.root
|
||||
const entry = this.resolve(id)
|
||||
if (!entry.subgroup) throw new Error(`entry ${id} is not a group`)
|
||||
return entry.subgroup
|
||||
}
|
||||
|
||||
/** Create an entry in the root group or a nested group. */
|
||||
async create(options: Omit<EntryOptions, 'id'>, parent: string | null = null, position = Infinity) {
|
||||
const group = this.resolveGroup(parent)
|
||||
group.data.splice(position, 0, options as EntryOptions)
|
||||
group.tree.write()
|
||||
return group.create(options)
|
||||
}
|
||||
|
||||
/** Stop and remove an entry from its parent group. */
|
||||
remove(id: string) {
|
||||
const entry = this.resolve(id)
|
||||
entry.parent.remove(id)
|
||||
entry.parent.tree.write()
|
||||
}
|
||||
|
||||
/** Update an entry and optionally move it to another group. */
|
||||
async update(id: string, options: Omit<EntryOptions, 'id' | 'name'>, parent?: string | null, position?: number) {
|
||||
const entry = this.resolve(id)
|
||||
const source = entry.parent
|
||||
if (parent !== undefined) {
|
||||
const target = this.resolveGroup(parent)
|
||||
source.unlink(entry.options)
|
||||
target.data.splice(position ?? Infinity, 0, entry.options)
|
||||
target.tree.write()
|
||||
entry.parent = target
|
||||
}
|
||||
source.tree.write()
|
||||
return entry.update(options, false, true)
|
||||
}
|
||||
|
||||
/** Import a plugin module from a specifier or `cordis:` builtin. */
|
||||
import(name: string, getOuterStack?: () => string[]) {
|
||||
if (name.startsWith('cordis:')) {
|
||||
return this.ctx.loader.builtins[name.slice(7)]
|
||||
}
|
||||
return composeError(async (info) => {
|
||||
// ModuleJob.run
|
||||
// onImport.tracePromise.__proto__
|
||||
// internal.import
|
||||
info.offset += 3
|
||||
if (this.ctx.loader.internal) {
|
||||
return await this.ctx.loader.internal.import(name, this.ctx.baseUrl!, {})
|
||||
} else if (name.startsWith('.')) {
|
||||
return await import(/* @vite-ignore */new URL(name, this.ctx.baseUrl).href)
|
||||
} else {
|
||||
return await import(/* @vite-ignore */name)
|
||||
}
|
||||
}, getOuterStack)
|
||||
}
|
||||
|
||||
/** Persist current tree state. In-memory trees may implement this as a no-op. */
|
||||
abstract write(): void
|
||||
}
|
||||
Reference in New Issue
Block a user