Type src/ against API and database shapes, fixing implicit anys
Adds .d.ts declarations beside the untyped JS modules imported from TS (api.js, navConfig.js, adminSchema.js, adminNav.js, src/data/*), with shapes taken from the server routes and migrations. get/post/ patch/del now return unknown unless the caller names the response. Fixes found along the way: - website/email/instagram are bare strings from splitLinks, not Link objects; OrganizationDetail's website and email pills rendered with no href or label and now link correctly. - The chapter map falls back to FALLBACK_COLOR for a region with no colour instead of painting its tiles black. Adds defineSection() so each page manifest entry's props are checked against its own Component. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
5286cae9ca
commit
2428f4412a
39 changed files with 1318 additions and 343 deletions
118
src/lib/adminSchema.d.ts
vendored
Normal file
118
src/lib/adminSchema.d.ts
vendored
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
/* Types for adminSchema.js: the client half of the descriptor-driven
|
||||
admin CRUD engine. Rows are whatever columns the server-side
|
||||
descriptor in server/src/admin-schema.js declares, so they stay a
|
||||
string-keyed record; the descriptors are what's fixed. */
|
||||
|
||||
/** A row from /api/admin/:entity or /:entity/:id. Nested paths
|
||||
* ('region.scope') are side-table objects under their key, and
|
||||
* child collections are arrays of rows under theirs. */
|
||||
export type AdminRow = Record<string, unknown>;
|
||||
|
||||
/** One row of an OPTION_QUERIES result. `kind` and `org_id` ride
|
||||
* along on the lists that filterBy narrows. */
|
||||
export type AdminOption = {
|
||||
id: string;
|
||||
label: string;
|
||||
kind?: string;
|
||||
org_id?: string | null;
|
||||
};
|
||||
|
||||
/** GET /api/admin/options, keyed by OPTION_QUERIES name. */
|
||||
export type AdminOptions = Record<string, AdminOption[]>;
|
||||
|
||||
export type FieldWidget =
|
||||
| "text"
|
||||
| "textarea"
|
||||
| "select"
|
||||
| "checkbox"
|
||||
| "color"
|
||||
| "number"
|
||||
| "date";
|
||||
|
||||
/** A bare value, or [value, label]. */
|
||||
export type SelectOption = string | readonly [string, string];
|
||||
|
||||
/** Show a group or collection only when another field has this value. */
|
||||
export type FieldCondition = { path: string; value: unknown };
|
||||
|
||||
export type AdminFieldSpec = {
|
||||
path: string;
|
||||
label: string;
|
||||
widget?: FieldWidget;
|
||||
required?: boolean;
|
||||
full?: boolean;
|
||||
help?: string;
|
||||
options?: readonly SelectOption[];
|
||||
optionsFrom?: string;
|
||||
blankLabel?: string;
|
||||
filterBy?: (option: AdminOption, row: AdminRow) => boolean;
|
||||
/* Set by EntityEdit on the id field rather than in a manifest. */
|
||||
readOnly?: boolean;
|
||||
prefix?: string;
|
||||
prefixPending?: boolean;
|
||||
placeholder?: string;
|
||||
};
|
||||
|
||||
export type FieldGroupSpec = {
|
||||
legend: string;
|
||||
note?: string;
|
||||
when?: FieldCondition;
|
||||
fields: AdminFieldSpec[];
|
||||
};
|
||||
|
||||
export type CollectionSpec = {
|
||||
key: string;
|
||||
label: string;
|
||||
addLabel?: string;
|
||||
note?: string;
|
||||
when?: FieldCondition;
|
||||
title?: (row: AdminRow, options: AdminOptions | null | undefined) => string;
|
||||
blank: AdminRow;
|
||||
fields: AdminFieldSpec[];
|
||||
children?: CollectionSpec[];
|
||||
};
|
||||
|
||||
export type ListColumn = {
|
||||
key: string;
|
||||
label: string;
|
||||
primary?: boolean;
|
||||
widget?: "bool";
|
||||
};
|
||||
|
||||
export type ListFilter = {
|
||||
key: string;
|
||||
label: string;
|
||||
options?: readonly SelectOption[];
|
||||
optionsFrom?: string;
|
||||
};
|
||||
|
||||
export type EntitySpec = {
|
||||
key: string;
|
||||
label: string;
|
||||
singular: string;
|
||||
idLabel: string;
|
||||
/** "auto": the table assigns the id, so the form shows it rather than asking. */
|
||||
idKind?: "auto";
|
||||
/** Field(s) the slug is composed from. Absent when idKind is "auto". */
|
||||
slugFrom?: string | string[];
|
||||
titleFrom?: string;
|
||||
list: { columns: ListColumn[]; filters: ListFilter[] };
|
||||
groups: FieldGroupSpec[];
|
||||
children?: CollectionSpec[];
|
||||
};
|
||||
|
||||
export type AdminEntityKey =
|
||||
| "organizations"
|
||||
| "events"
|
||||
| "people"
|
||||
| "teams"
|
||||
| "awards"
|
||||
| "timeline";
|
||||
|
||||
/* Indexed by route param as often as by name, so any other string
|
||||
reads as possibly missing. */
|
||||
export declare const ADMIN_ENTITIES: { readonly [K in AdminEntityKey]: EntitySpec } & {
|
||||
readonly [key: string]: EntitySpec | undefined;
|
||||
};
|
||||
|
||||
export declare function slugify(value: unknown): string;
|
||||
Loading…
Add table
Add a link
Reference in a new issue