style(storage,workspace): satisfy the repository lint gate

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.
This commit is contained in:
imccyu
2026-07-24 23:41:39 +08:00
parent 25a4a2063b
commit 2e986ee1e3
14 changed files with 113 additions and 78 deletions
+4 -4
View File
@@ -164,9 +164,9 @@ export class DomainImpl {
const host: TableHost = { const host: TableHost = {
domainName: spec.name, domainName: spec.name,
unit, unit,
enqueue: (job) => this.enqueue(job), enqueue: job => this.enqueue(job),
assertReadable: () => this.assertReadable(), assertReadable: () => { this.assertReadable() },
emitChanged: (change) => this.ctx.emit('domain/changed', change), emitChanged: (change) => { this.ctx.emit('domain/changed', change) },
} }
for (const [table, tableRecords] of records) { for (const [table, tableRecords] of records) {
this.tables.set(table, new KvTableImpl(host, table, tableRecords)) this.tables.set(table, new KvTableImpl(host, table, tableRecords))
@@ -178,7 +178,7 @@ export class DomainImpl {
this.assertReadable() this.assertReadable()
return this.globalValue return this.globalValue
}, },
set: (value) => this.enqueue(async () => { set: value => this.enqueue(async () => {
await this.unit.setGlobal(value) await this.unit.setGlobal(value)
this.globalValue = value this.globalValue = value
host.emitChanged({ domain: this.name, table: '', key: '', operation: 'put', value }) host.emitChanged({ domain: this.name, table: '', key: '', operation: 'put', value })
+4 -3
View File
@@ -113,11 +113,12 @@ export class DomainFacility {
} }
// A null stored global means "never written": serve `initial` without // A null stored global means "never written": serve `initial` without
// materializing it — the first `set` writes. // materializing it — the first `set` writes.
const globalValue = spec.global === undefined const globalSpec = spec.global
const globalValue = globalSpec === undefined
? undefined ? undefined
: snapshot.global === null : snapshot.global === null
? spec.global.initial ? globalSpec.initial
: parseRecord(spec.name, '', '', () => spec.global!.schema.parse(snapshot.global)) : parseRecord(spec.name, '', '', () => globalSpec.schema.parse(snapshot.global))
const domain = new DomainImpl(this.ctx, spec, unit, tables, globalValue) const domain = new DomainImpl(this.ctx, spec, unit, tables, globalValue)
// The open-domain table entry is itself the effect: registration and // The open-domain table entry is itself the effect: registration and
// the drain-then-unlist teardown live in one closure. // the drain-then-unlist teardown live in one closure.
+1 -1
View File
@@ -45,7 +45,7 @@ export interface DomainSpec {
/** Key type of one declared table, recovered from its phantom carrier. */ /** Key type of one declared table, recovered from its phantom carrier. */
export type TableKeyOf<S extends DomainSpec, N extends keyof S['tables']> = export type TableKeyOf<S extends DomainSpec, N extends keyof S['tables']> =
S['tables'][N] extends DomainTableSpec<infer K, unknown> ? K : never S['tables'][N] extends DomainTableSpec<infer K> ? K : never
/** Value type of one declared table. */ /** Value type of one declared table. */
export type TableValueOf<S extends DomainSpec, N extends keyof S['tables']> = export type TableValueOf<S extends DomainSpec, N extends keyof S['tables']> =
+5 -5
View File
@@ -174,14 +174,14 @@ describe('KvTable writes', () => {
const table = (await facility.open(spec)).table('items') const table = (await facility.open(spec)).table('items')
await table.put('counter', { label: 'c', count: 0 }) await table.put('counter', { label: 'c', count: 0 })
await Promise.all(Array.from({ length: 50 }, () => await Promise.all(Array.from({ length: 50 }, () =>
table.update('counter', (current) => ({ ...current, count: current.count + 1 })))) table.update('counter', current => ({ ...current, count: current.count + 1 }))))
expect(table.get('counter')).toEqual({ label: 'c', count: 50 }) expect(table.get('counter')).toEqual({ label: 'c', count: 50 })
}) })
it('update rejects a missing key; delete reports prior existence', async () => { it('update rejects a missing key; delete reports prior existence', async () => {
const { facility } = await harness() const { facility } = await harness()
const table = (await facility.open(spec)).table('items') const table = (await facility.open(spec)).table('items')
await expect(table.update('ghost', (v) => v)).rejects.toMatchObject({ code: 'missing-key' }) await expect(table.update('ghost', v => v)).rejects.toMatchObject({ code: 'missing-key' })
await table.put('a', { label: 'x', count: 1 }) await table.put('a', { label: 'x', count: 1 })
await expect(table.delete('a')).resolves.toBe(true) await expect(table.delete('a')).resolves.toBe(true)
await expect(table.delete('a')).resolves.toBe(false) await expect(table.delete('a')).resolves.toBe(false)
@@ -192,7 +192,7 @@ describe('KvTable writes', () => {
const domain = await facility.open(spec) const domain = await facility.open(spec)
const table = domain.table('items') const table = domain.table('items')
await table.put('a', { label: 'x', count: 1 }) await table.put('a', { label: 'x', count: 1 })
await table.update('a', (current) => ({ ...current, count: 2 })) await table.update('a', current => ({ ...current, count: 2 }))
await table.delete('a') await table.delete('a')
await table.delete('a') // no event: already absent await table.delete('a') // no event: already absent
await domain.global.set({ theme: 'dark' }) await domain.global.set({ theme: 'dark' })
@@ -216,7 +216,7 @@ describe('durability failure', () => {
pool.failNextWrites = 3 pool.failNextWrites = 3
await expect(table.put('a', { label: 'x', count: 99 })).rejects.toThrow(/injected/) await expect(table.put('a', { label: 'x', count: 99 })).rejects.toThrow(/injected/)
await expect(table.update('a', (c) => ({ ...c, count: c.count + 1 }))).rejects.toThrow(/injected/) await expect(table.update('a', c => ({ ...c, count: c.count + 1 }))).rejects.toThrow(/injected/)
await expect(table.delete('a')).rejects.toThrow(/injected/) await expect(table.delete('a')).rejects.toThrow(/injected/)
// Reads still serve the pre-failure record; no events leaked. // Reads still serve the pre-failure record; no events leaked.
@@ -225,7 +225,7 @@ describe('durability failure', () => {
expect(changes).toHaveLength(seen) expect(changes).toHaveLength(seen)
// The chain survives rejections: the next write lands cleanly with no residue. // The chain survives rejections: the next write lands cleanly with no residue.
await table.update('a', (c) => ({ ...c, count: c.count + 1 })) await table.update('a', c => ({ ...c, count: c.count + 1 }))
expect(table.get('a')).toEqual({ label: 'x', count: 2 }) expect(table.get('a')).toEqual({ label: 'x', count: 2 })
}) })
@@ -18,10 +18,10 @@
import { StorageError } from '@deepseek-ai/dsh-storage' import { StorageError } from '@deepseek-ai/dsh-storage'
import type { KvFacet, KvUnit, KvUnitDescriptor, StorageBackend } from '@deepseek-ai/dsh-storage' import type { KvFacet, KvUnit, KvUnitDescriptor, StorageBackend } from '@deepseek-ai/dsh-storage'
/** One unit's medium: tables of records plus the global slot. */ /** One unit's medium: tables of records plus the global slot (`null` = never written). */
export interface MemoryMedium { export interface MemoryMedium {
tables: Map<string, Map<string, unknown>> tables: Map<string, Map<string, unknown>>
global: unknown | null global: unknown
} }
/** /**
@@ -70,7 +70,7 @@ class MemoryKvUnit implements KvUnit {
} }
} }
async loadAll(): Promise<{ tables: Record<string, Record<string, unknown>>; global: unknown | null }> { async loadAll(): Promise<{ tables: Record<string, Record<string, unknown>>; global: unknown }> {
this.assertOpen() this.assertOpen()
const tables: Record<string, Record<string, unknown>> = {} const tables: Record<string, Record<string, unknown>> = {}
for (const table of this.descriptor.tables) { for (const table of this.descriptor.tables) {
+12 -12
View File
@@ -29,7 +29,7 @@ async function setup() {
return { ctx, facility } return { ctx, facility }
} }
const invariantViolation = expect.objectContaining<Partial<InvariantError>>({ const invariantViolation: unknown = expect.objectContaining<Partial<InvariantError>>({
code: 'INVARIANT', code: 'INVARIANT',
packageName: '@deepseek-ai/dsh-domain', packageName: '@deepseek-ai/dsh-domain',
}) })
@@ -40,42 +40,42 @@ describe('domain change-event invariants', () => {
const domain = await facility.open(spec) const domain = await facility.open(spec)
const rows = domain.table('rows') const rows = domain.table('rows')
await rows.put('a', { n: 1 }) await rows.put('a', { n: 1 })
await rows.update('a', (current) => ({ n: current.n + 1 })) await rows.update('a', current => ({ n: current.n + 1 }))
await expect(rows.delete('a')).resolves.toBe(true) await expect(rows.delete('a')).resolves.toBe(true)
await domain.global.set({ n: 5 }) await domain.global.set({ n: 5 })
}) })
it('rejects an event for a domain that is not open', async () => { it('rejects an event for a domain that is not open', async () => {
const { ctx } = await setup() const { ctx } = await setup()
expect(() => ctx.emit('domain/changed', { expect(() => { ctx.emit('domain/changed', {
domain: 'ghost', table: 'rows', key: 'a', operation: 'put', value: { n: 1 }, domain: 'ghost', table: 'rows', key: 'a', operation: 'put', value: { n: 1 },
})).toThrow(invariantViolation) }) }).toThrow(invariantViolation)
}) })
it('rejects a put event whose value is not the in-memory record', async () => { it('rejects a put event whose value is not the in-memory record', async () => {
const { ctx, facility } = await setup() const { ctx, facility } = await setup()
const domain = await facility.open(spec) const domain = await facility.open(spec)
await domain.table('rows').put('a', { n: 1 }) await domain.table('rows').put('a', { n: 1 })
expect(() => ctx.emit('domain/changed', { expect(() => { ctx.emit('domain/changed', {
domain: 'inv', table: 'rows', key: 'a', operation: 'put', value: { n: 999 }, domain: 'inv', table: 'rows', key: 'a', operation: 'put', value: { n: 999 },
})).toThrow(invariantViolation) }) }).toThrow(invariantViolation)
}) })
it('rejects a deletion event while the record is still in memory', async () => { it('rejects a deletion event while the record is still in memory', async () => {
const { ctx, facility } = await setup() const { ctx, facility } = await setup()
const domain = await facility.open(spec) const domain = await facility.open(spec)
await domain.table('rows').put('a', { n: 1 }) await domain.table('rows').put('a', { n: 1 })
expect(() => ctx.emit('domain/changed', { expect(() => { ctx.emit('domain/changed', {
domain: 'inv', table: 'rows', key: 'a', operation: 'deleted', domain: 'inv', table: 'rows', key: 'a', operation: 'deleted',
})).toThrow(invariantViolation) }) }).toThrow(invariantViolation)
}) })
it('rejects a global event whose value is not the in-memory global', async () => { it('rejects a global event whose value is not the in-memory global', async () => {
const { ctx, facility } = await setup() const { ctx, facility } = await setup()
await facility.open(spec) await facility.open(spec)
expect(() => ctx.emit('domain/changed', { expect(() => { ctx.emit('domain/changed', {
domain: 'inv', table: '', key: '', operation: 'put', value: { n: 42 }, domain: 'inv', table: '', key: '', operation: 'put', value: { n: 42 },
})).toThrow(invariantViolation) }) }).toThrow(invariantViolation)
}) })
it('tolerates operations outside the closed union without failing falsely', async () => { it('tolerates operations outside the closed union without failing falsely', async () => {
@@ -84,8 +84,8 @@ describe('domain change-event invariants', () => {
await domain.table('rows').put('a', { n: 1 }) await domain.table('rows').put('a', { n: 1 })
// Merge-hostile input: the closed union's satisfies-never default arm is // Merge-hostile input: the closed union's satisfies-never default arm is
// unreachable in typed code; an untyped emit must not crash the check. // unreachable in typed code; an untyped emit must not crash the check.
expect(() => ctx.emit('domain/changed', { expect(() => { ctx.emit('domain/changed', {
domain: 'inv', table: 'rows', key: 'a', operation: 'exotic', domain: 'inv', table: 'rows', key: 'a', operation: 'exotic',
} as unknown as DomainChanged)).not.toThrow() } as unknown as DomainChanged) }).not.toThrow()
}) })
}) })
+2 -2
View File
@@ -8,10 +8,10 @@
import { StorageError } from '@deepseek-ai/dsh-storage' import { StorageError } from '@deepseek-ai/dsh-storage'
import type { KvUnitDescriptor } from '@deepseek-ai/dsh-storage' import type { KvUnitDescriptor } from '@deepseek-ai/dsh-storage'
/** In-memory authoritative state of one unit; the file is its projection. */ /** In-memory authoritative state of one unit; the file is its projection. `global` is `null` until first written. */
export interface UnitState { export interface UnitState {
version: number version: number
global: unknown | null global: unknown
tables: Map<string, Map<string, unknown>> tables: Map<string, Map<string, unknown>>
} }
+9 -9
View File
@@ -36,10 +36,10 @@ export async function openJsonUnit(
const state: UnitState = const state: UnitState =
text === undefined text === undefined
? { ? {
version: descriptor.version, version: descriptor.version,
global: null, global: null,
tables: new Map(descriptor.tables.map((table) => [table, new Map()])), tables: new Map(descriptor.tables.map(table => [table, new Map<string, unknown>()])),
} }
: parse(text, descriptor) : parse(text, descriptor)
return new JsonKvUnit(descriptor, path, state, onClose) return new JsonKvUnit(descriptor, path, state, onClose)
} }
@@ -56,13 +56,13 @@ class JsonKvUnit implements KvUnit {
private readonly onClose: () => void, private readonly onClose: () => void,
) {} ) {}
async loadAll(): Promise<{ tables: Record<string, Record<string, unknown>>; global: unknown | null }> { loadAll(): Promise<{ tables: Record<string, Record<string, unknown>>; global: unknown }> {
this.assertOpen() this.assertOpen()
const tables: Record<string, Record<string, unknown>> = {} const tables: Record<string, Record<string, unknown>> = {}
for (const [table, records] of this.state.tables) { for (const [table, records] of this.state.tables) {
tables[table] = Object.fromEntries(records) tables[table] = Object.fromEntries(records)
} }
return { tables, global: this.state.global } return Promise.resolve({ tables, global: this.state.global })
} }
async putRecord(table: string, key: string, value: unknown): Promise<void> { async putRecord(table: string, key: string, value: unknown): Promise<void> {
@@ -73,7 +73,7 @@ class JsonKvUnit implements KvUnit {
records.set(key, value) records.set(key, value)
// Roll back on a failed publish: memory is authoritative, so a rejected // Roll back on a failed publish: memory is authoritative, so a rejected
// write must not survive in memory (or ride along with the next publish). // write must not survive in memory (or ride along with the next publish).
await this.publish().catch(async (error) => { await this.publish().catch((error: unknown) => {
if (hadKey) records.set(key, previous) if (hadKey) records.set(key, previous)
else records.delete(key) else records.delete(key)
throw error throw error
@@ -86,7 +86,7 @@ class JsonKvUnit implements KvUnit {
if (!records.has(key)) return if (!records.has(key)) return
const previous = records.get(key) const previous = records.get(key)
records.delete(key) records.delete(key)
await this.publish().catch(async (error) => { await this.publish().catch((error: unknown) => {
records.set(key, previous) records.set(key, previous)
throw error throw error
}) })
@@ -99,7 +99,7 @@ class JsonKvUnit implements KvUnit {
} }
const previous = this.state.global const previous = this.state.global
this.state.global = value this.state.global = value
await this.publish().catch(async (error) => { await this.publish().catch((error: unknown) => {
this.state.global = previous this.state.global = previous
throw error throw error
}) })
@@ -78,7 +78,7 @@ describe('json backend specifics', () => {
const root = await freshRoot() const root = await freshRoot()
const backend = new JsonStorageBackend(root) const backend = new JsonStorageBackend(root)
await backend.kv.open(descriptor) await backend.kv.open(descriptor)
await expect(backend.kv.open(descriptor)).rejects.toThrowError(/already open/) await expect(backend.kv.open(descriptor)).rejects.toThrow(/already open/)
await backend.close() await backend.close()
}) })
@@ -208,13 +208,15 @@ describe('json backend specifics', () => {
const bigWrite = unit.putRecord('t', 'big', { blob: 'x'.repeat(4 * 1024 * 1024) }) const bigWrite = unit.putRecord('t', 'big', { blob: 'x'.repeat(4 * 1024 * 1024) })
await unit.close() await unit.close()
await expect(bigWrite).resolves.toBeUndefined() await expect(bigWrite).resolves.toBeUndefined()
const onDisk = JSON.parse(await readFile(join(root, 'shape.json'), 'utf8')) const onDisk = JSON.parse(await readFile(join(root, 'shape.json'), 'utf8')) as {
expect(onDisk.tables.t.big).toBeDefined() tables: Record<string, Record<string, unknown>>
}
expect(onDisk.tables['t']?.['big']).toBeDefined()
const backend2 = new JsonStorageBackend(root) const backend2 = new JsonStorageBackend(root)
const opening = backend2.kv.open(descriptor) const opening = backend2.kv.open(descriptor)
const closing = backend2.close() const closing = backend2.close()
await expect(opening.then((u) => u.putRecord('t', 'x', {}))).rejects.toMatchObject({ code: 'closed' }) await expect(opening.then(u => u.putRecord('t', 'x', {}))).rejects.toMatchObject({ code: 'closed' })
await closing await closing
}) })
}) })
+53 -32
View File
@@ -62,24 +62,25 @@ export class SqliteKvUnit implements KvUnit {
: undefined : undefined
} }
async loadAll(): Promise<{ tables: Record<string, Record<string, unknown>>; global: unknown | null }> { loadAll(): Promise<{ tables: Record<string, Record<string, unknown>>; global: unknown }> {
this.ensureOpen() return this.settle(() => {
const tables: Record<string, Record<string, unknown>> = {} const tables: Record<string, Record<string, unknown>> = {}
for (const [name, statements] of this.tables) { for (const [name, statements] of this.tables) {
// Null prototype: record keys are arbitrary strings, so '__proto__' // Null prototype: record keys are arbitrary strings, so '__proto__'
// must land as an own property instead of mutating the prototype. // must land as an own property instead of mutating the prototype.
const records: Record<string, unknown> = Object.create(null) as Record<string, unknown> const records: Record<string, unknown> = Object.create(null) as Record<string, unknown>
for (const row of statements.selectAll.all() as unknown as Array<{ key: string; value: string }>) { for (const row of statements.selectAll.all() as unknown as Array<{ key: string; value: string }>) {
records[row.key] = this.parseValue(row.value, `table '${name}' key '${row.key}'`) records[row.key] = this.parseValue(row.value, `table '${name}' key '${row.key}'`)
}
tables[name] = records
} }
tables[name] = records let global: unknown = null
} if (this.globalSelect !== undefined) {
let global: unknown = null const row = this.globalSelect.get(this.descriptor.name) as { value: string } | undefined
if (this.globalSelect !== undefined) { if (row !== undefined) global = this.parseValue(row.value, 'global slot')
const row = this.globalSelect.get(this.descriptor.name) as { value: string } | undefined }
if (row !== undefined) global = this.parseValue(row.value, 'global slot') return { tables, global }
} })
return { tables, global }
} }
/** Parse one stored value column, mapping bad JSON to `malformed-medium`. */ /** Parse one stored value column, mapping bad JSON to `malformed-medium`. */
@@ -95,28 +96,48 @@ export class SqliteKvUnit implements KvUnit {
} }
} }
async putRecord(table: string, key: string, value: unknown): Promise<void> { putRecord(table: string, key: string, value: unknown): Promise<void> {
this.ensureOpen() return this.settle(() => {
this.statementsFor(table).upsert.run(key, JSON.stringify(value)) this.statementsFor(table).upsert.run(key, JSON.stringify(value))
})
} }
async deleteRecord(table: string, key: string): Promise<void> { deleteRecord(table: string, key: string): Promise<void> {
this.ensureOpen() return this.settle(() => {
this.statementsFor(table).remove.run(key) this.statementsFor(table).remove.run(key)
})
} }
async setGlobal(value: unknown): Promise<void> { setGlobal(value: unknown): Promise<void> {
this.ensureOpen() return this.settle(() => {
if (this.globalUpsert === undefined) { if (this.globalUpsert === undefined) {
throw new Error(`kv unit '${this.descriptor.name}' declared no global slot`) throw new Error(`kv unit '${this.descriptor.name}' declared no global slot`)
}
this.globalUpsert.run(this.descriptor.name, JSON.stringify(value))
})
}
close(): Promise<void> {
if (!this.closed) {
this.closed = true
this.onClose()
} }
this.globalUpsert.run(this.descriptor.name, JSON.stringify(value)) return Promise.resolve()
} }
async close(): Promise<void> { /**
if (this.closed) return * Run one synchronous primitive behind the closed guard, mapping a throw to
this.closed = true * a rejection so the Promise-returning contract never throws synchronously.
this.onClose() */
private settle<T>(operation: () => T): Promise<T> {
try {
this.ensureOpen()
return Promise.resolve(operation())
} catch (error) {
// Non-Error throws can only enter through JSON.stringify propagating a
// value's own toJSON throw; wrap those, preserve every real Error.
return Promise.reject(error instanceof Error ? error : new Error(String(error)))
}
} }
private ensureOpen(): void { private ensureOpen(): void {
@@ -171,6 +171,17 @@ describe('sqlite backend specifics', () => {
await reopened.close() await reopened.close()
}) })
it('wraps a non-Error toJSON throw into an Error rejection', async () => {
const backend = backendAt(':memory:')
const unit = await backend.kv.open(DESCRIPTOR)
// JSON.stringify propagates a value's own toJSON throw verbatim; the unit
// must still reject with an Error instance.
const hostile = { toJSON: () => { throw 'not an error' } }
await expect(unit.putRecord('records', 'k', hostile)).rejects.toThrow('not an error')
await expect(unit.putRecord('records', 'k', hostile)).rejects.toBeInstanceOf(Error)
await backend.close()
})
it('rejects setGlobal on a unit without a global slot and writes to undeclared tables', async () => { it('rejects setGlobal on a unit without a global slot and writes to undeclared tables', async () => {
const backend = backendAt(':memory:') const backend = backendAt(':memory:')
const unit = await backend.kv.open({ ...DESCRIPTOR, hasGlobal: false }) const unit = await backend.kv.open({ ...DESCRIPTOR, hasGlobal: false })
+1 -1
View File
@@ -69,7 +69,7 @@ export interface KvUnit {
* @returns every table's records keyed by table name, plus the global * @returns every table's records keyed by table name, plus the global
* singleton (`null` when never written or not declared). * singleton (`null` when never written or not declared).
*/ */
loadAll(): Promise<{ tables: Record<string, Record<string, unknown>>; global: unknown | null }> loadAll(): Promise<{ tables: Record<string, Record<string, unknown>>; global: unknown }>
/** /**
* Upsert one record durably. Overwrite semantics: an existing key is replaced. * Upsert one record durably. Overwrite semantics: an existing key is replaced.
+1 -1
View File
@@ -76,7 +76,7 @@ export class Storage extends Service {
/** Domain data form; present once the domain layer plugin is loaded. */ /** Domain data form; present once the domain layer plugin is loaded. */
get domain(): StorageForms extends { domain: infer D } ? D : never { get domain(): StorageForms extends { domain: infer D } ? D : never {
return this.form('domain' as keyof StorageForms) as StorageForms extends { domain: infer D } ? D : never return this.form('domain' as keyof StorageForms)
} }
} }
@@ -69,7 +69,7 @@ expect.extend({
received() received()
} catch (error) { } catch (error) {
const pass = Object.entries(expected).every( const pass = Object.entries(expected).every(
(entry) => (error as Record<string, unknown>)[entry[0]] === entry[1], entry => (error as Record<string, unknown>)[entry[0]] === entry[1],
) )
return { pass, message: () => `expected thrown error to match ${JSON.stringify(expected)}, got ${String(error)}` } return { pass, message: () => `expected thrown error to match ${JSON.stringify(expected)}, got ${String(error)}` }
} }