@ultimat3/admin 30.8.0 → 31.0.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/src/index.ts CHANGED
@@ -2,13 +2,10 @@
2
2
  // Explicit exports only — a barrel that re-exports everything is how internal helpers become
3
3
  // someone's dependency.
4
4
 
5
- export { AdminActionForm, type AdminActionFormProps } from './action-form';
6
5
  export {
7
- ACTION_NOT_APPLICABLE_REASON,
8
6
  type ActionGateInput,
9
7
  type AdminActionButton,
10
8
  actionButtons,
11
- actionDecisions,
12
9
  adminPermissionForAction,
13
10
  decideAction,
14
11
  type InvokeInput,
@@ -16,30 +13,9 @@ export {
16
13
  invokeAdminAction,
17
14
  permissionsForAction,
18
15
  } from './action-gate';
19
- export {
20
- ACTION_INPUT_PREFIX,
21
- type ActionInputControl,
22
- type ActionInputField,
23
- actionInputFields,
24
- decodeActionInput,
25
- } from './action-input';
26
16
  export { invokeRowAction, type RowActionInput } from './action-row';
27
- export {
28
- ACTION_OPERATION,
29
- ACTION_PARAM,
30
- AdminActions,
31
- type AdminActionsProps,
32
- actionFormHref,
33
- BATCH_OPERATION,
34
- DELETE_OPERATION,
35
- OPERATION_FIELD,
36
- } from './actions';
37
- export {
38
- type AdminRequestActor,
39
- ANONYMOUS_ADMIN_ACTOR,
40
- adminActorFrom,
41
- requestActor,
42
- } from './actor';
17
+ export { AdminActions, type AdminActionsProps } from './actions';
18
+ export { adminActorFrom } from './actor';
43
19
  export {
44
20
  type AdminApp,
45
21
  type AdminAuth,
@@ -58,12 +34,9 @@ export {
58
34
  type AiPanesOptions,
59
35
  type AiRunner,
60
36
  aiPanes,
61
- type GatewayAdapter,
62
37
  runAiPane,
63
38
  } from './ai-panes';
64
39
  export {
65
- AUDIT_PAGE_DEFAULT,
66
- AUDIT_PAGE_MAX,
67
40
  type AuditCursor,
68
41
  type AuditDraft,
69
42
  type AuditEntry,
@@ -72,25 +45,15 @@ export {
72
45
  type AuditLogOptions,
73
46
  type AuditOutcome,
74
47
  type AuditQuery,
75
- type AuditSink,
76
48
  auditCursorOf,
77
- auditEntry,
78
49
  auditEntryMatches,
79
50
  auditWindow,
80
51
  deniedDraft,
81
52
  diffRows,
82
53
  memoryAuditLog,
83
54
  } from './audit';
84
- export {
85
- type PostgresAuditLogOptions,
86
- postgresAuditLog,
87
- } from './audit-pg';
88
- export {
89
- type AuditRequest,
90
- auditHref,
91
- auditRequestOf,
92
- HISTORY_PARAM,
93
- } from './audit-request';
55
+ export { type PostgresAuditLogOptions, postgresAuditLog } from './audit-pg';
56
+ export { type AuditRequest, auditHref, auditRequestOf, HISTORY_PARAM } from './audit-request';
94
57
  // The audit table's DDL is `@ultimat3/admin/schema`'s alone — one import path for one statement.
95
58
  export {
96
59
  type AdminActor,
@@ -100,36 +63,23 @@ export {
100
63
  type AdminSubject,
101
64
  adminAllowed,
102
65
  adminDenied,
103
- anonymousAuthz,
104
66
  decideAll,
105
67
  expandPermissions,
106
- isAllowed,
107
68
  staticAuthz,
108
69
  } from './authz';
109
70
  export {
110
71
  type AdminBatchAnswer,
111
72
  type AdminBatchInput,
112
73
  type AdminBatchResult,
113
- BATCH_NOT_OFFERED_REASON,
114
74
  type BatchEnqueue,
115
75
  type BatchRow,
116
76
  type BatchRowOutcome,
117
77
  type BatchSelection,
118
- batchConfirmationToken,
119
78
  batchPlan,
120
79
  MAX_BATCH_QUEUED_ROWS,
121
80
  MAX_BATCH_ROWS,
122
81
  runAdminBatch,
123
82
  } from './batch';
124
- export {
125
- AdminBatchBar,
126
- type AdminBatchBarProps,
127
- } from './batch-bar';
128
- export {
129
- ADMIN_BATCH_JOB,
130
- batchEnqueue,
131
- } from './batch-job';
132
- export { BATCH_MATCHING_REASON, matchingConfirmationToken } from './batch-matching';
133
83
  export { BATCH_QUEUED_REASON } from './batch-queue';
134
84
  export {
135
85
  type AdminClaimData,
@@ -151,37 +101,26 @@ export {
151
101
  type ListResult,
152
102
  permissionsForOperation,
153
103
  } from './crud';
154
- export {
155
- type AdminActionDescription,
156
- type AdminDescription,
157
- type AdminResourceDescription,
158
- type AdminRouteDescription,
159
- type AdminScopeDescription,
160
- type AdminSectionDescription,
161
- describeAdmin,
104
+ export type {
105
+ AdminActionDescription,
106
+ AdminDescription,
107
+ AdminResourceDescription,
108
+ AdminRouteDescription,
109
+ AdminScopeDescription,
110
+ AdminSectionDescription,
162
111
  } from './describe';
163
- export {
164
- AdminDetail,
165
- type AdminDetailProps,
166
- } from './detail';
167
- export {
168
- type AdminColumnFacts,
169
- type AdminColumnReference,
170
- adminColumnsOf,
171
- adminSealedColumnsOf,
172
- } from './entity-columns';
112
+ export { AdminDetail, type AdminDetailProps } from './detail';
113
+ export type { AdminColumnFacts, AdminColumnReference } from './entity-columns';
173
114
  // The screens only. The sink and its stores are `@ultimat3/admin/error-groups`'s alone: every role
174
115
  // installs the sink, and this barrel would carry every admin screen into a worker's graph.
175
116
  export { ERRORS_READ, type ErrorGroupsPagesOptions, errorGroupsPages } from './error-groups-pages';
117
+ export type { AdminErrorCode } from './errors';
176
118
  // The /_x dashboard is NOT re-exported here — it has its own door, `@ultimat3/admin/dev`, so a
177
119
  // host that only mounts the dev panels never loads a production admin component.
178
120
  export {
179
- ADMIN_ERROR_CODES,
180
- ADMIN_ERROR_TITLES,
181
121
  AdminActionDuplicateError,
182
122
  AdminActionNotApplicableError,
183
123
  AdminEntityUnknownError,
184
- type AdminErrorCode,
185
124
  type AdminErrorParts,
186
125
  AdminFieldUnsupportedError,
187
126
  AdminFilterInvalidError,
@@ -190,7 +129,6 @@ export {
190
129
  AdminPageUnguardedError,
191
130
  AdminPolicyMissingError,
192
131
  AdminRepoUnboundError,
193
- adminErrorFrom,
194
132
  DevDashboardInProdError,
195
133
  DevSourceUnavailableError,
196
134
  } from './errors';
@@ -199,59 +137,21 @@ export {
199
137
  type AdminFieldType,
200
138
  type AdminFilterKind,
201
139
  type AdminWidget,
202
- fieldTypeFromColumn,
203
140
  filterable,
204
- filterKindFor,
205
141
  listable,
206
142
  searchable,
207
143
  sortable,
208
- WIDGET_BY_FIELD_TYPE,
209
- widgetFor,
210
144
  } from './fields';
211
- export { AdminForm, type AdminFormProps, issueText } from './form';
212
- export { currencyFieldOf, decodeForm, posted } from './form-decode';
145
+ export { AdminForm, type AdminFormProps } from './form';
146
+ export { posted } from './form-decode';
213
147
  export { JOB_MANAGE, JOB_READ } from './jobs/job-actions';
214
- export { JOB_ENTITY } from './jobs/job-entities';
215
- export { JOBS_PATH, jobRowScope } from './jobs/job-resources';
148
+ export { jobRowScope } from './jobs/job-resources';
216
149
  export { AdminLayout, type AdminLayoutProps, adminActorLabel } from './layout';
217
150
  export { AdminList, type AdminListProps } from './list';
218
- export { type ResourceColumnsInput, resourceColumns } from './list-columns';
219
- export { AdminFilterBar, type AdminFilterBarProps } from './list-filter-bar';
220
- export {
221
- type AskedFilter,
222
- checkedFilter,
223
- declaredFilters,
224
- FILTER_PREFIX,
225
- filterOpsOf,
226
- filterParam,
227
- knownFilterParams,
228
- } from './list-filters';
229
- export {
230
- CURSOR_PARAM,
231
- type ListLocation,
232
- listHref,
233
- pageRequestOf,
234
- SCOPE_PARAM,
235
- SORT_PARAM,
236
- } from './list-request';
237
- export {
238
- type AdminListRequest,
239
- findRow,
240
- type ListWhere,
241
- listWhere,
242
- rowWhere,
243
- scopeCounts,
244
- scopeNamed,
245
- } from './list-scope';
246
- export { AdminScopeTabs, type AdminScopeTabsProps } from './list-scope-tabs';
247
- export {
248
- type AdminMcpActor,
249
- type AdminMcpOptions,
250
- type AdminToolResult,
251
- adminMcp,
252
- callAdminTool,
253
- type McpInput,
254
- } from './mcp';
151
+ export { type ListLocation, listHref, pageRequestOf } from './list-request';
152
+ export { type AdminListRequest, findRow, type ListWhere, listWhere } from './list-scope';
153
+ export { type AdminMcpActor, type AdminMcpOptions, adminMcp } from './mcp';
154
+ export { type AdminToolResult, callAdminTool, type McpInput } from './mcp-dispatch';
255
155
  export { adminTokenScopes, adminToolScopes } from './mcp-scopes';
256
156
  export {
257
157
  type AdminMcpTool,
@@ -259,40 +159,21 @@ export {
259
159
  type AdminToolKind,
260
160
  adminMcpTools,
261
161
  adminToolCatalog,
262
- adminToolDecisions,
263
162
  } from './mcp-tools';
264
- export {
265
- ADMIN_MOUNTS,
266
- adminMountAt,
267
- adminMounts,
268
- clearAdminMounts,
269
- registerAdminMount,
270
- } from './mounts';
271
- export { adminNav, type NavGroup, type NavItem, type NavOptions, visibleNav } from './nav';
272
- export { AdminPageDenied, auditRefusal } from './page-guard';
163
+ export { ADMIN_MOUNTS, adminMounts, clearAdminMounts } from './mounts';
164
+ export { type NavGroup, type NavItem, type NavOptions, visibleNav } from './nav';
165
+ export { AdminPageDenied } from './page-guard';
273
166
  export {
274
167
  type AdminCustomPage,
275
168
  type AdminPageComponent,
276
169
  type AdminPageProps,
277
- pageNavItems,
278
170
  pagePermissions,
279
171
  pageRoutes,
280
172
  } from './pages';
281
- export {
282
- type AdminCursor,
283
- type AdminPage,
284
- decodeAdminCursor,
285
- encodeAdminCursor,
286
- fetchPage,
287
- listQuery,
288
- type PageRequest,
289
- pageFrom,
290
- } from './pagination';
173
+ export { type AdminCursor, type AdminPage, type PageRequest, pageFrom } from './pagination';
291
174
  export {
292
175
  ADMIN_DESTROY,
293
- ADMIN_IMPERSONATE,
294
176
  ADMIN_OPERATION_RULES,
295
- ADMIN_OPERATIONS,
296
177
  ADMIN_PERMISSION_SPEC,
297
178
  ADMIN_PERMISSIONS,
298
179
  ADMIN_READ,
@@ -300,8 +181,6 @@ export {
300
181
  type AdminOperation,
301
182
  type AdminPermission,
302
183
  type AdminPermissionRule,
303
- adminPermissionFor,
304
- CONFIRMATION_REQUIRED_REASON,
305
184
  confirmationToken,
306
185
  entityPermissionFor,
307
186
  isDestructive,
@@ -333,64 +212,38 @@ export {
333
212
  type AdminSort,
334
213
  type AdminTable,
335
214
  type AdminTableRead,
336
- computedRow,
337
- FILTER_OPS,
338
215
  type FilterOp,
339
216
  type KeysetBound,
340
- type RegisteredEntity,
341
- type RegisteredTable,
342
217
  readField,
343
218
  rowId,
344
219
  } from './registry';
220
+ export { type AdminRelated, type AdminRelatedList, relatedLists, relatedOf } from './related';
345
221
  export {
346
- type AdminRelated,
347
- type AdminRelatedList,
348
- RELATED_PAGE_SIZE,
349
- relatedLists,
350
- relatedOf,
351
- } from './related';
352
- export {
353
- type AdminLookupRequest,
354
- type AdminLookupResult,
355
222
  type AdminOption,
356
- adminLookup,
357
- LOOKUP_SELECT_MAX,
358
223
  labelOf,
359
224
  type RelationData,
360
225
  type RelationNeed,
361
226
  relationDataFor,
362
- relationNeeds,
363
227
  } from './relations';
364
- export { ComputedCell, TRUNCATE_AT } from './renderers';
365
- export { adminEntitiesOf, adminRepoFor, adminTablesOf } from './repo-entity';
366
- export {
367
- type AdminResource,
368
- type AdminResourceOptions,
369
- adminResource,
370
- repoOf,
371
- resourceFor,
372
- } from './resource';
228
+ export { adminEntitiesOf, adminRepoFor } from './repo-entity';
229
+ export { type AdminResource, type AdminResourceOptions, adminResource, repoOf } from './resource';
373
230
  export type { AdminFieldOverride } from './resource-fields';
374
- export {
375
- type AdminFormGroupOptions,
376
- type AdminSection,
377
- type AdminSectionOptions,
378
- DEFAULT_SECTION_KEY,
231
+ export type {
232
+ AdminFormGroupOptions,
233
+ AdminSection,
234
+ AdminSectionOptions,
379
235
  } from './resource-layout';
380
- export {
381
- ADMIN_RENDERERS,
382
- type AdminColumnComponent,
383
- type AdminColumnRenderProps,
384
- type AdminComputedColumn,
385
- type AdminComputedColumnOptions,
386
- type AdminRenderer,
387
- type AdminRowScope,
388
- type AdminScope,
389
- type AdminScopeOptions,
390
- type AdminScopeWhere,
391
- LOOKUP_SEGMENT,
236
+ export type {
237
+ AdminColumnComponent,
238
+ AdminColumnRenderProps,
239
+ AdminComputedColumn,
240
+ AdminComputedColumnOptions,
241
+ AdminRenderer,
242
+ AdminRowScope,
243
+ AdminScope,
244
+ AdminScopeOptions,
245
+ AdminScopeWhere,
392
246
  } from './resource-list';
393
- export { AdminViewWriteError, VIEW_OPERATIONS } from './resource-operations';
394
247
  export {
395
248
  type AdminRouteConfig,
396
249
  type AdminRouteMatch,
@@ -399,17 +252,13 @@ export {
399
252
  adminRouteMatch,
400
253
  adminRoutes,
401
254
  } from './routes';
402
- export { ROW_OUT_OF_SCOPE_REASON } from './row-scope-write';
403
255
  export { ROW_CHANGED_REASON, rowVersion, VERSION_FIELD } from './row-version';
404
256
  export {
405
257
  type AdminRouteRequest,
406
258
  type AdminRouteResponse,
407
259
  type AdminScreen,
408
260
  guardedScreen,
409
- lookupHref,
410
- widgetContextOf,
411
261
  } from './screen-frame';
412
- export { type OperationDecision, OperationMatrix, operationMatrix } from './screen-home';
413
262
  export {
414
263
  type AdminSearchHit,
415
264
  type AdminSearchInput,
@@ -417,23 +266,12 @@ export {
417
266
  adminSearch,
418
267
  } from './search';
419
268
  export { type AdminTestCtxInput, adminTestCtx } from './test-ctx';
420
- export {
421
- type AdminBranding,
422
- adminBranding,
423
- defaultBranding,
424
- type ThemeAttributes,
425
- type ThemeMode,
426
- type ThemeTokenRef,
427
- themeAttributes,
269
+ export type {
270
+ AdminBranding,
271
+ ThemeAttributes,
272
+ ThemeMode,
273
+ ThemeTokenRef,
428
274
  } from './theme';
429
275
  export { type ValidationIssue, type ValidationResult, validateInput } from './validate';
430
- export {
431
- assertMoney,
432
- assertZone,
433
- type GuardedField,
434
- type SelectOption,
435
- type WidgetContext,
436
- type WidgetProps,
437
- widgetProps,
438
- } from './widget-value';
276
+ export type { SelectOption, WidgetContext, WidgetProps } from './widget-value';
439
277
  export { optionLabel, Widget, type WidgetInput } from './widgets';
@@ -21,13 +21,13 @@ export function jobsOperator(): JobsOperator {
21
21
  throw new DriverUnavailableError({
22
22
  driver: 'none',
23
23
  cause: 'the jobs dashboard was asked for rows and this process installed no queue',
24
- fix: 'run the app through x dev or the container entry, which install the queue — or in a test: setJobDriver(memoryJobDriver())',
24
+ fix: 'run the app through x dev or the container entry, which install the queue — or in a test: installJobDriver(memoryJobDriver()) from @ultimat3/jobs/adapter',
25
25
  });
26
26
  }
27
27
  if (driver.introspect === undefined) {
28
28
  throw new NotImplementedError({
29
29
  cause: `the operator surface of the "${driver.name}" jobs driver is not implemented: the driver has no introspect`,
30
- fix: 'call setJobDriver(postgresJobDriver()) at boot — the pg and memory drivers implement introspect',
30
+ fix: 'export const runtime = { jobs: postgresJobDriver() } # apps/<app>/runtime.ts — or delete that line so the boot installs it; the pg and memory drivers implement introspect',
31
31
  });
32
32
  }
33
33
  return { driver, introspect: driver.introspect };
package/src/layout.tsx CHANGED
@@ -128,7 +128,7 @@ export function AdminLayout(props: AdminLayoutProps): JSX.Element {
128
128
 
129
129
  {/* The way out. The admin's route table never points back at the app, so without this an
130
130
  operator who opened the dashboard has no link home and edits the URL bar. */}
131
- <a class={styles['navLink']} href="/">
131
+ <a class={styles['navLink']} href={props.app.home ?? '/'}>
132
132
  {t('admin.backToApp')}
133
133
  </a>
134
134
  </aside>
@@ -0,0 +1,29 @@
1
+ // The admin as one request reached it: the same declaration, its links rooted where the request
2
+ // was SERVED — behind an HTTP base path, under a routed locale, or both (`/myapp/es/admin`). Every
3
+ // link, form action and redirect a screen writes starts from `app.basePath` or `app.home`, so this
4
+ // one substitution keeps every click where the operator is instead of dropping the prefix.
5
+
6
+ import type { AdminApp } from './admin';
7
+
8
+ /**
9
+ * `app` itself when it was reached at its declared base path; otherwise a copy whose `basePath` is
10
+ * the pathname up to and including the declared base (`/myapp/es/admin`) and whose `home` is the
11
+ * part before it with a trailing slash (`/myapp/es/`). The first match from the left is the mount:
12
+ * a row id or a page path that repeats the base comes after it. The route table (`routes`) is left
13
+ * as declared: the host matches on the stripped route path, and the audit names the route.
14
+ */
15
+ export function adminAppFor(app: AdminApp, url: string): AdminApp {
16
+ const segments = (URL.parse(url, 'http://localhost')?.pathname ?? '').split('/').slice(1);
17
+ const base = app.basePath.split('/').filter((segment) => segment !== '');
18
+ for (let at = 0; at + base.length <= segments.length; at += 1) {
19
+ if (!base.every((segment, i) => segments[at + i] === segment)) continue;
20
+ if (at === 0) return app;
21
+ const served = segments.slice(0, at);
22
+ return {
23
+ ...app,
24
+ basePath: `/${[...served, ...base].join('/')}`,
25
+ home: `/${served.join('/')}/`,
26
+ };
27
+ }
28
+ return app;
29
+ }