feat(storage): storage hub with named backend registry and data-form mounts
ctx.storage is a pure registration hub: multiple named backends stay mounted side by side, data forms (domain first) mount via the merge-extensible StorageForms map. src/backend.ts is the normative KV-facet contract; tests/contract.ts is the shared conformance suite every backend runs. Backends expose data-shape facets (kv now, an append-log facet reserved for the future session-backend migration).
This commit is contained in:
86
packages/storage/storage/src/index.ts
Normal file
86
packages/storage/storage/src/index.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* 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 () => {
|
||||
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) as StorageForms extends { domain: infer D } ? D : never
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount the storage hub service.
|
||||
* @param ctx - Plugin context.
|
||||
*/
|
||||
export function apply(ctx: Context) {
|
||||
ctx.plugin(Storage)
|
||||
}
|
||||
Reference in New Issue
Block a user