@aiquants/auth-react-router 0.15.1 → 0.17.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/README.md +100 -6
- package/dist/admin.d.mts +10 -5
- package/dist/admin.d.ts +10 -5
- package/dist/admin.js +1 -1
- package/dist/admin.mjs +1 -1
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -81,15 +81,88 @@ export const userAdminApp = createUserAdminApp({
|
|
|
81
81
|
import { AuthUserAdminAppView } from "@aiquants/auth-react-router/admin"
|
|
82
82
|
export const loader = userAdminApp.loader
|
|
83
83
|
export const action = userAdminApp.action
|
|
84
|
-
|
|
84
|
+
// mounted as the splat route "/users/*"
|
|
85
|
+
export default () => <AuthUserAdminAppView basePath="/users" />
|
|
85
86
|
```
|
|
86
87
|
|
|
88
|
+
- **`basePath` is required** (`AuthUserAdminAppView` / `UserAdminShell`): the absolute path the console is mounted at,
|
|
89
|
+
without a trailing slash (`"/users"` for a `/users/*` route). The five tabs and the per-row "members" links of the
|
|
90
|
+
groups tab are built from it. There is no default: the host decides where the console lives, and a built-in default
|
|
91
|
+
would keep drawing links to that default after the mount moves — the routes still resolve and the build still
|
|
92
|
+
passes, only the links point at a place that is not served.
|
|
93
|
+
|
|
87
94
|
- **`guards` is mandatory** — this surface lists every user and mutates group membership, so an unguarded mount is an account-enumeration and privilege-escalation path. Allow by returning, deny by throwing (`Response` / `redirect` / `Error`); the return value is `void` by contract, so a predicate-style guard that returns `false` cannot silently fail open.
|
|
88
95
|
- **Per-operation authorization**: the guard receives the CRUD verb each intent actually performs (`read` / `create` / `update` / `delete`), so a principal holding only `update` cannot delete. Membership add/remove map to `create`/`delete` because they add and remove rows.
|
|
89
96
|
- Unknown intents reach neither the guard nor the store (the intent table is a `Map`, so prototype keys such as `constructor` do not resolve).
|
|
90
97
|
- Store errors (e.g. "cannot remove the last administrator") are returned as `{ ok: false, error }` and rendered by the views; guard rejections propagate untouched.
|
|
91
98
|
- Labels default to **English** (`defaultUserAdminLabels`); inject `jaUserAdminLabels` or a partial override via `resolveUserAdminLabels`.
|
|
92
99
|
|
|
100
|
+
### Localization (`labels.locale` vs `labels.formatLocale`)
|
|
101
|
+
|
|
102
|
+
Every string this package draws lives in `UserAdminLabels`, and a host picks the language by injecting a whole catalog
|
|
103
|
+
(`defaultUserAdminLabels` for English, the package default, or `jaUserAdminLabels`) or a partial override. Two keys
|
|
104
|
+
carry language, and they are **different axes**:
|
|
105
|
+
|
|
106
|
+
| Key | Type | `defaultUserAdminLabels` | `jaUserAdminLabels` | Drives |
|
|
107
|
+
| --- | --- | --- | --- | --- |
|
|
108
|
+
| `locale` | `UserAdminLocale` (`"en"` \| `"ja"`) | `"en"` | `"ja"` | UI chrome language of both upstream-group pickers (`SelectBox` `locale`) |
|
|
109
|
+
| `formatLocale` | BCP 47 `string` | `"en-US"` | `"ja-JP"` | `Intl.DateTimeFormat` (with `timeZone`) and the user-list `Intl.Collator` |
|
|
110
|
+
|
|
111
|
+
- **`locale`** is forwarded to both `@aiquants/select-box` pickers (the create-group dialog and the directory link
|
|
112
|
+
form). The picker strings this package owns (`directoryView.pickerNoOptions` / `pickerClear` / `pickerToggle` /
|
|
113
|
+
`pickerSelected` / `pickerAvailable`) are passed as the picker's `labels`, the placeholder is the per-instance
|
|
114
|
+
`placeholder` prop, and every other key comes from the select-box catalog of `locale` (the values below are those of
|
|
115
|
+
`defaultUserAdminLabels` / `jaUserAdminLabels` and of select-box 0.11.0):
|
|
116
|
+
|
|
117
|
+
| select-box key | Source | `"en"` | `"ja"` |
|
|
118
|
+
| --- | --- | --- | --- |
|
|
119
|
+
| `noOptions` | `directoryView.pickerNoOptions` | No matching group | 一致するグループがありません |
|
|
120
|
+
| `clear` | `directoryView.pickerClear` | Clear the selection | 選択を解除 |
|
|
121
|
+
| `toggle` | `directoryView.pickerToggle` | Show the group list | グループ一覧を開く |
|
|
122
|
+
| `selection` | `directoryView.pickerSelected` (`{value}` = the picked option's label) | Selected: {value} | 選択中: {value} |
|
|
123
|
+
| `status` | `directoryView.pickerAvailable` (`{count}` = options shown) | {count} groups available | 候補 {count} 件 |
|
|
124
|
+
| `placeholder` | `placeholder` prop: `groupsView.pickUpstreamPlaceholder` (dialog) / `directoryView.pickUpstreamPlaceholder` (link form) | Search by name or address... | 名前かアドレスで検索... |
|
|
125
|
+
| `scrollUp` | select-box catalog | Scroll up | 上へスクロール |
|
|
126
|
+
| `scrollDown` | select-box catalog | Scroll down | 下へスクロール |
|
|
127
|
+
| `scrollLeft` | select-box catalog | Scroll left | 左へスクロール |
|
|
128
|
+
| `scrollRight` | select-box catalog | Scroll right | 右へスクロール |
|
|
129
|
+
| `scrollToTop` | select-box catalog | Top | 先頭へ |
|
|
130
|
+
| `scrollToBottom` | select-box catalog | Bottom | 末尾へ |
|
|
131
|
+
| `noItems` | select-box catalog | No items | 項目がありません |
|
|
132
|
+
| `searching` | select-box catalog (message of the open list while nothing matches yet and more rows may still come) | Searching... | 検索中... |
|
|
133
|
+
| `guess` | select-box catalog (accessible description of a suggested row, one the picker lists as a guess rather than a match) | suggestion | 推測 |
|
|
134
|
+
| `removeTag` | select-box catalog (multi-select chips; both pickers are single-select) | Remove {label} | 「{label}」を削除 |
|
|
135
|
+
| `resizeHandle` | select-box catalog (tooltip of the option list's resize handle) | Resize handle | サイズ変更ハンドル |
|
|
136
|
+
|
|
137
|
+
To change a catalog-sourced string, there is no per-key override here: pick the `locale` whose catalog you want.
|
|
138
|
+
|
|
139
|
+
- `resolveUserAdminLabels` (run once when `createUserAdminApp` is built) **validates `locale`**: an omitted value keeps
|
|
140
|
+
the English default `"en"`; `"en"` / `"ja"` pass; anything else (`"ja-JP"`, `"EN"`, `""`, `null`, ...) throws
|
|
141
|
+
`RangeError` naming `labels.formatLocale`, so a mistake stops the app at startup instead of crashing the picker on
|
|
142
|
+
first render. There is no language negotiation — map `navigator.language` or a user setting to `"en"` / `"ja"`
|
|
143
|
+
yourself.
|
|
144
|
+
- **`formatLocale` is not validated**: an unreadable tag degrades (dates fall back to the raw ISO string, sorting to the
|
|
145
|
+
runtime collator) instead of throwing, because formatting must never take the screen down.
|
|
146
|
+
|
|
147
|
+
#### Migrating from 0.15.x (breaking changes in 0.16.0)
|
|
148
|
+
|
|
149
|
+
1. **`basePath` is now required** on `AuthUserAdminAppView` / `UserAdminShell` (see above). Pass the absolute mount
|
|
150
|
+
path, without a trailing slash: `<AuthUserAdminAppView basePath="/users" />`. Omitting it fails type-checking
|
|
151
|
+
(TS2741); an untyped caller would draw links such as `undefined/groups`.
|
|
152
|
+
2. **`labels.locale` changed meaning**: the old `labels.locale` (a BCP 47 tag for dates and sorting) is now
|
|
153
|
+
`labels.formatLocale` — no alias. The new `labels.locale` (type `UserAdminLocale` = `"en"` | `"ja"`) is the UI
|
|
154
|
+
language of the pickers, validated at startup.
|
|
155
|
+
- Hosts that inject `jaUserAdminLabels` whole, or spread it (`{ ...jaUserAdminLabels, heading: "" }`), need no
|
|
156
|
+
change: it carries `locale: "ja"` and `formatLocale: "ja-JP"`.
|
|
157
|
+
- A host that overrode `locale: "ja-JP"` must write `formatLocale: "ja-JP"`, and add `locale: "ja"` if it relies
|
|
158
|
+
on Japanese picker chrome (without it the pickers' catalog-sourced strings, such as the scroll-arrow names, are
|
|
159
|
+
English).
|
|
160
|
+
- A host that builds a full `UserAdminLabels` literal must add both keys.
|
|
161
|
+
3. **`@aiquants/select-box` `texts` is gone**: the pickers take select-box 0.10.0 `locale` / `labels`
|
|
162
|
+
(`SelectBoxLabelOverrides`) instead of `texts` / `SelectBoxTexts`. This is internal to the views — hosts that only
|
|
163
|
+
render `AuthUserAdminAppView` do nothing — but a host must install `@aiquants/select-box` >= 0.10.0 and
|
|
164
|
+
`@aiquants/virtualscroll` >= 3.7.0 (the new peer floors).
|
|
165
|
+
|
|
93
166
|
### Upstream group catalog (optional port)
|
|
94
167
|
|
|
95
168
|
The groups and directory tabs let the operator **pick** an upstream group instead of spelling its address. Wire the optional
|
|
@@ -180,11 +253,32 @@ the host, or the group picker's dropdown renders unstyled:
|
|
|
180
253
|
@import "@aiquants/select-box/styles/select-box.css";
|
|
181
254
|
```
|
|
182
255
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
256
|
+
That is the components-only build. Follow select-box's own README ("CSS Setup") for the rest of your build: a Tailwind v4
|
|
257
|
+
host imports it (and the `@aiquants/virtualscroll` peer build) in `layer(components)` and points `@source` at
|
|
258
|
+
`node_modules/@aiquants/select-box/dist` (the published package ships no `src`); a host without Tailwind imports
|
|
259
|
+
`@aiquants/select-box/styles/select-box.standalone.css` instead.
|
|
260
|
+
|
|
261
|
+
The option rows of both pickers are drawn by this package: each row shows the group name and address, a note (the local
|
|
262
|
+
group already linked to it, which makes the row unpickable, or else its member count when known), and the part of the
|
|
263
|
+
label the typed query matched. The match paint is
|
|
264
|
+
the picker's own (`RenderOptionContext.highlight`: the query compiled once under the picker's normalization and bound to
|
|
265
|
+
the row's match category), so a row the picker lists as a guess is never painted as an exact match; the runs carry
|
|
266
|
+
`data-aqar-match="exact"` or `"fuzzy"` and are paint-only (background and text colour, no bold), so a label keeps its
|
|
267
|
+
width while you type.
|
|
268
|
+
|
|
269
|
+
`@aiquants/fuzzy-search` is a transitive peer (the picker's search index, reached only through select-box) and is
|
|
270
|
+
declared here as well, so a host that installs this package is told what the picker needs rather than discovering it
|
|
271
|
+
as a runtime resolution failure. `@aiquants/virtualscroll` is both: select-box renders its option list with it, and
|
|
272
|
+
this package imports it **directly** — `src/admin/labels.ts` resolves the UI language with
|
|
273
|
+
`resolveVirtualScrollLocale`, so `dist/admin.mjs` carries its specifier. The pickers use the `locale` / `labels` props
|
|
274
|
+
of `@aiquants/select-box` and its 0.11 render and typing contract (`RenderOptionContext.highlight`, and `value` /
|
|
275
|
+
`onChange` typed by the option type), plus the locale resolution of `@aiquants/virtualscroll` 3.7.0, so select-box 0.11.0
|
|
276
|
+
and virtualscroll 3.7.0 are the peer floors (the published manifest carries `^0.11.0`, which a 0.10 install does not
|
|
277
|
+
satisfy).
|
|
278
|
+
|
|
279
|
+
All three are **required** peers, not optional. `src/admin/views.tsx` imports `@aiquants/select-box` and
|
|
280
|
+
`src/admin/labels.ts` imports `@aiquants/virtualscroll` at the top level, and `./admin` re-exports both modules, so the
|
|
281
|
+
specifiers survive into `dist/admin.mjs`: a host without them does not get a degraded
|
|
188
282
|
picker, it gets `ERR_MODULE_NOT_FOUND` for the whole admin surface — including the groups and allowlist tabs, which have
|
|
189
283
|
nothing to do with the picker. Marking them optional would suppress the install warning that is the only thing standing
|
|
190
284
|
between a consumer and that failure. Note also that `@aiquants/select-box` pins React 19, which is stricter than this
|
package/dist/admin.d.mts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { VirtualScrollLocale } from "@aiquants/virtualscroll";
|
|
1
2
|
import * as react_router from "react-router";
|
|
2
3
|
import { AuthUser, AuthGroup, AuthGroupMember, AuthAllowlistEntry, AuthDirectoryMembershipMode, AuthDirectoryStatus, AuthDirectoryCatalogSnapshot, AuthDirectoryCatalogGroup } from "@aiquants/auth-core";
|
|
3
4
|
import React__default from "react";
|
|
5
|
+
type UserAdminLocale = VirtualScrollLocale;
|
|
4
6
|
type UserAdminLabels = {
|
|
5
7
|
title: string;
|
|
6
|
-
locale:
|
|
8
|
+
locale: UserAdminLocale;
|
|
9
|
+
formatLocale: string;
|
|
7
10
|
timeZone: string;
|
|
8
11
|
common: {
|
|
9
12
|
create: string;
|
|
@@ -211,7 +214,8 @@ declare const defaultUserAdminLabels: UserAdminLabels;
|
|
|
211
214
|
declare const jaUserAdminLabels: UserAdminLabels;
|
|
212
215
|
type PartialUserAdminLabels = {
|
|
213
216
|
title?: string;
|
|
214
|
-
locale?:
|
|
217
|
+
locale?: UserAdminLocale;
|
|
218
|
+
formatLocale?: string;
|
|
215
219
|
timeZone?: string;
|
|
216
220
|
common?: Partial<UserAdminLabels["common"]>;
|
|
217
221
|
errors?: Partial<UserAdminLabels["errors"]>;
|
|
@@ -382,9 +386,10 @@ type UserAdminShellProps = {
|
|
|
382
386
|
title: string;
|
|
383
387
|
annotation: React__default.ReactNode;
|
|
384
388
|
}) => React__default.ReactNode;
|
|
389
|
+
basePath: string;
|
|
385
390
|
pendingSyncPolling?: PendingSyncPolling;
|
|
386
391
|
};
|
|
387
|
-
declare function UserAdminShell({ children, renderHeader, pendingSyncPolling }: UserAdminShellProps): React__default.JSX.Element;
|
|
392
|
+
declare function UserAdminShell({ children, renderHeader, pendingSyncPolling, basePath }: UserAdminShellProps): React__default.JSX.Element;
|
|
388
393
|
type UserAdminLoaderData = {
|
|
389
394
|
segment: string;
|
|
390
395
|
users: AuthUser[];
|
|
@@ -401,5 +406,5 @@ declare function GroupMembersView(): React__default.JSX.Element;
|
|
|
401
406
|
declare function AllowlistView(): React__default.JSX.Element;
|
|
402
407
|
declare function DirectoryView(): React__default.JSX.Element;
|
|
403
408
|
type AuthUserAdminAppViewProps = UserAdminShellProps;
|
|
404
|
-
declare function AuthUserAdminAppView({ renderHeader, pendingSyncPolling }
|
|
405
|
-
export { AllowlistView, AuthUserAdminAppView, type AuthUserAdminAppViewProps, DEFAULT_PENDING_SYNC_POLLING, DirectoryView, GroupMembersView, GroupsView, type PartialUserAdminLabels, type PendingSyncPolling, type UserAdminAccess, type UserAdminAppConfig, type UserAdminCatalog, type UserAdminCatalogFailure, type UserAdminCatalogGroup, type UserAdminCatalogSnapshot, type UserAdminGuards, UserAdminInputError, type UserAdminLabels, type UserAdminLinkSyncOutcome, type UserAdminLoaderData, UserAdminOperatorError, type UserAdminPermission, UserAdminShell, type UserAdminShellProps, type UserAdminStore, type UserAdminSyncRequestOutcome, UsersView, createInMemoryUserAdminStore, createUserAdminApp, defaultUserAdminLabels, jaUserAdminLabels, normalizeUpstreamKey, resolveUserAdminLabels, userAdminAccessFor, userAdminIntents };
|
|
409
|
+
declare function AuthUserAdminAppView({ renderHeader, pendingSyncPolling, basePath }: AuthUserAdminAppViewProps): React__default.JSX.Element;
|
|
410
|
+
export { AllowlistView, AuthUserAdminAppView, type AuthUserAdminAppViewProps, DEFAULT_PENDING_SYNC_POLLING, DirectoryView, GroupMembersView, GroupsView, type PartialUserAdminLabels, type PendingSyncPolling, type UserAdminAccess, type UserAdminAppConfig, type UserAdminCatalog, type UserAdminCatalogFailure, type UserAdminCatalogGroup, type UserAdminCatalogSnapshot, type UserAdminGuards, UserAdminInputError, type UserAdminLabels, type UserAdminLinkSyncOutcome, type UserAdminLoaderData, type UserAdminLocale, UserAdminOperatorError, type UserAdminPermission, UserAdminShell, type UserAdminShellProps, type UserAdminStore, type UserAdminSyncRequestOutcome, UsersView, createInMemoryUserAdminStore, createUserAdminApp, defaultUserAdminLabels, jaUserAdminLabels, normalizeUpstreamKey, resolveUserAdminLabels, userAdminAccessFor, userAdminIntents };
|
package/dist/admin.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { VirtualScrollLocale } from "@aiquants/virtualscroll";
|
|
1
2
|
import * as react_router from "react-router";
|
|
2
3
|
import { AuthUser, AuthGroup, AuthGroupMember, AuthAllowlistEntry, AuthDirectoryMembershipMode, AuthDirectoryStatus, AuthDirectoryCatalogSnapshot, AuthDirectoryCatalogGroup } from "@aiquants/auth-core";
|
|
3
4
|
import React__default from "react";
|
|
5
|
+
type UserAdminLocale = VirtualScrollLocale;
|
|
4
6
|
type UserAdminLabels = {
|
|
5
7
|
title: string;
|
|
6
|
-
locale:
|
|
8
|
+
locale: UserAdminLocale;
|
|
9
|
+
formatLocale: string;
|
|
7
10
|
timeZone: string;
|
|
8
11
|
common: {
|
|
9
12
|
create: string;
|
|
@@ -211,7 +214,8 @@ declare const defaultUserAdminLabels: UserAdminLabels;
|
|
|
211
214
|
declare const jaUserAdminLabels: UserAdminLabels;
|
|
212
215
|
type PartialUserAdminLabels = {
|
|
213
216
|
title?: string;
|
|
214
|
-
locale?:
|
|
217
|
+
locale?: UserAdminLocale;
|
|
218
|
+
formatLocale?: string;
|
|
215
219
|
timeZone?: string;
|
|
216
220
|
common?: Partial<UserAdminLabels["common"]>;
|
|
217
221
|
errors?: Partial<UserAdminLabels["errors"]>;
|
|
@@ -382,9 +386,10 @@ type UserAdminShellProps = {
|
|
|
382
386
|
title: string;
|
|
383
387
|
annotation: React__default.ReactNode;
|
|
384
388
|
}) => React__default.ReactNode;
|
|
389
|
+
basePath: string;
|
|
385
390
|
pendingSyncPolling?: PendingSyncPolling;
|
|
386
391
|
};
|
|
387
|
-
declare function UserAdminShell({ children, renderHeader, pendingSyncPolling }: UserAdminShellProps): React__default.JSX.Element;
|
|
392
|
+
declare function UserAdminShell({ children, renderHeader, pendingSyncPolling, basePath }: UserAdminShellProps): React__default.JSX.Element;
|
|
388
393
|
type UserAdminLoaderData = {
|
|
389
394
|
segment: string;
|
|
390
395
|
users: AuthUser[];
|
|
@@ -401,5 +406,5 @@ declare function GroupMembersView(): React__default.JSX.Element;
|
|
|
401
406
|
declare function AllowlistView(): React__default.JSX.Element;
|
|
402
407
|
declare function DirectoryView(): React__default.JSX.Element;
|
|
403
408
|
type AuthUserAdminAppViewProps = UserAdminShellProps;
|
|
404
|
-
declare function AuthUserAdminAppView({ renderHeader, pendingSyncPolling }
|
|
405
|
-
export { AllowlistView, AuthUserAdminAppView, type AuthUserAdminAppViewProps, DEFAULT_PENDING_SYNC_POLLING, DirectoryView, GroupMembersView, GroupsView, type PartialUserAdminLabels, type PendingSyncPolling, type UserAdminAccess, type UserAdminAppConfig, type UserAdminCatalog, type UserAdminCatalogFailure, type UserAdminCatalogGroup, type UserAdminCatalogSnapshot, type UserAdminGuards, UserAdminInputError, type UserAdminLabels, type UserAdminLinkSyncOutcome, type UserAdminLoaderData, UserAdminOperatorError, type UserAdminPermission, UserAdminShell, type UserAdminShellProps, type UserAdminStore, type UserAdminSyncRequestOutcome, UsersView, createInMemoryUserAdminStore, createUserAdminApp, defaultUserAdminLabels, jaUserAdminLabels, normalizeUpstreamKey, resolveUserAdminLabels, userAdminAccessFor, userAdminIntents };
|
|
409
|
+
declare function AuthUserAdminAppView({ renderHeader, pendingSyncPolling, basePath }: AuthUserAdminAppViewProps): React__default.JSX.Element;
|
|
410
|
+
export { AllowlistView, AuthUserAdminAppView, type AuthUserAdminAppViewProps, DEFAULT_PENDING_SYNC_POLLING, DirectoryView, GroupMembersView, GroupsView, type PartialUserAdminLabels, type PendingSyncPolling, type UserAdminAccess, type UserAdminAppConfig, type UserAdminCatalog, type UserAdminCatalogFailure, type UserAdminCatalogGroup, type UserAdminCatalogSnapshot, type UserAdminGuards, UserAdminInputError, type UserAdminLabels, type UserAdminLinkSyncOutcome, type UserAdminLoaderData, type UserAdminLocale, UserAdminOperatorError, type UserAdminPermission, UserAdminShell, type UserAdminShellProps, type UserAdminStore, type UserAdminSyncRequestOutcome, UsersView, createInMemoryUserAdminStore, createUserAdminApp, defaultUserAdminLabels, jaUserAdminLabels, normalizeUpstreamKey, resolveUserAdminLabels, userAdminAccessFor, userAdminIntents };
|