@zerotal/admin 1.8.1 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,28 @@ follows the Zerotal monorepo's unified versioning.
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [1.9.0] — 2026-08-29
12
+
13
+ ### Documented
14
+
15
+ - **Every promised export is documented.** The `docs-coverage` gate reads `maturity: stable` as a
16
+ promise about a package's exports, and measures how much of that promise is written down. It
17
+ was 798 gaps across the suite; it is now zero. This package's share is covered on its own
18
+ pages — types named, options shapes described, and the decisions behind them recorded where
19
+ somebody looking for them will find them.
20
+
21
+ ### Changed
22
+
23
+ - **INTERNAL: the panel's own page machinery is marked `@internal`.** The `make*Page` factories,
24
+ the page classes they produce (`ResourceListPage`, `RecordViewPage`, `ResourceFormPage`,
25
+ `RolesPage`, `MediaPage`, `NotificationsPage`, `ConsolePage`, …), the action and widget
26
+ renderers, and `AdminPanelHost`.
27
+
28
+ **Nothing is removed and nothing breaks** — they are still exported and still work. An app
29
+ declares a `Resource` and the panel builds these pages from it; none of them has a caller
30
+ outside `@zerotal/admin` or a line in the guide, because writing one was never the way to use
31
+ this package.
32
+
11
33
  ## [1.6.0] — 2026-08-15
12
34
 
13
35
  ### Fixed
package/api-surface.md CHANGED
@@ -1114,38 +1114,32 @@ interface ActionContext = {
1114
1114
  slug: string
1115
1115
  }
1116
1116
 
1117
- interface ActionPage = {
1118
- download?: (filename: string, content: string | Uint8Array, mime?: string) => unknown
1119
- flash: (message: string, level?: string) => unknown
1120
- redirect: (url: string) => { withSuccess(message: string): unknown;}
1121
- }
1122
-
1123
1117
  interface AdminAuthConfig = {
1124
- authMiddleware?: MiddlewareClass[]
1125
- authenticateWhen?: (user: unknown) => boolean | Promise<boolean>
1126
- emailVerification?: { isVerified(user: unknown): boolean; resend(user: unknown): Promise<void> | void;}
1127
- enabled?: boolean
1128
- guestMiddleware?: MiddlewareClass[]
1129
- heading?: string
1130
- identifier?: string
1131
- loginPath?: string
1132
- passwordReset?: { sendResetLink(email: string): Promise<boolean> | boolean; reset(input: { email: string; token: string; password: string; }): Promise<boolean> | boolean;}
1133
- profilePath?: string
1134
- redirectTo?: string
1135
- remember?: boolean
1136
- updateProfile?: (user: unknown, data: Record<string, unknown>) => Promise<void> | void
1118
+ authMiddleware?: MiddlewareClass[] | undefined
1119
+ authenticateWhen?: ((user: unknown) => boolean | Promise<boolean>) | undefined
1120
+ emailVerification?: { isVerified(user: unknown): boolean; resend(user: unknown): Promise<void> | void;} | undefined
1121
+ enabled?: boolean | undefined
1122
+ guestMiddleware?: MiddlewareClass[] | undefined
1123
+ heading?: string | undefined
1124
+ identifier?: string | undefined
1125
+ loginPath?: string | undefined
1126
+ passwordReset?: { sendResetLink(email: string): Promise<boolean> | boolean; reset(input: { email: string; token: string; password: string; }): Promise<boolean> | boolean;} | undefined
1127
+ profilePath?: string | undefined
1128
+ redirectTo?: string | undefined
1129
+ remember?: boolean | undefined
1130
+ updateProfile?: ((user: unknown, data: Record<string, unknown>) => Promise<void> | void) | undefined
1137
1131
  }
1138
1132
 
1139
1133
  interface AdminConfigShape = {
1140
- auth?: AdminAuthConfig
1141
- authorize?: AdminAuthorizer
1134
+ auth?: AdminAuthConfig | undefined
1135
+ authorize?: AdminAuthorizer | undefined
1142
1136
  brand: string
1143
- middleware?: MiddlewareClass[]
1137
+ middleware?: MiddlewareClass[] | undefined
1144
1138
  path: string
1145
- plugins?: Record<string, boolean>
1146
- tagline?: string
1147
- theme?: AdminThemeConfig
1148
- userMenu?: UserMenu
1139
+ plugins?: Record<string, boolean> | undefined
1140
+ tagline?: string | undefined
1141
+ theme?: AdminThemeConfig | undefined
1142
+ userMenu?: UserMenu | undefined
1149
1143
  }
1150
1144
 
