Files
KArtSell.Aegis/frontend/src/shared/crud/resourceDefinition.ts
T
kjh2064 3287ae399e feat: type-safe CRUD sensitive field definitions (AEG-V16-021)
Constrain sensitive fields to row keys and preserve generic resource-definition contracts.
2026-08-08 15:00:57 +09:00

31 lines
1.7 KiB
TypeScript

import type { ZodType } from 'zod'
import type { UiGridColumn } from '../ui/adapter/contracts'
export type CrudConcurrencyMode = 'NONE' | 'ETAG_IF_MATCH'
export type CrudIdempotencyMode = 'NONE' | 'IDEMPOTENCY_KEY'
export type CrudRowField<TRow> = Extract<keyof TRow, string>
export interface CrudResourceDefinition<TQuery, TRow, TForm> {
resourceId: string
querySchemaVersion: string
responseSchemaVersion: string
querySchema: ZodType<TQuery>
rowSchema: ZodType<TRow>
formSchema: ZodType<TForm>
columns: readonly UiGridColumn[]
sensitiveFields: readonly CrudRowField<TRow>[]
permissionPolicy: string
concurrencyMode: CrudConcurrencyMode
idempotencyMode: CrudIdempotencyMode
}
export function assertCrudResourceDefinition<TQuery, TRow, TForm>(definition: CrudResourceDefinition<TQuery, TRow, TForm>): void {
if (!definition.resourceId.trim()) throw new Error('resourceId is required')
if (!definition.querySchemaVersion.trim() || !definition.responseSchemaVersion.trim()) throw new Error('schema versions are required')
if (!definition.permissionPolicy.trim()) throw new Error('permissionPolicy is required')
if (definition.concurrencyMode !== 'NONE' && definition.concurrencyMode !== 'ETAG_IF_MATCH') throw new Error('invalid concurrencyMode')
if (definition.idempotencyMode !== 'NONE' && definition.idempotencyMode !== 'IDEMPOTENCY_KEY') throw new Error('invalid idempotencyMode')
const fields = new Set(definition.columns.map(x => x.field))
for (const sensitive of definition.sensitiveFields) {
const column = definition.columns.find(x => x.field === sensitive)
if (!fields.has(sensitive) || !column?.sensitive) throw new Error(`Sensitive field '${sensitive}' has no sensitive grid column contract`)
}
}