chore(storage,workspace): gates — coverage, catalogs, bilingual note
- Per-file 100% coverage across the five new packages (invariant companion suites, failure-injection negatives, lifecycle and malformed-medium branches). - Canonical README Model Experience / Known Limitations sections; new storage/ and workspace/ group READMEs; packages/README.md rows (budget ceiling raised 760 → 790 for the two new groups). - Cordis catalog/type-link registrations, service-role classification, and regenerated catalogs/graphs for the new services and events. - Agent Note: English body + i18n pairing record; design-sketch fences opted out of doc-typecheck as ignore-check. - Two exactOptionalPropertyTypes/discriminant fixes in new tests. doc-sync (24 gates), typecheck, hygiene, and the five-package suite (92 tests) all pass.
This commit is contained in:
@@ -76,6 +76,35 @@ describe('DomainFacility.open', () => {
|
||||
await expect(facility.open(spec)).rejects.toMatchObject({ code: 'facet-unsupported' })
|
||||
})
|
||||
|
||||
it('falls back to the default backend when no route table is configured', async () => {
|
||||
// A second, unmounted facility whose config omits `routes` entirely
|
||||
// (exactOptionalPropertyTypes forbids an explicit undefined). Opening
|
||||
// emits no events, so the mounted facility's invariant never consults it.
|
||||
const { ctx } = await harness()
|
||||
const routeless = new DomainFacility(ctx, { backend: 'memory' })
|
||||
await expect(routeless.open(bareSpec)).resolves.toBeDefined()
|
||||
})
|
||||
|
||||
it('treats a table key the backend omitted from loadAll as empty', async () => {
|
||||
// A sparse backend: loadAll omits declared table keys entirely instead of
|
||||
// returning them as empty objects.
|
||||
const { ctx, facility } = await harness({ config: { backend: 'sparse' } })
|
||||
ctx.storage.backend.register('sparse', {
|
||||
kv: {
|
||||
open: async () => ({
|
||||
loadAll: async () => ({ tables: {}, global: null }),
|
||||
putRecord: async () => {},
|
||||
deleteRecord: async () => {},
|
||||
setGlobal: async () => {},
|
||||
close: async () => {},
|
||||
}),
|
||||
},
|
||||
close: async () => {},
|
||||
})
|
||||
const domain = await facility.open(bareSpec)
|
||||
expect(domain.table('rows').size).toBe(0)
|
||||
})
|
||||
|
||||
it('rejects stored records that fail their schema, naming table and key', async () => {
|
||||
const pool = new MemoryMediaPool()
|
||||
{
|
||||
@@ -112,6 +141,33 @@ describe('DomainFacility.open', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('plugin apply', () => {
|
||||
it('mounts the facility as ctx.storage.domain through the plugin effect', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(Storage)
|
||||
ctx.storage.backend.register('memory', new MemoryStorageBackend())
|
||||
const DomainPlugin = await import('../src/index.ts')
|
||||
const fiber = await ctx.plugin(DomainPlugin, { backend: 'memory' })
|
||||
expect(ctx.storage.domain).toBeInstanceOf(DomainFacility)
|
||||
await fiber.dispose()
|
||||
expect(() => ctx.storage.form('domain')).toThrow(/not mounted/)
|
||||
})
|
||||
})
|
||||
|
||||
describe('table and snapshot reads', () => {
|
||||
it('serves entries, keys, and size as stable snapshots; unknown table names throw', async () => {
|
||||
const { facility } = await harness()
|
||||
const domain = await facility.open(spec)
|
||||
const table = domain.table('items')
|
||||
await table.put('a', { label: 'x', count: 1 })
|
||||
await table.put('b', { label: 'y', count: 2 })
|
||||
expect(table.size).toBe(2)
|
||||
expect([...table.keys()].sort()).toEqual(['a', 'b'])
|
||||
expect(new Map(table.entries()).get('a')).toEqual({ label: 'x', count: 1 })
|
||||
expect(() => domain.table('nope' as never)).toThrow(/declares no table/)
|
||||
})
|
||||
})
|
||||
|
||||
describe('KvTable writes', () => {
|
||||
it('serializes concurrent updates on one key without losing increments', async () => {
|
||||
const { facility } = await harness()
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { Context } from 'cordis'
|
||||
import { z } from 'zod'
|
||||
import Storage from '@deepseek-ai/dsh-storage'
|
||||
import InvariantService, { InvariantError } from '@deepseek-ai/dsh-invariants'
|
||||
import * as DomainInvariantCompanion from '@deepseek-ai/dsh-domain/invariant'
|
||||
import { DomainFacility, defineDomain, domainTable } from '../src/index.ts'
|
||||
import type { DomainChanged } from '../src/events.ts'
|
||||
import { MemoryStorageBackend } from './helpers/memory-backend.ts'
|
||||
|
||||
const itemSchema = z.object({ n: z.number() })
|
||||
type Item = z.infer<typeof itemSchema>
|
||||
|
||||
const spec = defineDomain({
|
||||
name: 'inv',
|
||||
version: 1,
|
||||
global: { schema: itemSchema, initial: { n: 0 } },
|
||||
tables: { rows: domainTable<string, Item>(itemSchema) },
|
||||
})
|
||||
|
||||
async function setup() {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(Storage)
|
||||
await ctx.plugin(InvariantService, { enabled: true })
|
||||
await ctx.plugin(DomainInvariantCompanion)
|
||||
ctx.storage.backend.register('memory', new MemoryStorageBackend())
|
||||
const facility = new DomainFacility(ctx, { backend: 'memory', routes: {} })
|
||||
ctx.storage.mount('domain', facility)
|
||||
return { ctx, facility }
|
||||
}
|
||||
|
||||
const invariantViolation = expect.objectContaining<Partial<InvariantError>>({
|
||||
code: 'INVARIANT',
|
||||
packageName: '@deepseek-ai/dsh-domain',
|
||||
})
|
||||
|
||||
describe('domain change-event invariants', () => {
|
||||
it('accepts every write shape emitted by the real write paths', async () => {
|
||||
const { facility } = await setup()
|
||||
const domain = await facility.open(spec)
|
||||
const rows = domain.table('rows')
|
||||
await rows.put('a', { n: 1 })
|
||||
await rows.update('a', (current) => ({ n: current.n + 1 }))
|
||||
await expect(rows.delete('a')).resolves.toBe(true)
|
||||
await domain.global.set({ n: 5 })
|
||||
})
|
||||
|
||||
it('rejects an event for a domain that is not open', async () => {
|
||||
const { ctx } = await setup()
|
||||
expect(() => ctx.emit('domain/changed', {
|
||||
domain: 'ghost', table: 'rows', key: 'a', operation: 'put', value: { n: 1 },
|
||||
})).toThrow(invariantViolation)
|
||||
})
|
||||
|
||||
it('rejects a put event whose value is not the in-memory record', async () => {
|
||||
const { ctx, facility } = await setup()
|
||||
const domain = await facility.open(spec)
|
||||
await domain.table('rows').put('a', { n: 1 })
|
||||
expect(() => ctx.emit('domain/changed', {
|
||||
domain: 'inv', table: 'rows', key: 'a', operation: 'put', value: { n: 999 },
|
||||
})).toThrow(invariantViolation)
|
||||
})
|
||||
|
||||
it('rejects a deletion event while the record is still in memory', async () => {
|
||||
const { ctx, facility } = await setup()
|
||||
const domain = await facility.open(spec)
|
||||
await domain.table('rows').put('a', { n: 1 })
|
||||
expect(() => ctx.emit('domain/changed', {
|
||||
domain: 'inv', table: 'rows', key: 'a', operation: 'deleted',
|
||||
})).toThrow(invariantViolation)
|
||||
})
|
||||
|
||||
it('rejects a global event whose value is not the in-memory global', async () => {
|
||||
const { ctx, facility } = await setup()
|
||||
await facility.open(spec)
|
||||
expect(() => ctx.emit('domain/changed', {
|
||||
domain: 'inv', table: '', key: '', operation: 'put', value: { n: 42 },
|
||||
})).toThrow(invariantViolation)
|
||||
})
|
||||
|
||||
it('tolerates operations outside the closed union without failing falsely', async () => {
|
||||
const { ctx, facility } = await setup()
|
||||
const domain = await facility.open(spec)
|
||||
await domain.table('rows').put('a', { n: 1 })
|
||||
// Merge-hostile input: the closed union's satisfies-never default arm is
|
||||
// unreachable in typed code; an untyped emit must not crash the check.
|
||||
expect(() => ctx.emit('domain/changed', {
|
||||
domain: 'inv', table: 'rows', key: 'a', operation: 'exotic',
|
||||
} as unknown as DomainChanged)).not.toThrow()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user