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-25 11:08:04 +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 = {
domainName: spec.name,
unit,
enqueue: (job) => this.enqueue(job),
assertReadable: () => this.assertReadable(),
emitChanged: (change) => this.ctx.emit('domain/changed', change),
enqueue: job => this.enqueue(job),
assertReadable: () => { this.assertReadable() },
emitChanged: (change) => { this.ctx.emit('domain/changed', change) },
}
for (const [table, tableRecords] of records) {
this.tables.set(table, new KvTableImpl(host, table, tableRecords))
@@ -178,7 +178,7 @@ export class DomainImpl {
this.assertReadable()
return this.globalValue
},
set: (value) => this.enqueue(async () => {
set: value => this.enqueue(async () => {
await this.unit.setGlobal(value)
this.globalValue = 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
// materializing it — the first `set` writes.
const globalValue = spec.global === undefined
const globalSpec = spec.global
const globalValue = globalSpec === undefined
? undefined
: snapshot.global === null
? spec.global.initial
: parseRecord(spec.name, '', '', () => spec.global!.schema.parse(snapshot.global))
? globalSpec.initial
: parseRecord(spec.name, '', '', () => globalSpec.schema.parse(snapshot.global))
const domain = new DomainImpl(this.ctx, spec, unit, tables, globalValue)
// The open-domain table entry is itself the effect: registration and
// 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. */
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. */
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')
await table.put('counter', { label: 'c', count: 0 })
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 })
})
it('update rejects a missing key; delete reports prior existence', async () => {
const { facility } = await harness()
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 expect(table.delete('a')).resolves.toBe(true)
await expect(table.delete('a')).resolves.toBe(false)
@@ -192,7 +192,7 @@ describe('KvTable writes', () => {
const domain = await facility.open(spec)
const table = domain.table('items')
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') // no event: already absent
await domain.global.set({ theme: 'dark' })
@@ -216,7 +216,7 @@ describe('durability failure', () => {
pool.failNextWrites = 3
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/)
// Reads still serve the pre-failure record; no events leaked.
@@ -225,7 +225,7 @@ describe('durability failure', () => {
expect(changes).toHaveLength(seen)
// 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 })
})
@@ -18,10 +18,10 @@
import { StorageError } 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 {
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()
const tables: Record<string, Record<string, unknown>> = {}
for (const table of this.descriptor.tables) {
+12 -12
View File
@@ -29,7 +29,7 @@ async function setup() {
return { ctx, facility }
}
const invariantViolation = expect.objectContaining<Partial<InvariantError>>({
const invariantViolation: unknown = expect.objectContaining<Partial<InvariantError>>({
code: 'INVARIANT',
packageName: '@deepseek-ai/dsh-domain',
})
@@ -40,42 +40,42 @@ describe('domain change-event invariants', () => {
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 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', {
expect(() => { ctx.emit('domain/changed', {
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 () => {
const { ctx, facility } = await setup()
const domain = await facility.open(spec)
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 },
})).toThrow(invariantViolation)
}) }).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', {
expect(() => { ctx.emit('domain/changed', {
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 () => {
const { ctx, facility } = await setup()
await facility.open(spec)
expect(() => ctx.emit('domain/changed', {
expect(() => { ctx.emit('domain/changed', {
domain: 'inv', table: '', key: '', operation: 'put', value: { n: 42 },
})).toThrow(invariantViolation)
}) }).toThrow(invariantViolation)
})
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 })
// 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', {
expect(() => { ctx.emit('domain/changed', {
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 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 {
version: number
global: unknown | null
global: unknown
tables: Map<string, Map<string, unknown>>
}
+9 -9
View File
@@ -36,10 +36,10 @@ export async function openJsonUnit(
const state: UnitState =
text === undefined
? {
version: descriptor.version,
global: null,
tables: new Map(descriptor.tables.map((table) => [table, new Map()])),
}
version: descriptor.version,
global: null,
tables: new Map(descriptor.tables.map(table => [table, new Map<string, unknown>()])),
}
: parse(text, descriptor)
return new JsonKvUnit(descriptor, path, state, onClose)
}
@@ -56,13 +56,13 @@ class JsonKvUnit implements KvUnit {
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()
const tables: Record<string, Record<string, unknown>> = {}
for (const [table, records] of this.state.tables) {
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> {
@@ -73,7 +73,7 @@ class JsonKvUnit implements KvUnit {
records.set(key, value)
// 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).
await this.publish().catch(async (error) => {
await this.publish().catch((error: unknown) => {
if (hadKey) records.set(key, previous)
else records.delete(key)
throw error
@@ -86,7 +86,7 @@ class JsonKvUnit implements KvUnit {
if (!records.has(key)) return
const previous = records.get(key)
records.delete(key)
await this.publish().catch(async (error) => {
await this.publish().catch((error: unknown) => {
records.set(key, previous)
throw error
})
@@ -99,7 +99,7 @@ class JsonKvUnit implements KvUnit {
}
const previous = this.state.global
this.state.global = value
await this.publish().catch(async (error) => {
await this.publish().catch((error: unknown) => {
this.state.global = previous
throw error
})
@@ -78,7 +78,7 @@ describe('json backend specifics', () => {
const root = await freshRoot()
const backend = new JsonStorageBackend(root)
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()
})
@@ -208,13 +208,15 @@ describe('json backend specifics', () => {
const bigWrite = unit.putRecord('t', 'big', { blob: 'x'.repeat(4 * 1024 * 1024) })
await unit.close()
await expect(bigWrite).resolves.toBeUndefined()
const onDisk = JSON.parse(await readFile(join(root, 'shape.json'), 'utf8'))
expect(onDisk.tables.t.big).toBeDefined()
const onDisk = JSON.parse(await readFile(join(root, 'shape.json'), 'utf8')) as {
tables: Record<string, Record<string, unknown>>
}
expect(onDisk.tables['t']?.['big']).toBeDefined()
const backend2 = new JsonStorageBackend(root)
const opening = backend2.kv.open(descriptor)
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
})
})
+53 -32
View File
@@ -62,24 +62,25 @@ export class SqliteKvUnit implements KvUnit {
: undefined
}
async loadAll(): Promise<{ tables: Record<string, Record<string, unknown>>; global: unknown | null }> {
this.ensureOpen()
const tables: Record<string, Record<string, unknown>> = {}
for (const [name, statements] of this.tables) {
// Null prototype: record keys are arbitrary strings, so '__proto__'
// must land as an own property instead of mutating the prototype.
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 }>) {
records[row.key] = this.parseValue(row.value, `table '${name}' key '${row.key}'`)
loadAll(): Promise<{ tables: Record<string, Record<string, unknown>>; global: unknown }> {
return this.settle(() => {
const tables: Record<string, Record<string, unknown>> = {}
for (const [name, statements] of this.tables) {
// Null prototype: record keys are arbitrary strings, so '__proto__'
// must land as an own property instead of mutating the prototype.
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 }>) {
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) {
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 }
let global: unknown = null
if (this.globalSelect !== undefined) {
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 }
})
}
/** 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> {
this.ensureOpen()
this.statementsFor(table).upsert.run(key, JSON.stringify(value))
putRecord(table: string, key: string, value: unknown): Promise<void> {
return this.settle(() => {
this.statementsFor(table).upsert.run(key, JSON.stringify(value))
})
}
async deleteRecord(table: string, key: string): Promise<void> {
this.ensureOpen()
this.statementsFor(table).remove.run(key)
deleteRecord(table: string, key: string): Promise<void> {
return this.settle(() => {
this.statementsFor(table).remove.run(key)
})
}
async setGlobal(value: unknown): Promise<void> {
this.ensureOpen()
if (this.globalUpsert === undefined) {
throw new Error(`kv unit '${this.descriptor.name}' declared no global slot`)
setGlobal(value: unknown): Promise<void> {
return this.settle(() => {
if (this.globalUpsert === undefined) {
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
this.closed = true
this.onClose()
/**
* Run one synchronous primitive behind the closed guard, mapping a throw to
* a rejection so the Promise-returning contract never throws synchronously.
*/
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 {
@@ -171,6 +171,17 @@ describe('sqlite backend specifics', () => {
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 () => {
const backend = backendAt(':memory:')
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
* 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.
+1 -1
View File
@@ -76,7 +76,7 @@ export class Storage extends Service {
/** 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
return this.form('domain' as keyof StorageForms)
}
}
@@ -69,7 +69,7 @@ expect.extend({
received()
} catch (error) {
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)}` }
}