fix(storage,workspace): review-bot findings — emit isolation, domain ownership, null globals
- domain/changed emission is isolated from the write path: an observer throwing synchronously can no longer turn a committed (durable + in-memory) write into a rejection; the failure is logged and later writes proceed. - Domain lifecycle belongs to the opening consumer: Domain gains an idempotent close() (drain, unit close, reservation release), the facility stops registering effects on its own context and instead closes any still-open domains on unmount; WorkspaceRegistry holds its domain through its own effect, so disposing and re-mounting the consumer no longer wedges on already-open. - defineDomain rejects a global schema accepting null at declaration time: JSON null is the medium's absence sentinel, so a nullable global could never round-trip; failing loud at the spec keeps set(null) unrepresentable. Regression tests cover all three (hostile listener, close/reopen and consumer re-mount, nullable-global rejection).
This commit is contained in:
@@ -84,6 +84,9 @@ export class WorkspaceRegistry extends Service {
|
||||
/** Open the domain and rebuild the entity cache before the service is published as active. */
|
||||
protected async [Service.init](): Promise<void> {
|
||||
const domain = await this.ctx.storage.domain.open(workspaceDomainSpec)
|
||||
// This registry owns the domain handle it opened: closing on fiber
|
||||
// disposal frees the domain name, so a re-plugged registry can reopen it.
|
||||
this.ctx.effect(() => () => domain.close(), 'workspace.domainClose')
|
||||
this.table = domain.table('workspaces')
|
||||
const persistence = this.ctx.get('sessionPersistence')
|
||||
if (persistence !== undefined) {
|
||||
|
||||
@@ -185,6 +185,24 @@ describe('WorkspaceRegistry.create', () => {
|
||||
const registry = new WorkspaceRegistry(ctx)
|
||||
await expect(registry.create(dir)).rejects.toThrow(/not started/)
|
||||
})
|
||||
|
||||
it('closes its domain on fiber disposal so a re-plugged registry reopens it', async () => {
|
||||
const dir = await makeDir('replug')
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(Storage)
|
||||
ctx.storage.backend.register('memory', new MemoryStorageBackend())
|
||||
ctx.storage.mount('domain', new DomainFacility(ctx, { backend: 'memory', routes: {} }))
|
||||
const fiber = ctx.plugin(WorkspaceRegistry)
|
||||
await fiber
|
||||
const first = await ctx.workspace.create(dir)
|
||||
await fiber.dispose()
|
||||
// The registry's effect closed the domain, freeing the name: a second
|
||||
// plugin of the same registry must reopen it (not already-open) and see
|
||||
// the durable record.
|
||||
await ctx.plugin(WorkspaceRegistry)
|
||||
const reloaded = await ctx.workspace.resolveByPath(dir)
|
||||
expect(reloaded?.id).toBe(first.id)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Workspace.attachSession', () => {
|
||||
|
||||
Reference in New Issue
Block a user