eslint --fix formatting sweep plus the manual residue: sync method bodies drop async behind Promise-returning signatures (the sqlite unit routes primitives through a settle() guard preserving the never-throws- synchronously contract), catch callbacks type their reason as unknown, loadAll's global slot is plain unknown (null semantics stay in JSDoc), a non-null assertion becomes a narrowing, and unsafe any assignments in tests gain explicit types. One justified eslint-disable for prefer-promise-reject-errors follows the core/session precedent — wrapping would discard the original StorageError code.
87 lines
2.9 KiB
TypeScript
87 lines
2.9 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 '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'
|
|
|
|
declare module '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 = 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
|