Machine-produced by `pnpm run rescope-vendor --apply` plus the regeneration it prints: `pnpm install` for the lockfile, `pnpm run gen-third-party-notices`, `verify-translation-pairing --write` for the touched bilingual pairs, `gen-doc-graphs`, and one typert snapshot whose ids embed character offsets. `pnpm run rescope-vendor --check` verifies the result. Renames nine vendored packages (cordis, cosmokit, schemastery and the six @cordisjs plugins) and every reference that resolves them: manifest names and dependency keys, module specifiers including declare-module merges, cordis.yml plugin names, tsconfig paths, every Markdown fence, and `docs/` prose. Directory names, upstream versions, and dependency ranges are unchanged, so vendor/README.md still reads as an upstream snapshot; its manifest table gains an upstream-name column so THIRD_PARTY_NOTICES keeps MIT attribution pointed at each fork's origin. The tutorial tier follows the rename end to end: its yaml fences named plugins the Loader can no longer resolve, its `ts ignore-check` fences disagreed with the compiled fences beside them, and its prose quoted both. The contracts that told readers to keep upstream names — the root convention and the vendoring cookbook's tree comment and manifest invariant — now say to rescope instead. Two rules read `@deepseek-ai/` as "another workspace plugin": the client bundle purity gate now names the vendored libraries a browser bundle inlines, and the files where a bare `cordis` is an agent-preset id keep that product data.
99 lines
3.4 KiB
TypeScript
99 lines
3.4 KiB
TypeScript
/**
|
|
* Storage hub (`ctx.storage`): a named backend registry plus mounted
|
|
* data-form facilities. The hub itself performs no IO — backends own media,
|
|
* data forms (the domain layer first) own semantics.
|
|
* @module @deepseek-ai/dsh-storage
|
|
*/
|
|
|
|
import { Context, Service } from '@deepseek-ai/cordis'
|
|
import { StorageError } from './error.ts'
|
|
import { BackendRegistry } from './registry.ts'
|
|
|
|
export { BackendRegistry } from './registry.ts'
|
|
export { StorageError } from './error.ts'
|
|
export type { StorageErrorCode } from './error.ts'
|
|
export { UNIT_NAME_RE } from './backend.ts'
|
|
export type { StorageBackend, KvFacet, KvUnit, KvUnitDescriptor } from './backend.ts'
|
|
|
|
/**
|
|
* Derive the Cordis lifecycle service that one named backend plugin provides.
|
|
* Domain-form providers inject these keys so activation cannot race backend
|
|
* registration even though callers continue resolving backends through the
|
|
* storage registry.
|
|
* @param name - Backend registry name.
|
|
* @returns the corresponding lifecycle-only service key.
|
|
*/
|
|
export function storageBackendServiceKey(name: string): string {
|
|
return `storage.backend.${name}`
|
|
}
|
|
|
|
declare module '@deepseek-ai/cordis' {
|
|
interface Context {
|
|
storage: Storage
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Data forms mountable on the hub, keyed by form name. Form owners extend
|
|
* this map via declaration merging (the domain layer merges
|
|
* `domain: DomainFacility`) and mount the facility in their `apply`.
|
|
*/
|
|
export interface StorageForms {}
|
|
|
|
/**
|
|
* The storage hub service. Backends register under `backend`; data forms
|
|
* mount under their `StorageForms` key and are reached as `ctx.storage.<form>`.
|
|
*/
|
|
export class Storage extends Service {
|
|
/** Named backend table; multiple backends stay mounted side by side. */
|
|
readonly backend: BackendRegistry = new BackendRegistry()
|
|
|
|
private readonly forms = new Map<keyof StorageForms, unknown>()
|
|
|
|
constructor(ctx: Context) {
|
|
super(ctx, 'storage')
|
|
}
|
|
|
|
/**
|
|
* Mount a data-form facility on the hub. Mounting is an effect: the
|
|
* returned disposer unmounts the form.
|
|
* @param form - Form key declared in {@link StorageForms}.
|
|
* @param facility - The facility instance to expose.
|
|
* @returns the disposer that unmounts the form.
|
|
*/
|
|
mount<K extends keyof StorageForms>(form: K, facility: StorageForms[K]): () => void {
|
|
if (this.forms.has(form)) {
|
|
throw new StorageError('duplicate-mount', `storage form '${String(form)}' is already mounted`)
|
|
}
|
|
this.forms.set(form, facility)
|
|
return () => {
|
|
// Same stale-disposer guard as BackendRegistry.register.
|
|
if (this.forms.get(form) === facility) {
|
|
this.forms.delete(form)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resolve a mounted data form.
|
|
* @param form - Form key declared in {@link StorageForms}.
|
|
* @returns the mounted facility.
|
|
*/
|
|
form<K extends keyof StorageForms>(form: K): StorageForms[K] {
|
|
if (!this.forms.has(form)) {
|
|
throw new StorageError('form-not-mounted', `storage form '${String(form)}' is not mounted`)
|
|
}
|
|
return this.forms.get(form) as StorageForms[K]
|
|
}
|
|
|
|
/** Domain data form; present once the domain layer plugin is loaded. */
|
|
get domain(): StorageForms extends { domain: infer D } ? D : never {
|
|
return this.form('domain' as keyof StorageForms)
|
|
}
|
|
}
|
|
|
|
// Service packages default-export their service class and nothing else
|
|
// plugin-shaped (packages/AGENTS.md): mixing a default export with a
|
|
// function-plugin `apply` makes the Loader drop the plugin namespace.
|
|
export default Storage
|