1151
1145
  interface AdminModel = {
@@ -1170,18 +1164,6 @@ interface AdminNotification = {
1170
1164
  title: string
1171
1165
  }
1172
1166
 
1173
- interface AdminPanelHost = {
1174
- console: (contribution: ConsoleContribution) => void
1175
- enabled: (id: string) => boolean
1176
- navItem: (contribution: NavContribution) => void
1177
- page: (contribution: PageContribution) => void
1178
- renderHook: (name: string, hook: (context: RenderHookContext) => HtmlNode | string | null) => void
1179
- searchProvider: (provider: PanelSearchProvider) => void
1180
- topbarSlot: (slot: TopbarSlot) => void
1181
- userMenuItem: (item: UserMenuContribution) => void
1182
- widget: (contribution: WidgetContribution) => void
1183
- }
1184
-
1185
1167
  interface AdminPlugin = {
1186
1168
  id: string
1187
1169
  install: (panel: AdminPanelHost) => void | Promise<void>
@@ -1220,15 +1202,15 @@ interface AdminRecord = {
1220
1202
  }
1221
1203
 
1222
1204
  interface AdminThemeConfig = {
1223
- cdn?: boolean
1224
- noFonts?: boolean
1225
- stylesheet?: string
1226
- tokensCss?: string
1205
+ cdn?: boolean | undefined
1206
+ noFonts?: boolean | undefined
1207
+ stylesheet?: string | undefined
1208
+ tokensCss?: string | undefined
1227
1209
  }
1228
1210
 
1229
1211
  interface AuthRolesOptions = {
1230
- guard?: string
1231
- superusers?: string[]
1212
+ guard?: string | undefined
1213
+ superusers?: string[] | undefined
1232
1214
  }
1233
1215
 
1234
1216
  interface ChartData = {
@@ -1330,15 +1312,15 @@ interface DashboardLayoutStore = {
1330
1312
  }
1331
1313
 
1332
1314
  interface DatabaseMediaOptions = {
1333
- columns?: Partial<Record<keyof MediaItem, string>>
1334
- table?: string
1315
+ columns?: Partial<Record<keyof MediaItem, string>> | undefined
1316
+ table?: string | undefined
1335
1317
  }
1336
1318
 
1337
1319
  interface DatabaseNotificationOptions = {
1338
- limit?: number
1339
- notifiable?: () => Promise<unknown> | unknown
1340
- present?: (row: StoredNotification, data: Record<string, unknown>) => AdminNotification
1341
- table?: string
1320
+ limit?: number | undefined
1321
+ notifiable?: (() => Promise<unknown> | unknown) | undefined
1322
+ present?: ((row: StoredNotification, data: Record<string, unknown>) => AdminNotification) | undefined
1323
+ table?: string | undefined
1342
1324
  }
1343
1325
 
1344
1326
  interface EmptyState = {
@@ -1396,8 +1378,8 @@ interface HistoryEntry = {
1396
1378
 
1397
1379
  interface HistoryOptions = {
1398
1380
  id: unknown
1399
- limit?: number
1400
- resolveActor?: (id: number) => Promise<string | null> | string | null
1381
+ limit?: number | undefined
1382
+ resolveActor?: ((id: number) => Promise<string | null> | string | null) | undefined
1401
1383
  type: string
1402
1384
  }
1403
1385
 
@@ -1414,12 +1396,12 @@ interface ImportResult = {
1414
1396
  }
1415
1397
 
1416
1398
  interface ListOptions = {
1417
- modifyQuery?: QueryModifier
1418
- page?: number
1419
- perPage?: number
1399
+ modifyQuery?: QueryModifier | undefined
1400
+ page?: number | undefined
1401
+ perPage?: number | undefined
1420
1402
  search?: string | undefined
1421
1403
  sortBy?: string | undefined
1422
- sortDir?: 'asc' | 'desc'
1404
+ sortDir?: 'asc' | 'desc' | undefined
1423
1405
  thenSort?: readonly { column: string; direction: 'asc' | 'desc';}[] | undefined
1424
1406
  trashed?: 'with' | 'only' | undefined
1425
1407
  }
@@ -1492,22 +1474,6 @@ interface PageContribution = {
1492
1474
  title: string
1493
1475
  }
1494
1476
 
1495
- interface PanelPage = {
1496
- ability: string | undefined
1497
- cluster?: typeof Cluster | undefined
1498
- navigationBadge?: (() => Promise<string | number | null> | string | number | null) | undefined
1499
- navigationBadgeColor?: BadgeTone | undefined
1500
- navigationGroup: string | undefined
1501
- navigationIcon: string
1502
- navigationLabel: string
1503
- navigationSort: number
1504
- page: PanelPageClass
1505
- routeParams: string[]
1506
- showInNavigation: boolean
1507
- slug: string
1508
- title: string
1509
- }
1510
-
1511
1477
  interface PanelSearchProvider = {
1512
1478
  ability: string
1513
1479
  icon?: string
@@ -1527,19 +1493,6 @@ interface PivotColumn = {
1527
1493
  label?: string
1528
1494
  }
1529
1495
 
1530
- interface RecordPage = {
1531
- lastPage: number
1532
- page: number
1533
- perPage: number
1534
- rows: Record<string, unknown>[]
1535
- total: number
1536
- }
1537
-
1538
- interface RenderableCell = {
1539
- badge?: BadgeTone | undefined
1540
- text: string
1541
- }
1542
-
1543
1496
  interface RenderHookContext = {
1544
1497
  page?: 'list' | 'record' | 'form' | 'dashboard' | undefined
1545
1498
  recordId?: string | undefined
@@ -1597,10 +1550,10 @@ interface StoredNotification = {
1597
1550
  }
1598
1551
 
1599
1552
  interface StoreMediaOptions = {
1600
- accept?: string[]
1601
- disk?: string
1602
- folder?: string
1603
- maxBytes?: number
1553
+ accept?: string[] | undefined
1554
+ disk?: string | undefined
1555
+ folder?: string | undefined
1556
+ maxBytes?: number | undefined
1604
1557
  provider: MediaProvider
1605
1558
  }
1606
1559
 
@@ -1663,8 +1616,6 @@ type ActionVisible = (record: AdminRecord | undefined, ctx: ActionContext) => bo
1663
1616
 
1664
1617
  type AdminAuthorizer = (ability: string) => boolean | Promise<boolean>
1665
1618
 
1666
- type AdminPageClass = typeof AdminPage & (new () => AdminPage)
1667
-
1668
1619
  type BadgeTone = 'default' | 'primary' | 'success' | 'muted' | 'destructive'
1669
1620
 
1670
1621
  type CalloutTone = 'default' | 'primary' | 'success' | 'warning' | 'destructive'
@@ -1675,8 +1626,6 @@ type ChartResolver = () => Promise<ChartData> | ChartData
1675
1626
 
1676
1627
  type ChartType = 'line' | 'bar' | 'doughnut' | 'pie'
1677
1628
 
1678
- type ClusterClass = typeof Cluster
1679
-
1680
1629
  type ColumnKind = 'text' | 'toggle' | 'select' | 'input' | 'image' | 'color' | 'icon'
1681
1630
 
1682
1631
  type Conjunction = 'and' | 'or'
@@ -1711,8 +1660,6 @@ type FormComponent = FormSection | FormTabs | Wizard | FormSplit | Callout | Pri
1711
1660
 
1712
1661
  type InfolistComponent = Section | Entry
1713
1662
 
1714
- type PanelPageClass = new () => object
1715
-
1716
1663
  type PrimeKind = 'text' | 'html' | 'image'
1717
1664
 
1718
1665
  type QueryModifier = (query: AdminQuery) => AdminQuery
@@ -1727,8 +1674,6 @@ type RenderHookName = 'body.start' | 'body.end' | 'sidebar.start' | 'sidebar.end
1727
1674
 
1728
1675
  type ResourceClass = typeof Resource
1729
1676
 
1730
- type ResourceFormClass = new () => Form & Record<string, unknown>
1731
-
1732
1677
  type StatsResolver = () => Promise<Stat[]> | Stat[]
1733
1678
 
1734
1679
  type SummaryKind = 'sum' | 'avg' | 'count' | 'min' | 'max' | 'range'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zerotal/admin",
3
- "version": "1.8.1",
3
+ "version": "1.10.0",
4
4
  "license": "MIT",
5
5
  "maturity": "stable",
6
6
  "private": false,
@@ -32,16 +32,16 @@
32
32
  "typecheck": "tsc --noEmit"
33
33
  },
34
34
  "dependencies": {
35
- "@zerotal/cache": "1.8.1",
36
- "@zerotal/core": "1.8.1",
37
- "@zerotal/flow": "1.8.1",
38
- "@zerotal/flow-ui": "1.8.1",
39
- "@zerotal/validator": "1.8.1"
35
+ "@zerotal/cache": "1.10.0",
36
+ "@zerotal/core": "1.10.0",
37
+ "@zerotal/flow": "1.10.0",
38
+ "@zerotal/flow-ui": "1.10.0",
39
+ "@zerotal/validator": "1.10.0"
40
40
  },
41
41
  "devDependencies": {
42
- "@zerotal/orm": "1.8.1",
43
- "@zerotal/auth": "1.8.1",
44
- "@zerotal/queue": "1.8.1"
42
+ "@zerotal/orm": "1.10.0",
43
+ "@zerotal/auth": "1.10.0",
44
+ "@zerotal/queue": "1.10.0"
45
45
  },
46
46
  "peerDependencies": {
47
47
  "@zerotal/orm": "^1.0.0",
package/src/Cluster.ts CHANGED
@@ -46,5 +46,9 @@ export abstract class Cluster {
46
46
  }
47
47
  }
48
48
 
49
- /** A Cluster subclass (used by its static surface — never instantiated). */
49
+ /**
50
+ * A Cluster subclass (used by its static surface — never instantiated).
51
+ *
52
+ * @internal
53
+ */
50
54
  export type ClusterClass = typeof Cluster;
@@ -72,6 +72,8 @@ export interface NavGroup {
72
72
  * A registered custom page, normalized from either door — an {@link AdminPage}
73
73
  * subclass registered by the app, or a {@link PageContribution} pushed in by a
74
74
  * package.
75
+ *
76
+ * @internal
75
77
  */
76
78
  export interface PanelPage {
77
79
  slug: string;
package/src/Resource.ts CHANGED
@@ -114,6 +114,7 @@ export interface EmptyState {
114
114
  actions?: ActionItem[];
115
115
  }
116
116
 
117
+ /** @internal */
117
118
  export interface RecordPage {
118
119
  rows: Record<string, unknown>[];
119
120
  total: number;
@@ -123,13 +124,13 @@ export interface RecordPage {
123
124
  }
124
125
 
125
126
  export interface ListOptions {
126
- page?: number;
127
- perPage?: number;
127
+ page?: number | undefined;
128
+ perPage?: number | undefined;
128
129
  search?: string | undefined;
129
130
  sortBy?: string | undefined;
130
- sortDir?: "asc" | "desc";
131
+ sortDir?: "asc" | "desc" | undefined;
131
132
  /** Scope the base query before search/sort/pagination (e.g. the active tab). */
132
- modifyQuery?: QueryModifier;
133
+ modifyQuery?: QueryModifier | undefined;
133
134
  /** Soft-delete scope: include trashed (`with`) or only trashed (`only`). */
134
135
  trashed?: "with" | "only" | undefined;
135
136
  /**
@@ -25,7 +25,11 @@ import { flattenFields } from "../form/index.ts";
25
25
 
26
26
  export type ActionColor = "default" | "primary" | "success" | "muted" | "destructive";
27
27
 
28
- /** Minimal view of the host page an action handler can drive (flash/redirect). */
28
+ /**
29
+ * Minimal view of the host page an action handler can drive (flash/redirect).
30
+ *
31
+ * @internal
32
+ */
29
33
  export interface ActionPage {
30
34
  flash(message: string, level?: string): unknown;
31
35
  redirect(url: string): { withSuccess(message: string): unknown };
@@ -27,7 +27,11 @@ export interface RenderActionOptions {
27
27
  args?: unknown[];
28
28
  }
29
29
 
30
- /** Render a single action. Link actions become `<a navigate>`, callbacks `<button>`. */
30
+ /**
31
+ * Render a single action. Link actions become `<a navigate>`, callbacks `<button>`.
32
+ *
33
+ * @internal
34
+ */
31
35
  export function renderAction(
32
36
  a: Action,
33
37
  ctx: ActionContext,
@@ -92,6 +96,8 @@ export function renderAction(
92
96
  * Returns `null` when nothing inside is visible, so an empty menu never appears.
93
97
  * `argsFor` builds each member's handler arguments, since those carry the
94
98
  * action's own key.
99
+ *
100
+ * @internal
95
101
  */
96
102
  export function renderActionGroup(
97
103
  group: ActionGroup,
@@ -128,6 +134,8 @@ export function renderActionGroup(
128
134
  /**
129
135
  * Render an action as a full-width dropdown menu row (icon + label). Used by the
130
136
  * row-action overflow menu when a row has more actions than fit inline.
137
+ *
138
+ * @internal
131
139
  */
132
140
  export function renderActionMenuItem(
133
141
  a: Action,
@@ -7,6 +7,7 @@ import type { HtmlNode } from "@zerotal/flow";
7
7
  import { Panel } from "../Panel.ts";
8
8
  import { adminHead } from "../theme.ts";
9
9
 
10
+ /** @internal */
10
11
  export class AuthLayout extends Layout {
11
12
  static override get head(): string {
12
13
  const cfg = Panel.config();
package/src/authRoles.ts CHANGED
@@ -24,7 +24,7 @@ export interface AuthRolesOptions {
24
24
  * Guard the roles belong to. Defaults to `"web"`, matching the framework's
25
25
  * own default, so most apps need not think about it.
26
26
  */
27
- guard?: string;
27
+ guard?: string | undefined;
28
28
  /**
29
29
  * Role names that hold every permission.
30
30
  *
@@ -32,7 +32,7 @@ export interface AuthRolesOptions {
32
32
  * something the data can tell you. A superuser role is shown as holding
33
33
  * everything and cannot be edited or deleted from the panel.
34
34
  */
35
- superusers?: string[];
35
+ superusers?: string[] | undefined;
36
36
  }
37
37
 
38
38
  /** The role surface this needs, kept structural so `auth` stays optional. */
package/src/config.ts CHANGED
@@ -26,37 +26,46 @@ export interface UserMenu {
26
26
  */
27
27
  export interface AdminAuthConfig {
28
28
  /** Mount the auth pages. */
29
- enabled?: boolean;
29
+ enabled?: boolean | undefined;
30
30
  /** Login route (relative to the panel path). Default `/login`. */
31
- loginPath?: string;
31
+ loginPath?: string | undefined;
32
32
  /** Profile route (relative to the panel path). Default `/profile`. */
33
- profilePath?: string;
33
+ profilePath?: string | undefined;
34
34
  /** Where to send the user after a successful login. Default the panel root. */
35
- redirectTo?: string;
35
+ redirectTo?: string | undefined;
36
36
  /** The credential column users log in with. Default `"email"`. */
37
- identifier?: string;
37
+ identifier?: string | undefined;
38
38
  /** Show a "remember me" checkbox. Default `true`. */
39
- remember?: boolean;
39
+ remember?: boolean | undefined;
40
40
  /** Heading on the auth screens. Defaults to the panel brand. */
41
- heading?: string;
41
+ heading?: string | undefined;
42
42
  /** Extra gate after the password check (e.g. "is the account active?"). */
43
- authenticateWhen?: (user: unknown) => boolean | Promise<boolean>;
43
+ authenticateWhen?: ((user: unknown) => boolean | Promise<boolean>) | undefined;
44
44
  /** Persist profile edits (name/email). Defaults to `user.fill(data); user.save()`. */
45
- updateProfile?: (user: unknown, data: Record<string, unknown>) => Promise<void> | void;
45
+ updateProfile?:
46
+ ((user: unknown, data: Record<string, unknown>) => Promise<void> | void) | undefined;
46
47
  /** Middleware guarding the guest screens (login/forgot/reset). Default none. */
47
- guestMiddleware?: MiddlewareClass[];
48
+ guestMiddleware?: MiddlewareClass[] | undefined;
48
49
  /** Middleware guarding the profile/verify screens. Defaults to the panel guard. */
49
- authMiddleware?: MiddlewareClass[];
50
+ authMiddleware?: MiddlewareClass[] | undefined;
50
51
  /** Password-reset broker — mounting the forgot/reset pages when provided. */
51
- passwordReset?: {
52
- sendResetLink(email: string): Promise<boolean> | boolean;
53
- reset(input: { email: string; token: string; password: string }): Promise<boolean> | boolean;
54
- };
52
+ passwordReset?:
53
+ | {
54
+ sendResetLink(email: string): Promise<boolean> | boolean;
55
+ reset(input: {
56
+ email: string;
57
+ token: string;
58
+ password: string;
59
+ }): Promise<boolean> | boolean;
60
+ }
61
+ | undefined;
55
62
  /** Email-verification hooks — mounting the verify page when provided. */
56
- emailVerification?: {
57
- isVerified(user: unknown): boolean;
58
- resend(user: unknown): Promise<void> | void;
59
- };
63
+ emailVerification?:
64
+ | {
65
+ isVerified(user: unknown): boolean;
66
+ resend(user: unknown): Promise<void> | void;
67
+ }
68
+ | undefined;
60
69
  }
61
70
 
62
71
  /** Admin panel configuration (read from `config/admin.ts`, with defaults). */
@@ -66,7 +75,7 @@ export interface AdminConfigShape {
66
75
  /** Brand name shown in the sidebar. */
67
76
  brand: string;
68
77
  /** Optional short tagline under the brand. */
69
- tagline?: string;
78
+ tagline?: string | undefined;
70
79
  /**
71
80
  * Middleware guarding every panel route. When left empty, the panel is
72
81
  * default-denied in production-like environments (fail closed) and open only
@@ -75,13 +84,13 @@ export interface AdminConfigShape {
75
84
  * `[AuthMiddleware.with({ ... })]`. To deliberately expose it without auth,
76
85
  * pass an explicit pass-through middleware.
77
86
  */
78
- middleware?: MiddlewareClass[];
87
+ middleware?: MiddlewareClass[] | undefined;
79
88
  /** Optional top-bar user menu (Profile / Logout / …). */
80
- userMenu?: UserMenu;
89
+ userMenu?: UserMenu | undefined;
81
90
  /** Styling source — Tailwind Play CDN (default) or a prebuilt stylesheet. */
82
- theme?: AdminThemeConfig;
91
+ theme?: AdminThemeConfig | undefined;
83
92
  /** Auth pages (login / profile / password-reset / email-verification). */
84
- auth?: AdminAuthConfig;
93
+ auth?: AdminAuthConfig | undefined;
85
94
  /**
86
95
  * Decide the abilities named by pages, widgets, nav entries and search
87
96
  * providers. Set this when the app models permissions itself; leave it unset to
@@ -90,13 +99,13 @@ export interface AdminConfigShape {
90
99
  * With neither configured, every ability is denied outside a development
91
100
  * environment — a panel with no authorization wired stays closed in production.
92
101
  */
93
- authorize?: AdminAuthorizer;
102
+ authorize?: AdminAuthorizer | undefined;
94
103
  /**
95
104
  * Switch contributing packages off by id — `{ monitor: false }` keeps the
96
105
  * monitor provider installed but drops its pages, widgets and nav entries from
97
106
  * the panel. Anything absent here is on.
98
107
  */
99
- plugins?: Record<string, boolean>;
108
+ plugins?: Record<string, boolean> | undefined;
100
109
  }
101
110
 
102
111
  /**
@@ -21,9 +21,9 @@ import type { MediaItem, MediaProvider } from "./media.ts";
21
21
 
22
22
  export interface DatabaseMediaOptions {
23
23
  /** Table holding the catalogue. Defaults to `"media"`. */
24
- table?: string;
24
+ table?: string | undefined;
25
25
  /** Map the panel's fields onto your column names. */
26
- columns?: Partial<Record<keyof MediaItem, string>>;
26
+ columns?: Partial<Record<keyof MediaItem, string>> | undefined;
27
27
  }
28
28
 
29
29
  const DEFAULT_COLUMNS: Record<keyof MediaItem, string> = {
@@ -32,21 +32,22 @@ export interface StoredNotification {
32
32
 
33
33
  export interface DatabaseNotificationOptions {
34
34
  /** Table holding the notifications. Defaults to `"notifications"`. */
35
- table?: string;
35
+ table?: string | undefined;
36
36
  /**
37
37
  * Who the bell is showing. Defaults to the signed-in user from
38
38
  * `@zerotal/auth`; returning `null` shows nothing, which is the right answer
39
39
  * for a guest.
40
40
  */
41
- notifiable?: () => Promise<unknown> | unknown;
41
+ notifiable?: (() => Promise<unknown> | unknown) | undefined;
42
42
  /** How many to show. Defaults to 20 — a bell is not an archive. */
43
- limit?: number;
43
+ limit?: number | undefined;
44
44
  /**
45
45
  * Turn a stored row into what the panel renders. The default reads `title`,
46
46
  * `body`/`message`, `url`/`href` and `icon` out of the payload, which is what
47
47
  * most notifications carry.
48
48
  */
49
- present?: (row: StoredNotification, data: Record<string, unknown>) => AdminNotification;
49
+ present?:
50
+ ((row: StoredNotification, data: Record<string, unknown>) => AdminNotification) | undefined;
50
51
  }
51
52
 
52
53
  /** Parse a row's JSON payload, tolerating a payload that was never JSON. */
@@ -16,6 +16,7 @@ import type { RuleBuilder, FieldRule } from "@zerotal/validator";
16
16
  import type { Field, FieldMode } from "./Field.ts";
17
17
  import { type FormComponent, flattenFields } from "./Section.ts";
18
18
 
19
+ /** @internal */
19
20
  export type ResourceFormClass = new () => Form & Record<string, unknown>;
20
21
 
21
22
  /** @internal */
package/src/history.ts CHANGED
@@ -50,9 +50,9 @@ export interface HistoryOptions {
50
50
  type: string;
51
51
  id: unknown;
52
52
  /** How many entries to show. Defaults to 25 — a record page is not an archive. */
53
- limit?: number;
53
+ limit?: number | undefined;
54
54
  /** Turn an actor id into a name. Defaults to `#<id>`. */
55
- resolveActor?: (id: number) => Promise<string | null> | string | null;
55
+ resolveActor?: ((id: number) => Promise<string | null> | string | null) | undefined;
56
56
  }
57
57
 
58
58
  /** Fields that change on every write and say nothing about intent. */
package/src/media.ts CHANGED
@@ -220,12 +220,12 @@ export function isUpload(value: unknown): value is UploadedFileLike {
220
220
  export interface StoreMediaOptions {
221
221
  provider: MediaProvider;
222
222
  /** Disk to write to; the default disk when omitted. */
223
- disk?: string;
224
- folder?: string;
223
+ disk?: string | undefined;
224
+ folder?: string | undefined;
225
225
  /** Refuse anything larger, in bytes. */
226
- maxBytes?: number;
226
+ maxBytes?: number | undefined;
227
227
  /** Accepted MIME types; anything goes when omitted. */
228
- accept?: string[];
228
+ accept?: string[] | undefined;
229
229
  }
230
230
 
231
231
  /**
@@ -96,5 +96,7 @@ export abstract class AdminPage extends Component {
96
96
  /**
97
97
  * A concrete {@link AdminPage} subclass — the static metadata above, plus the
98
98
  * zero-argument constructor Flow builds the page with.
99
+ *
100
+ * @internal
99
101
  */
100
102
  export type AdminPageClass = typeof AdminPage & (new () => AdminPage);
@@ -36,6 +36,7 @@ const ALIGN: Record<string, string> = {
36
36
  end: "text-right tabular-nums",
37
37
  };
38
38
 
39
+ /** @internal */
39
40
  export class ConsolePage extends Component {
40
41
  static layout = AdminLayout;
41
42
 
@@ -304,6 +305,8 @@ function cell(c: ConsoleColumn, row: ConsoleRow): HtmlNode | string {
304
305
  * shared class resolving itself from the route — keeps the console on a static
305
306
  * and matches how resource list pages are generated. The explicit name matters:
306
307
  * Flow's component registry is keyed by constructor name.
308
+ *
309
+ * @internal
307
310
  */
308
311
  export function makeConsolePage(
309
312
  console: ConsoleContribution,
@@ -253,7 +253,11 @@ export class DashboardPage extends Component {
253
253
  }
254
254
  }
255
255
 
256
- /** The dashboard for one panel, showing only that panel's resources and widgets. */
256
+ /**
257
+ * The dashboard for one panel, showing only that panel's resources and widgets.
258
+ *
259
+ * @internal
260
+ */
257
261
  export function makeDashboardPage(panel: PanelInstance = Panel.default()): typeof DashboardPage {
258
262
  const Page = class extends DashboardPage {
259
263
  static override panel = panel;
@@ -15,6 +15,7 @@ import { deleteMedia, formatSize, isImage, isUpload, mediaUrl, storeMedia } from
15
15
  /** How many files the grid shows before the "load more" step. */
16
16
  const PAGE_SIZE = 60;
17
17
 
18
+ /** @internal */
18
19
  export class MediaPage extends Component {
19
20
  static layout = AdminLayout;
20
21
  /** The panel this page belongs to — set by each generated subclass. */
@@ -338,7 +339,11 @@ export class MediaPage extends Component {
338
339
  }
339
340
  }
340
341
 
341
- /** Build a MediaPage bound to one panel. */
342
+ /**
343
+ * Build a MediaPage bound to one panel.
344
+ *
345
+ * @internal
346
+ */
342
347
  export function makeMediaPage(panel: PanelInstance = Panel.default()): typeof MediaPage {
343
348
  return class BoundMediaPage extends MediaPage {
344
349
  static override panel = panel;
@@ -10,6 +10,7 @@ import { Icon } from "../ui/icons.tsx";
10
10
  import { Panel } from "../Panel.ts";
11
11
  import type { PanelInstance } from "../PanelInstance.ts";
12
12
 
13
+ /** @internal */
13
14
  export class NotificationsPage extends Component {
14
15
  static layout = AdminLayout;
15
16
  /** The panel this page belongs to — set by each generated subclass. */
@@ -142,7 +143,11 @@ export class NotificationsPage extends Component {
142
143
  }
143
144
  }
144
145
 
145
- /** The notification centre for one panel, reading that panel's provider. */
146
+ /**
147
+ * The notification centre for one panel, reading that panel's provider.
148
+ *
149
+ * @internal
150
+ */
146
151
  export function makeNotificationsPage(
147
152
  panel: PanelInstance = Panel.default(),
148
153
  ): typeof NotificationsPage {
@@ -95,6 +95,7 @@ interface PivotProxy {
95
95
  all?: () => unknown;
96
96
  }
97
97
 
98
+ /** @internal */
98
99
  export class RecordViewPage extends Component {
99
100
  static layout = AdminLayout;
100
101
  /** Set by each generated subclass. */
@@ -925,6 +926,8 @@ export class RecordViewPage extends Component {
925
926
  /**
926
927
  * Build a uniquely-named View page subclass bound to a resource — mirrors
927
928
  * {@link makeResourceListPage} so Flow keeps component identity stable.
929
+ *
930
+ * @internal
928
931
  */
929
932
  export function makeRecordViewPage(
930
933
  resource: ResourceClass,
@@ -63,7 +63,11 @@ const _formRegistry = new Map<string, FormMeta>();
63
63
 
64
64
  const formKey = (panelId: string, slug: string): string => `${panelId}:${slug}`;
65
65
 
66
- /** Register a resource's form config so the shared page can resolve it. */
66
+ /**
67
+ * Register a resource's form config so the shared page can resolve it.
68
+ *
69
+ * @internal
70
+ */
67
71
  export function registerResourceForm(panelId: string, slug: string, meta: FormMeta): void {
68
72
  _formRegistry.set(formKey(panelId, slug), meta);
69
73
  }
@@ -72,6 +76,7 @@ const SPAN_CLASS = ["", "", "sm:col-span-2", "sm:col-span-3", "sm:col-span-4"];
72
76
  const INPUT_CLASS =
73
77
  "mt-1.5 block w-full rounded-lg border border-input bg-background px-3 py-2 text-sm text-foreground shadow-sm outline-none transition placeholder:text-muted-foreground focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-background disabled:cursor-not-allowed disabled:opacity-60";
74
78
 
79
+ /** @internal */
75
80
  export class ResourceFormPage extends Component {
76
81
  @locked slug = "";
77
82
  @locked mode: FieldMode = "create";
@@ -104,6 +104,7 @@ function parseFilters(s: string): Record<string, string> {
104
104
  }
105
105
  }
106
106
 
107
+ /** @internal */
107
108
  export class ResourceListPage extends Component {
108
109
  static layout = AdminLayout;
109
110
  /** Set by each generated subclass. */
@@ -2535,6 +2536,8 @@ export class ResourceListPage extends Component {
2535
2536
  /**
2536
2537
  * Build a uniquely-named List page subclass bound to a resource. The distinct
2537
2538
  * class name keeps Flow's snapshot/component identity stable per resource.
2539
+ *
2540
+ * @internal
2538
2541
  */
2539
2542
  export function makeResourceListPage(
2540
2543
  resource: ResourceClass,
@@ -12,6 +12,7 @@ import type { PanelInstance } from "../PanelInstance.ts";
12
12
  import type { Permission, Role } from "../roles.ts";
13
13
  import { groupPermissions, panelPermissions, roleHas } from "../roles.ts";
14
14
 
15
+ /** @internal */
15
16
  export class RolesPage extends Component {
16
17
  static layout = AdminLayout;
17
18
  /** The panel this page belongs to — set by each generated subclass. */
@@ -317,7 +318,11 @@ export class RolesPage extends Component {
317
318
  }
318
319
  }
319
320
 
320
- /** Build a RolesPage bound to one panel. */
321
+ /**
322
+ * Build a RolesPage bound to one panel.
323
+ *
324
+ * @internal
325
+ */
321
326
  export function makeRolesPage(panel: PanelInstance = Panel.default()): typeof RolesPage {
322
327
  return class BoundRolesPage extends RolesPage {
323
328
  static override panel = panel;
@@ -158,7 +158,11 @@ export class SearchPage extends Component {
158
158
  }
159
159
  }
160
160
 
161
- /** The search page for one panel, searching only that panel's resources. */
161
+ /**
162
+ * The search page for one panel, searching only that panel's resources.
163
+ *
164
+ * @internal
165
+ */
162
166
  export function makeSearchPage(panel: PanelInstance = Panel.default()): typeof SearchPage {
163
167
  const Page = class extends SearchPage {
164
168
  static override panel = panel;
package/src/plugin.ts CHANGED
@@ -40,6 +40,8 @@ import type { RenderHookContext } from "./renderHooks.ts";
40
40
  * over without importing anything from `@zerotal/admin`. The panel gives the page
41
41
  * its own layout when it mounts the route, so a contributed page renders only its
42
42
  * content and inherits the panel's chrome.
43
+ *
44
+ * @internal
43
45
  */
44
46
  export type PanelPageClass = new () => object;
45
47
 
@@ -245,6 +247,8 @@ export interface UserMenuContribution {
245
247
  * Contributors should declare their own minimal copy of the members they use
246
248
  * rather than importing this type, so they depend on the admin package at build
247
249
  * time not at all.
250
+ *
251
+ * @internal
248
252
  */
249
253
  export interface AdminPanelHost {
250
254
  /**
@@ -37,6 +37,7 @@ export interface SummaryResult {
37
37
  text: string;
38
38
  }
39
39
 
40
+ /** @internal */
40
41
  export interface RenderableCell {
41
42
  /** Pre-escaped/plain text, or a badge descriptor. */
42
43
  text: string;
package/src/theme.ts CHANGED
@@ -38,13 +38,13 @@ export const THEME_TOGGLE_SCRIPT = FLOW_THEME_TOGGLE_SCRIPT;
38
38
  */
39
39
  export interface AdminThemeConfig {
40
40
  /** A prebuilt stylesheet URL/path to link instead of (or alongside) the CDN. */
41
- stylesheet?: string;
41
+ stylesheet?: string | undefined;
42
42
  /** Keep loading the Tailwind Play CDN. Defaults to `true` unless `stylesheet` is set. */
43
- cdn?: boolean;
43
+ cdn?: boolean | undefined;
44
44
  /** Extra design-token CSS appended after the defaults (override `:root` / `.dark`). */
45
- tokensCss?: string;
45
+ tokensCss?: string | undefined;
46
46
  /** Skip the bundled Google Fonts links (e.g. you self-host Inter). */
47
- noFonts?: boolean;
47
+ noFonts?: boolean | undefined;
48
48
  }
49
49
 
50
50
  /** The design-token CSS (`:root` + `.dark` custom properties + base styles). */
@@ -15,6 +15,7 @@ import { Icon } from "./icons.tsx";
15
15
  import { Command, Toaster, Sidebar } from "@zerotal/flow-ui";
16
16
  import type { CommandItem, SidebarItem, SidebarGroup } from "@zerotal/flow-ui";
17
17
 
18
+ /** @internal */
18
19
  export class AdminLayout extends Layout {
19
20
  /**
20
21
  * The panel this shell belongs to. The base class serves whichever panel owns
@@ -335,6 +336,8 @@ export class AdminLayout extends Layout {
335
336
  * The router keys shell persistence on the layout class name, so each panel needs
336
337
  * a class of its own: without that, navigating from one panel to another would
337
338
  * swap the content while leaving the previous panel's sidebar in place.
339
+ *
340
+ * @internal
338
341
  */
339
342
  export function makeAdminLayout(panel: PanelInstance): typeof AdminLayout {
340
343
  const cached = LAYOUTS.get(panel);
@@ -45,6 +45,8 @@ export function chartInitScript(configs: unknown[]): string {
45
45
  *
46
46
  * Returns `null` when there is nothing to draw, so a caller can drop the whole
47
47
  * block rather than leaving an empty gap above its table.
48
+ *
49
+ * @internal
48
50
  */
49
51
  export async function renderWidgets(
50
52
  widgets: DashboardWidget[],