This package is the canonical implementation of the Resource Framework that powers Suitsbooks admin surfaces. It turns your Drizzle schema and snapshot metadata into:
- Fully type-safe resource routes (
defineDrizzleResourceRoute) - An opinionated table stack with filtering, search, and editing controls (
ResourceTable) - Auto-generated create/edit dialogs and drilldown views (
CreateResourceDialog,ResourceDrilldown) - Shared utilities for metadata lookups, filtering, CSV export, notifications, and retry-safe mutations
It ships both React UI primitives and non-UI helpers so the rest of the app can consume resources without copying infrastructure.
- Architecture overview
- Data flow & configuration
- Core exports
- Directories at a glance
- Components & UI building blocks
- Hooks
- Adapters & network helpers
- Registries & constructors
- Utilities
- Types & schema
- Resource Forms
- Testing
- Best practices & patterns
Drizzle schema (drizzle/schema.ts)
↓
drizzle-kit generate
↓
meta/snapshot.json ←─ runtime metadata for every table/column
↓
types/drizzle-schema.ts ← compile-time typings generated by scripts
↓
packages/resource-framework ← metadata-driven UI
meta/snapshot.jsonis the single source of truth for field types, defaults, enums, masks, etc.- Runtime utilities in
utils/drizzle-editor.tsinterpret that JSON and exposegetDrizzleFieldType,getDrizzleColumnInfo, and type coercion helpers. - Type definitions in
types/drizzle-schema.tsmirror the snapshot so route definitions and column builders stay type-safe.
- Schema definition: declare tables using Drizzle ORM declarative methods (
pgTable,text,numeric, etc.). - Snapshot generation: run
npx drizzle-kit generateafter schema changes to updatemeta/andtypes/. - Define resource routes with
defineDrizzleResourceRoute(includes table name, id column, default columns, drilldown config, edit/create flags). - Register routes inside
registries/resource-routes.ts,RESOURCE_ROUTES, and optionallyRESOURCE_DRILLDOWN_ROUTES. - Render UI using
ResourceTable,ResourceDrilldown,CreateResourceDialog, and the helpers described below.
Routes optionally include:
createconfig (scopes, required fields,columnsoverrides)drilldownconfig (section templates, widgets, actions, auto-hide flags)icons,page_label,deferToHeader,enableSearch,force_external_api_updates, etc.categoriesused by the UI to group tabs or sidebar entries (seeutils/categories.ts)
Most of the public API is re-exported from index.ts. Highlights:
| Feature | Export(s) |
|---|---|
| Resource routes | defineDrizzleResourceRoute, defineDrizzleColumns, RESOURCE_ROUTES, getResourceRoute, resourceRoutes |
| UI primitives | ResourceTable, ResourceDrilldown, CreateResourceDialog, CreateResourceButton |
| Drilldown helpers | ResourceDrilldownSection, DrilldownSection, DrilldownSummary, SectionWidgetGroup, ResourceDrilldownNoEditFields, DrilldownActivity |
| Table controls | DisplaySettings, TableAddButton, TableSearchInput, TablePaginationControls, TableFullscreenToggle, TableDownloadButton, TableDeleteDialog, TableTopControls, TableHeaderCell, TableBodyCell |
| Adapters | fetchDataViaAthena, insertDataViaAthena, updateDataViaAthena, deleteDataViaAthena, uploadFileViaAthena, refreshFileUrlViaAthena, applyTransform |
| Utilities | buildCategoryByKey, coerceByDatatype, buildTableColumns, insertRow, coerceValue, applyClientFilters, parseQueryFilters, parseQuerySort |
| Hooks | useResourceContext, useResourceRoute, useTableConfiguration, useFetchData, etc. |
| Types | ResourceRoute, ResourceFieldSpec, ColumnConfig, DrilldownSectionConfig, DrizzleTableName, DrizzleColumnValue, etc. |
| Resource forms | defineResourceFormSchema, defineResourceForm, createResourceFormRows, resolveResourceFormRows, useResourceFormRuntime, EntityFormV2 |
athena-gateway.ts: typed CRUD adapter that routes reads and mutations through the Athena SDK and gateway.athena-files.ts: file upload + signed URL refresh helpers that target Athena-hosted file endpoints.transforms.ts: helpers for applying server-defineddata.transformsonto fetch responses before they reach the UI.
ResourceProvider.tsx: context provider wiring user/org info, notifications, and routing metadata to downstream table and dialog components.ResourceTable.tsx: table orchestrator that handles columns, pagination, search, filters, display settings, and drilldown navigation.ResourceDrilldown.tsx: detail view that renders panels defined byResourceDrilldownSectionand optional widgets (SectionWidgetGroup). SupportsDrilldownSummary, file explorer, and custom actions.CreateResourceDialog.tsx/CreateResourceButton: type-safe create experience based onResourceCreateConfig.components/sections/*: building blocks for drilldown sections, widgets, summaries, and attachments.components/table/*: portable table controls (search, add, delete, download, pagination, fullscreen) for consistent UI.components/form-v2/EntityFormV2.tsx: shared form stack powering dialogs, drilldown actions, and card/plan selects.components/form-v2/*: the shared resource-form renderer stack (EntityFormV2,FormFieldV2, review cards).components/edit-state: helpers for inline edit indicators (AddField,toggle-edit,SaveAll).components/cells: custom cell renderers (e.g.,ScopeCell,AssigneesCell) used in table defaults.components/drilldown: layout components (DrilldownLayout,DrilldownTable,DrilldownActivity) plus file explorer and action toolbar.
column-registry.tsx: centralized TanStack column builders (buildStatusColumn,buildMonthColumn, etc.), default editors, and sort/filter helpers.define-columns.ts:defineColumns()converts high-levelResourceFieldSpecintoBuiltColumnSpecwhile inferring editors/data sources fromgetDrizzleColumnInfo.define-drizzle-resource-route.ts: validates route definitions, wires defaults, and keeps metadata in sync with the generated schema.
handle-options.ts: exportsfetchOptions()for populating select editors, resolves query filters, and respectsCache-Controlheaders.handle-update.ts: mutation handler that writes through the Athena adapter.handle-csv-export.ts: produces CSV downloads from current query state, infers column types, and exposeshandleDownloadCsv.
useResourceContext: tracks the activeresourceName,route, and filters shared across UI pieces.useResourceRoute: returns the typed route entry and registers side effects (breadcrumbs, header actions).useResourceColumns: memoizes column definitions for the current route.useTableConfiguration: manages saved layouts (column visibility, sort order, pagination state).useFetchData: core data fetching withcacheEnabled/force_no_cache.useUpdateData: mutation hook used byCreateResourceDialog, inline editors, and drilldown actions.useAdvancedFilters,useQueryFilters: parse and apply URL filters (dork query style).useApiClient: shared Axios instance configured with user headers plusCache-Control.useEventsStream: powers drilldown activity feeds or chat tables.useKeyboardShortcut,useMobile: desktop shortcuts and responsive helpers.useNotification: thin wrapper around the shared notification hook to show messages after fetch/update.useUserPreferences,useUserScopes: surface stored UI preferences and permission scopes.
resource-routes.ts: the authoritativeRESOURCE_ROUTES,resourceRoutes, andgetResourceRoutehelper for every page that renders a table/drilldown. Populate this before rendering.resource-drilldown-routes.ts: optionalRESOURCE_DRILLDOWN_ROUTESto override section layouts, widgets, actions, and titles for drilldown pages.filter-registry.tsx: default filter operator sets per datatype so the table filter UI stays consistent.
VideoRenderer.tsx: helper for embedding video previews in table cells or drilldown sections when a field indicates media content.
- Declares every shared config shape (
ResourceRoute,ResourceFieldSpec,ColumnConfig,DrilldownSectionConfig,ResourceFormField, etc.). Keep this in sync with constructor logic.
drizzle-schema.ts: generated typings derived frommeta/snapshot.json(drizzle table/column names, types, optional metadata).tanstack-table.d.ts: helper declarations to keep TanStack columns typed across the registry.
categories.ts: helpers to derive sidebar/category tabs from route metadata.coerce.ts: converts string inputs into typed values (numbers, booleans, enums).client-filter.ts: client-side filtering helpers (applyClientFilters,coerceValue).column-builder.ts: consumed byResourceTableto build TanStack columns.csv.ts: infers CSV value types so downloads use the same formatting.dork-query.ts: parses filter/sort query strings (aka dork query) and syncs them with the URL.drizzle-editor.ts: maps snapshot metadata to editor types (getDrizzleEditorType,getDrizzleFieldType,getDrizzleColumnInfo).insert.ts:insertRow()applies defaults, resolves transforms, and posts via the adapter.resource-forms.ts: validation, normalization, ordered-step helpers, builder DSL, and row-to-runtime resolution for persisted form contracts.key-case.ts: normalizes camelCase/snake_case accessors used by query parsers.query-parser.ts: helper forparseQueryFiltersandparseQuerySort.render-functions.ts: placeholdernoopand other render helpers.- Additional helpers:
buildCategoryByKey,coerceByDatatype,inferCsvTypes,applyTransform,parseDorkQuery, etc.
- Covers column builders, coercion helpers, filter parsing, templates, hooks, and utility functions.
- Run
pnpm test --filter resource-frameworkor use Vitest directly for this folder.
- Wrap pages in
ResourceProviderto supplyResourceContext. It readsRESOURCE_ROUTES, listens for header actions, and providesuser,organization, andnotification. - Re-export
suppressHydrationWarningbecause some metadata is client-rendered.
- Handles data fetching, column resolution, search, filters, pagination, and drilldown linking.
- Defaults to rendering:
TableTopControls: search input, download, add button, fullscreen toggle.DisplaySettings: column visibility and reset controls.- TanStack
Tablebuilt from the column registry. TablePaginationInfoandTablePaginationControls.
- Accepts
resourceName, optionalcolumns,filters,forceLoading, andtableConfigOverrides. - Supports
deferToHeaderso headers/new buttons can live in the global header.
- Shows a record detail page configured via
ResourceDrilldownSectionarrays. - Supports:
- Summary area (
DrilldownSummary) - Section grids (1-4 columns)
- Widget injection via
SectionWidgetGroup DrilldownActions,DrilldownActivity, File Explorer, and edit tooling
- Summary area (
- Sections can define
widgets: [{ type: "json" }]and custom components must be registered viaregisterSectionWidgetinsidepackages/resource-framework/components/sections/widgets. - Always use the
uuidwhen building drilldown URLs (notrow_id).
- Driven by
ResourceCreateConfigdefined per route. - Automatically infers editors (
text,select,textarea,boolean) through the column registry and Drizzle metadata helpers. - Supports
defaultValues,columnsoverrides (includingeditor,data_source,options), customdialogcomponents, andonSubmit. - Uses
EntityFormV2internally, sharing inputs with other flows like plan/card selectors and Stripe pay fields.
components/form/form-field.tsxandform-v2components add consistent validation,react-error-boundaryfallbacks, and accessibility patterns.fields/select-data-source.tsxandhandler/fetch-options.tskeep dropdowns driven by database tables and cache state viaCache-Control.edit-state/*components show unsaved changes and inline editing states.
Use the exported hooks to keep UI synchronized:
useResourceRoute/useResourceColumnsfor column metadata.useTableConfigurationfor column visibility, order, and sort state.useFetchDatawithcacheEnabledcontrols; route entries may setforce_no_cache.useUpdateDatahonorsforce_external_api_updatesand ensures notifications follow the workspace pattern (useNotification+notification(...)).useAdvancedFilters/useQueryFiltersparse filters from URLs and feed them into the table.useKeyboardShortcut/useMobilesupport global shortcuts and responsive behavior.useApiClientcentralizes HTTP headers and base URLs.
adapters/athena-gateway.tsis the primary data plane:- Reads and writes go through the typed Athena SDK (
@xylex-group/athena). - Tenant/user headers are injected from framework context.
- Responses are normalized so the rest of the framework can stay stable during migration.
- Reads and writes go through the typed Athena SDK (
adapters/athena-files.tshandles file transfer endpoints hosted behind the Athena base URL:uploadFileViaAthena()posts multipart form data to/api/upload.refreshFileUrlViaAthena()refreshes signed file URLs via/api/files/refresh-url.- each call carries
X-Request-Id; mutations also carryIdempotency-Key.
handlers/handle-options.ts,handle-update.ts, and the core hooks call these adapters so package consumers do not have to manage transport details themselves.
- Always register new resources in
registries/resource-routes.ts(RESOURCE_ROUTES). UsegetResourceRoute(resourceName)to resolve configuration at runtime. - Supplement or override drilldown layouts in
registries/resource-drilldown-routes.ts. - Use
defineDrizzleResourceRoute/defineColumns/column-registryso route metadata stays type-safe and reuses shared editors, formatters, and sort logic. - The
filter-registrymaps datatypes to operator sets. UsegetFilterOptions()to feed the filter UI.
Expose consistent helpers for:
- Categories (
buildCategoryByKey) - Type coercion (
coerceByDatatype,coerceValue) - CSV exports (
inferCsvTypes) - Column builders (
buildTableColumns) - Query parsing/syncing (
parseQueryFilters,parseQuerySort,parseDorkQuery,applyDorkQueryToUrl) - Drizzle metadata (
getDrizzleEditorType,getDrizzleFieldType,getDrizzleColumnInfo) - Insert helpers (
insertRow) - Client-side filtering (
applyClientFilters) - Key casing (
toCamelCase,toSnakeCase,getValueByKeyCase,getValueByPathCase)
resource-types.tsdefines shared config shapes (ResourceRoute,ResourceFieldSpec,ColumnConfig,DrilldownSectionConfig,ResourceFormField, etc.).types/drizzle-schema.tsprovides strongly typed table/column names (DrizzleTableName,DrizzleColumnName), column value types (DrizzleColumnValue), and metadata.- Always keep
meta/andtypes/in sync with the Drizzle schema. Regenerate after schema changes (npx drizzle-kit generate).
resource_forms is now a first-class subsystem, not just a demo pattern.
- Author form schemas with
defineResourceFormSchema(). - Wrap them in
defineResourceForm()to attach metadata, defaults, and explicit version lineage. - Convert definitions into persisted rows with
createResourceFormRow()/createResourceFormRows(). - Read rows back through
resolveResourceFormRow()/resolveResourceFormRows(). - Migrate submission payloads deterministically with
migrateResolvedResourceFormSubmission()when downstream contracts expect a different form version. - Use
useResourceFormRuntime()to manage selected-form and value state. - Render the validated schema with
EntityFormV2.
Persist migration_key + schema_version on each resource_forms row so builder changes can be migrated as an explicit form family/version pair.
Use this layer whenever a form should be driven by persisted metadata instead of hardcoded JSX.
- Unit tests live inside
packages/resource-framework/__tests__. - They cover column builders, coercion helpers, filter parsing, drilldown helpers, display configuration, templates, and hooks (
build-columns.test.tsx,coerce.test.ts,use-table-configuration.test.ts, etc.). - Run
npm testfor contract/unit coverage. - Run
npm run test:integrationto execute the env-validated Athena CRUD/file integration suite. - Run
npm run typecheckfor the workspace-aware package/demo/playground TypeScript checks.
- Run
npx drizzle-kit generatewhenever you touchdrizzle/schema.ts. The metadata snapshot is consumed at runtime. - Prefer
defineDrizzleResourceRoute/defineDrizzleColumnsto inline strings for type safety. - Wrap UI with
ResourceProviderto keep context, notifications, and header integration consistent. - Drilldown widgets must be registered via
components/sections/widgetsandregisterSectionWidget. - Always call
notification({ message, success })viauseNotificationafter mutations to show consistent alerts. - Use the
Table*components for action buttons/controls so variant/icon/rounding rules stay consistent. - When resources require non-standard data, keep
handlers/handle-options.tsupdated with new data sources to power dropdowns.
This README is intended to be the canonical reference you can cite from .mdc documentation pages.
A powerful, type-safe abstraction layer built on Drizzle ORM that provides automatic CRUD operations, data fetching, table rendering, and form generation for your database tables.
The Resource Framework turns your Drizzle schema into a fully-functional admin interface with:
- Type-safe table and column references - Full TypeScript intellisense
- Automatic form generation - Create/edit forms based on your schema
- Data fetching - Built-in hooks for querying data
- Table rendering - Responsive tables with sorting, filtering, pagination
- Drilldown views - Automatic detail pages for records
// drizzle/schema.ts
import { pgTable, text, numeric, timestamp } from "drizzle-orm/pg-core";
export const invoices = pgTable("invoices", {
invoice_id: text().primaryKey(),
organization_id: text().notNull(),
recipient_email: text().notNull(),
amount: numeric("amount", { precision: 10, scale: 2 }),
status: text().notNull(),
due_date: timestamp({ withTimezone: true }),
});npx drizzle-kit generateThis creates drizzle/meta/0000_snapshot.json which the framework reads for metadata.
import { CreateResourceDialog } from "@/packages/resource-framework";
function MyComponent() {
const [open, setOpen] = useState(false);
return (
<CreateResourceDialog
open={open}
onCloseAction={() => setOpen(false)}
table="invoices"
columns={["organization_id", "recipient_email", "amount", "status"]}
required={["organization_id", "recipient_email"]}
title="Create invoice"
/>
);
}When you load the dialog configuration from resource_routes via resourceName, store a create entry that lists columns to control which fields appear:
{
"create": {
"scope": "create:invoices",
"required": ["recipient_email", "amount"],
"columns": ["recipient_email", "amount", "status"]
}
}That's it! The framework automatically:
- Infers field types from your schema
- Renders appropriate inputs (text, number, date, etc.)
- Validates required fields
- Inserts data into the database
- Shows success/error notifications
Automatically generates create forms based on your schema.
Basic usage:
<CreateResourceDialog
open={open}
table="invoices"
columns={["recipient_email", "amount"]}
onCloseAction={() => setOpen(false)}
/>With configuration:
<CreateResourceDialog
table="invoices"
columns={[
{
column_name: "status",
header: "Status",
editor: {
type: "select",
options: [
{ label: "Draft", value: "draft" },
{ label: "Sent", value: "sent" },
],
},
},
{
column_name: "customer_id",
editor: {
type: "select",
data_source: "customers", // Auto-loads from database
},
},
]}
defaultValues={{
organization_id: user.organization_id,
}}
/>Displays data in a responsive table.
<ResourceTable resourceName="invoices" />Shows detailed view of a single record.
<ResourceDrilldown resourceName="invoices" />Use type-safe definitions for autocomplete and compile-time checks:
import {
defineDrizzleResourceRoute,
defineDrizzleColumns
} from "@/packages/resource-framework";
const route = defineDrizzleResourceRoute({
table: "invoices", // Autocomplete from schema
idColumn: "invoice_id", // Type-safe
columns: defineDrizzleColumns<"invoices">([
{ column_name: "recipient_email" }, // Autocomplete
{ column_name: "amount" },
]),
});Field types are automatically inferred from your Drizzle schema:
| Drizzle Type | Field Type | Rendered As |
|---|---|---|
text() |
text | <input type="text"> |
integer() |
number | <input type="number"> |
boolean() |
boolean | <input type="checkbox"> |
timestamp() |
date | <input type="datetime-local"> |
numeric() |
number | <input type="number"> |
Configure how fields are rendered:
Select with static options:
{
column_name: "status",
editor: {
type: "select",
options: [
{ label: "Draft", value: "draft" },
{ label: "Sent", value: "sent" },
]
}
}Select with dynamic data source:
{
column_name: "customer_id",
editor: {
type: "select",
data_source: {
table: "customers",
value_column: "customer_id",
label_column: "name",
}
}
}Pre-fill fields that shouldn't be edited:
<CreateResourceDialog
columns={["recipient_email", "amount"]}
defaultValues={{
organization_id: user.organization_id,
created_by: user.user_id,
status: "draft",
}}
/>| Prop | Type | Description |
|---|---|---|
open |
boolean |
Dialog open state |
onCloseAction |
() => void |
Called when dialog closes |
table |
string |
Database table name |
columns |
Array<string | ColumnConfig> |
Columns to include |
required |
string[] |
Required field names |
defaultValues |
Record<string, FieldValue> |
Default values |
title |
string |
Dialog title |
See the full API reference in docs/RESOURCE_FRAMEWORK.md.
Get metadata about a column:
import { getDrizzleColumnInfo } from "@/packages/resource-framework";
const info = getDrizzleColumnInfo("invoices", "amount");
// { dataType: "number", fieldType: "number", isNullable: false }Get the inferred field type:
import { getDrizzleFieldType } from "@/packages/resource-framework";
const type = getDrizzleFieldType("invoices", "due_date");
// "date"All table names from your schema:
import type { DrizzleTableName } from "@/packages/resource-framework";
const table: DrizzleTableName = "invoices"; // AutocompleteColumn names for a specific table:
import type { DrizzleColumnName } from "@/packages/resource-framework";
type Col = DrizzleColumnName<"invoices">;
// "invoice_id" | "organization_id" | "recipient_email" | ...Value type for a column:
import type { DrizzleColumnValue } from "@/packages/resource-framework";
type Amount = DrizzleColumnValue<"invoices", "amount">; // numberThe force_external_api_updates flag is still accepted for route compatibility, but it now routes through the Athena gateway adapter rather than a direct browser call to a legacy endpoint:
// In RESOURCE_ROUTES
export const RESOURCE_ROUTES = {
invoices: {
table: "invoices",
idColumn: "invoice_id",
// Keep using the external Athena-backed mutation path
force_external_api_updates: true, // default: false
}
};When enabled:
- All edit/update operations stay on the Athena adapter path.
- No client-embedded legacy API secret is used.
- Useful while migrating routes that still need the gateway-backed mutation contract.
- If
false(default), the same Athena adapter path is used without the compatibility flag.
The Resource Framework can defer the page title and "New" button to the app header using useContentStore:
// In RESOURCE_ROUTES
export const RESOURCE_ROUTES = {
invoices: {
table: "invoices",
idColumn: "invoice_id",
page_label: "Invoices",
enableNewResourceCreation: true,
newResourceButtonText: "New invoice",
// Defer everything to header
deferToHeader: true,
// Or defer individually
deferTitleToHeader: true,
deferNewButtonToHeader: true,
deferSubtitleToHeader: true,
}
};When enabled:
- Title moves from table header to app header
- "New" button appears in app header with icon
- Subtitle can be set in app header
- Table shows clean interface without redundant headers
This creates a cleaner, more integrated UI where controls live in the main app header instead of within the table component.
- Quick Reference:
docs/RESOURCE_FRAMEWORK_QUICKREF.md- Cheat sheet - Full Documentation:
docs/RESOURCE_FRAMEWORK.md- Complete guide - Drizzle Integration:
docs/DRIZZLE_INTEGRATION.md- Architecture details - Demo:
/v2/beautiful- Interactive demonstration
Visit /v2/beautiful in your app to see:
- Interactive CreateResourceDialog demo
- Real-time configuration editor
- Code examples
- Documentation
Your Drizzle Schema (schema.ts)
↓
Drizzle Snapshot (meta/snapshot.json)
↓
Type Inference (types/drizzle-schema.ts)
↓
Metadata Utils (utils/drizzle-editor.ts)
↓
Components (CreateResourceDialog, ResourceTable, etc.)
The framework uses type-only imports for compile-time checks and JSON snapshot for runtime metadata. This provides type safety without bloating your client bundle.
<CreateResourceDialog
table="invoices"
columns={["recipient_email", "amount", "due_date"]}
required={["recipient_email", "amount"]}
defaultValues={{
organization_id: user.organization_id,
status: "draft",
}}
/><CreateResourceDialog
table="invoices"
columns={[
{
column_name: "customer_id",
editor: { type: "select", data_source: "customers" }
},
"amount",
"due_date",
]}
/>const route = defineDrizzleResourceRoute({
table: "invoices",
idColumn: "invoice_id",
columns: defineDrizzleColumns<"invoices">([
{ column_name: "recipient_email", header: "Recipient" },
{ column_name: "amount", header: "Amount" },
]),
});DO:
- Use
defineDrizzleResourceRoutefor type safety - Pre-fill system fields with
defaultValues - Mark internal columns as
hidden - Use data sources for foreign keys
- Run
drizzle-kit generateafter schema changes
DON'T:
- Import Drizzle schema directly on client
- Hardcode table/column names
- Forget to regenerate snapshot after schema changes
No columns showing?
npx drizzle-kit generateType errors?
- Restart TypeScript server
- Ensure tables are exported in
drizzle/schema.ts
Fields showing as text?
{
column_name: "amount",
data_type: "number",
editor: { type: "number" }
}The framework is designed to be extended:
- Add custom type mappings in
utils/drizzle-editor.ts - Create custom editors in
components/dialog.tsx - Add custom formatters in column configs
Part of the Suitsbooks project.
Need help? Check the docs folder for comprehensive guides.
This framework is a metadata-driven UI layer where the critical runtime path is: UI components (ResourceTable/ResourceDrilldown) -> Athena SDK adapter -> Athena gateway/DB, plus file flows via Athena-hosted upload and signed S3/MinIO URL refresh endpoints. The highest operational risk is concentrated in network dependency health and consistency between storage and metadata writes.
- Failure mode: Data-plane dependency outage. Trigger: server-action/data API or fallback endpoint unavailability. Symptoms: tables fail to load, updates fail, repeated retries, user-facing errors. Detection: elevated client
isError, higher mutation failure rate, API p95/p99 latency spikes. Mitigation: move all CRUD behind Athena gateway, add circuit breaking and bounded retries, and standardize error envelopes. - Failure mode: Read-after-write inconsistency. Trigger: update succeeds but immediate refetch returns stale/cached row. Symptoms: values appear reverted, duplicate save attempts, mismatch logs. Detection: compare update payload vs post-update read in telemetry. Mitigation: require canonical updated row in Athena write responses, enforce consistent cache policy per route, and use version/etag checks.
- Failure mode: File upload two-phase gap. Trigger: object upload succeeds but metadata insert fails (or inverse). Symptoms: orphaned objects, missing files in UI, broken links. Detection: periodic reconciliation between object storage keys and file metadata table. Mitigation: transactional server workflow via Athena, idempotency keys, and compensating cleanup jobs.
- Failure mode: Signed URL refresh dependency failures. Trigger:
/api/files/refresh-urlequivalent path unavailable or key extraction mismatch. Symptoms: 403 loops, preview/download failures, repeated refresh attempts. Detection: refresh failure-rate and 403-rate dashboards by renderer type. Mitigation: centralized Athena file URL refresh endpoint, proactive refresh with jitter, and resilient fallback UX. - Failure mode: Client-side privileged fallback exposure. Trigger: browser path uses direct privileged external update calls. Symptoms: secret leakage risk and unauthorized mutation attempts. Detection: secret scanning + anomaly detection on mutation traffic. Mitigation: remove client-embedded secrets and route all privileged operations through Athena with scoped server-side auth.
- Confirmed from repo: UI layer,
useApiClientdata access, Athena SDK adapter, Athena-configured file upload path, and signed URL refresh flow. - Inferred due to missing backend internals here: exact DB topology, queueing/event infrastructure, and gateway-level HA posture.
- Target gateway spec:
https://athena-db.com/openapi.yaml - Implementation plan: see TODO.md.