@jini-ai/user-management 0.3.3 → 0.6.10

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.
Files changed (68) hide show
  1. package/CHANGELOG.md +15 -1
  2. package/dist/admin/adapters/memory.d.ts +3 -5
  3. package/dist/admin/adapters/memory.js +27 -28
  4. package/dist/admin/conformance/index.js +1 -0
  5. package/dist/admin/controllers/auth.controller.js +8 -6
  6. package/dist/admin/controllers/members.controller.js +9 -6
  7. package/dist/admin/controllers/roles.controller.js +99 -105
  8. package/dist/admin/controllers/users.controller.js +56 -36
  9. package/dist/admin/error-copy.d.ts +11 -0
  10. package/dist/admin/error-copy.js +10 -0
  11. package/dist/admin/people.rules.js +37 -31
  12. package/dist/admin/react/components/MemberRow.d.ts +1 -1
  13. package/dist/admin/react/components/MemberRow.js +12 -2
  14. package/dist/admin/react/hooks/AuthBinding.hooks.d.ts +6 -21
  15. package/dist/admin/react/hooks/AuthBinding.hooks.js +3 -8
  16. package/dist/admin/react/hooks/AuthScope.hooks.d.ts +22 -0
  17. package/dist/admin/react/hooks/AuthScope.hooks.js +10 -0
  18. package/dist/admin/react/hooks/LoginPage.hooks.d.ts +1 -1
  19. package/dist/admin/react/hooks/LoginPage.hooks.js +1 -1
  20. package/dist/admin/react/hooks/MembersBinding.hooks.d.ts +6 -17
  21. package/dist/admin/react/hooks/MembersBinding.hooks.js +3 -8
  22. package/dist/admin/react/hooks/MembersPage.hooks.d.ts +1 -1
  23. package/dist/admin/react/hooks/MembersPage.hooks.js +1 -1
  24. package/dist/admin/react/hooks/MembersScope.hooks.d.ts +17 -0
  25. package/dist/admin/react/hooks/MembersScope.hooks.js +10 -0
  26. package/dist/admin/react/hooks/RolesBinding.hooks.d.ts +7 -21
  27. package/dist/admin/react/hooks/RolesBinding.hooks.js +3 -8
  28. package/dist/admin/react/hooks/RolesController.hooks.js +1 -1
  29. package/dist/admin/react/hooks/RolesPage.hooks.d.ts +2 -2
  30. package/dist/admin/react/hooks/RolesPage.hooks.js +22 -8
  31. package/dist/admin/react/hooks/RolesScope.hooks.d.ts +21 -0
  32. package/dist/admin/react/hooks/RolesScope.hooks.js +10 -0
  33. package/dist/admin/react/hooks/UsersBinding.hooks.d.ts +6 -18
  34. package/dist/admin/react/hooks/UsersBinding.hooks.js +3 -8
  35. package/dist/admin/react/hooks/UsersPage.hooks.d.ts +1 -1
  36. package/dist/admin/react/hooks/UsersPage.hooks.js +1 -1
  37. package/dist/admin/react/hooks/UsersScope.hooks.d.ts +19 -0
  38. package/dist/admin/react/hooks/UsersScope.hooks.js +10 -0
  39. package/dist/admin/react/hooks/UsersTab.hooks.d.ts +6 -0
  40. package/dist/admin/react/hooks/UsersTab.hooks.js +34 -22
  41. package/dist/admin/react/index.d.ts +4 -4
  42. package/dist/admin/react/pages/LoginPage.d.ts +1 -1
  43. package/dist/admin/react/pages/MembersPage.d.ts +1 -1
  44. package/dist/admin/react/pages/RolesPage.d.ts +2 -2
  45. package/dist/admin/react/pages/RolesPage.js +6 -2
  46. package/dist/admin/react/pages/UsersPage.d.ts +2 -2
  47. package/dist/admin/react/pages/UsersPage.js +6 -2
  48. package/dist/admin/rules.js +14 -12
  49. package/dist/react/errors.js +16 -9
  50. package/dist/react/index.d.ts +1 -1
  51. package/dist/react/members/hooks/use-members.hooks.js +21 -12
  52. package/dist/react/testing.js +4 -6
  53. package/dist/react/users/UserRowParts.d.ts +53 -0
  54. package/dist/react/users/UserRowParts.js +39 -0
  55. package/dist/react/users/Users.d.ts +31 -2
  56. package/dist/react/users/Users.js +41 -36
  57. package/dist/react/users/hooks/use-users.hooks.js +57 -30
  58. package/dist/server/admin-crud-service.js +25 -13
  59. package/dist/server/agent-tool-input.js +62 -36
  60. package/dist/server/agent-tool-registrations.d.ts +46 -0
  61. package/dist/server/agent-tool-registrations.js +28 -20
  62. package/dist/server/auth-service.js +35 -20
  63. package/dist/server/authorize.js +7 -9
  64. package/dist/server/permission-migrations.js +35 -25
  65. package/dist/server/repo.memory-transactions.js +5 -6
  66. package/dist/server/repo.memory.js +35 -81
  67. package/dist/server/seed.js +100 -84
  68. package/package.json +12 -12
@@ -101,20 +101,21 @@ export function createUsersController({ api, safety }, { permissions = [], openO
101
101
  return false;
102
102
  const draft = draftGeneration;
103
103
  set({ saving: true, formError: null });
104
+ let created = false;
104
105
  try {
105
106
  await api.createUser({ username: s.username, password: s.password }, { ...call, ...(s.email ? { email: s.email } : {}) });
106
107
  if (draft === draftGeneration)
107
108
  set({ username: '', email: '', password: '', formOpen: false });
108
109
  await load({});
109
- return store.active({});
110
+ created = store.active({});
110
111
  }
111
112
  catch (error) {
112
113
  set({ formError: report(error, 'failed to create user') });
113
- return false;
114
114
  }
115
115
  finally {
116
116
  set({ saving: false });
117
117
  }
118
+ return created;
118
119
  }
119
120
  function toggleExpanded({ principalId }, _optional = {}) {
120
121
  if (!store.active({}) || !row(principalId))
@@ -123,81 +124,99 @@ export function createUsersController({ api, safety }, { permissions = [], openO
123
124
  // A stale grant cannot clear a newer panel's selection or busy flag. Reset on switching.
124
125
  set({ expandedId: read({}).expandedId === principalId ? null : principalId, editEmail: row(principalId).email ?? '', pendingRoleId: '', pendingPolicyId: '', grantError: null, grantSaving: false });
125
126
  }
127
+ /** Per-kind panel writes: the authority checked, the selection that must be present, the call,
128
+ * and the selection cleared on success (email keeps its field, but still notifies). */
129
+ const panelWrites = {
130
+ email: { action: 'email', fallback: 'failed to update email', cleared: {}, missing: () => false,
131
+ write: (principalId, s) => api.updateUser({ principalId }, { ...call, email: s.editEmail }) },
132
+ role: { action: 'grants', fallback: 'failed to assign role', cleared: { pendingRoleId: '' }, missing: s => !s.pendingRoleId,
133
+ write: (principalId, s) => api.assignRole({ principalId, roleId: s.pendingRoleId }, call) },
134
+ policy: { action: 'grants', fallback: 'failed to attach policy', cleared: { pendingPolicyId: '' }, missing: s => !s.pendingPolicyId,
135
+ write: (principalId, s) => api.attachPolicy({ principalId, policyId: s.pendingPolicyId }, call) },
136
+ };
137
+ /** Busy, expanded-row and authority checks run before the selection check, so only a refusal of authority reports. */
138
+ function panelSavable(s, principalId, panel) {
139
+ return !s.grantSaving && s.expandedId === principalId && gate(principalId, panel.action) && !panel.missing(s);
140
+ }
126
141
  async function savePanel({ principalId, kind }, _optional = {}) {
127
- const s = read({}), action = kind === 'email' ? 'email' : 'grants';
128
- if (s.grantSaving || s.expandedId !== principalId || !gate(principalId, action))
129
- return false;
130
- if ((kind === 'role' && !s.pendingRoleId) || (kind === 'policy' && !s.pendingPolicyId))
142
+ const s = read({}), panel = panelWrites[kind];
143
+ if (!panelSavable(s, principalId, panel))
131
144
  return false;
132
145
  const generation = panelGeneration, draft = draftGeneration;
133
146
  set({ grantSaving: true, grantError: null });
147
+ let saved = false;
134
148
  try {
135
- if (kind === 'email')
136
- await api.updateUser({ principalId }, { ...call, email: s.editEmail });
137
- else if (kind === 'role')
138
- await api.assignRole({ principalId, roleId: s.pendingRoleId }, call);
139
- else
140
- await api.attachPolicy({ principalId, policyId: s.pendingPolicyId }, call);
149
+ await panel.write(principalId, s);
141
150
  if (generation === panelGeneration && draft === draftGeneration)
142
- set(kind === 'role' ? { pendingRoleId: '' } : kind === 'policy' ? { pendingPolicyId: '' } : {});
151
+ set(panel.cleared);
143
152
  await load({});
144
- return store.active({});
153
+ saved = store.active({});
145
154
  }
146
155
  catch (error) {
147
156
  if (generation === panelGeneration)
148
- set({ grantError: report(error, `failed to ${kind === 'email' ? 'update email' : kind === 'role' ? 'assign role' : 'attach policy'}`) });
149
- return false;
157
+ set({ grantError: report(error, panel.fallback) });
150
158
  }
151
159
  finally {
152
160
  if (generation === panelGeneration)
153
161
  set({ grantSaving: false });
154
162
  }
163
+ return saved;
155
164
  }
156
165
  async function enableUser({ principalId }, _optional = {}) {
157
166
  if (read({}).rowSavingId || !gate(principalId, 'enable'))
158
167
  return false;
159
168
  set({ rowSavingId: principalId, rowError: null });
169
+ let enabled = false;
160
170
  try {
161
171
  await api.enableUser({ principalId }, call);
162
172
  await load({});
163
- return store.active({});
173
+ enabled = store.active({});
164
174
  }
165
175
  catch (error) {
166
176
  set({ rowError: report(error, 'failed to change status') });
167
- return false;
168
177
  }
169
178
  finally {
170
179
  if (read({}).rowSavingId === principalId)
171
180
  set({ rowSavingId: null });
172
181
  }
182
+ return enabled;
173
183
  }
174
- async function confirmDestructive(_required, _optional = {}) {
175
- const s = read({}), target = s.pending;
184
+ /** The pending target when it may be confirmed now; a reset also needs a matching, non-empty pair. */
185
+ function confirmable(s) {
186
+ const target = s.pending;
176
187
  if (!target || s.confirming || s.rowSavingId || !gate(target.user.principalId, target.kind))
177
- return false;
188
+ return null;
178
189
  if (target.kind === 'reset' && (!s.newPassword || s.newPassword !== s.confirmPassword))
190
+ return null;
191
+ return target;
192
+ }
193
+ async function destroy(target, s) {
194
+ const principalId = target.user.principalId;
195
+ if (target.kind === 'reset') {
196
+ await api.resetUserPassword({ principalId, password: s.newPassword }, call);
197
+ set({ notice: principalId === s.caller?.user.id ? 'Password changed. Sign in again with your new password.' : `Password reset for "${target.user.username}" — every active session for this user was revoked.` });
198
+ return;
199
+ }
200
+ if (target.kind === 'disable')
201
+ await api.disableUser({ principalId }, call);
202
+ else
203
+ await api.deleteUser({ principalId }, call);
204
+ await load({});
205
+ }
206
+ async function confirmDestructive(_required, _optional = {}) {
207
+ const s = read({}), target = confirmable(s);
208
+ if (!target)
179
209
  return false;
180
210
  set({ confirming: true, rowError: null, passwordError: null });
181
- let success = false;
211
+ let success = false, live = false;
182
212
  try {
183
- const principalId = target.user.principalId;
184
- if (target.kind === 'reset') {
185
- await api.resetUserPassword({ principalId, password: s.newPassword }, call);
186
- set({ notice: principalId === s.caller?.user.id ? 'Password changed. Sign in again with your new password.' : `Password reset for "${target.user.username}" — every active session for this user was revoked.` });
187
- }
188
- else {
189
- if (target.kind === 'disable')
190
- await api.disableUser({ principalId }, call);
191
- else
192
- await api.deleteUser({ principalId }, call);
193
- await load({});
194
- }
213
+ await destroy(target, s);
214
+ // Read liveness before the close below: its host callback may dispose this controller.
195
215
  success = true;
196
- return store.active({});
216
+ live = store.active({});
197
217
  }
198
218
  catch (error) {
199
219
  set(target.kind === 'reset' ? { passwordError: report(error, 'failed to reset password') } : { rowError: report(error, `failed to ${target.kind} user`) });
200
- return false;
201
220
  }
202
221
  finally {
203
222
  // Reset stays open on failure so the operator can retry the exact typed credential.
@@ -205,6 +224,7 @@ export function createUsersController({ api, safety }, { permissions = [], openO
205
224
  close();
206
225
  set({ confirming: false });
207
226
  }
227
+ return live;
208
228
  }
209
229
  return {
210
230
  getSnapshot: store.getSnapshot, subscribe: store.subscribe, allowed,
@@ -0,0 +1,11 @@
1
+ /** Shared pieces of the People and Roles error-copy mappers (`describePeopleError`, `describeRolesError`). */
2
+ /** The string `code` of a structured error, or undefined when there is none. */
3
+ export declare function errorCode({ error }: {
4
+ error: unknown;
5
+ }, _optional?: Record<string, never>): string | undefined;
6
+ /** A real Error's non-empty message, else the given fallback copy. */
7
+ export declare function errorMessageOr({ error, fallback }: {
8
+ error: unknown;
9
+ fallback: string;
10
+ }, _optional?: Record<string, never>): string;
11
+ //# sourceMappingURL=error-copy.d.ts.map
@@ -0,0 +1,10 @@
1
+ /** Shared pieces of the People and Roles error-copy mappers (`describePeopleError`, `describeRolesError`). */
2
+ /** The string `code` of a structured error, or undefined when there is none. */
3
+ export function errorCode({ error }, _optional = {}) {
4
+ return error && typeof error === 'object' && 'code' in error && typeof error.code === 'string' ? error.code : undefined;
5
+ }
6
+ /** A real Error's non-empty message, else the given fallback copy. */
7
+ export function errorMessageOr({ error, fallback }, _optional = {}) {
8
+ return error instanceof Error && error.message ? error.message : fallback;
9
+ }
10
+ //# sourceMappingURL=error-copy.js.map
@@ -1,13 +1,9 @@
1
+ import { errorCode, errorMessageOr } from './error-copy.js';
1
2
  export function hasAdminGrant({ permissions, permission }, _optional = {}) {
2
3
  return permissions.includes('*') || permissions.includes(permission);
3
4
  }
4
- /** Server trash authority is a required additional check. This portable UI also requires
5
- * user.manage: member.manage by itself must never reveal actions over operators.
6
- * Unknown ownership never becomes permission. The member.manage self-email exception does not
7
- * confer any credential, status or grant authority over other operators. */
8
- export function userActionAllowed({ user, action, permissions, caller, safety }, _optional = {}) {
9
- if (!caller)
10
- return false;
5
+ /** Unknown ownership never becomes permission, and only a wildcard holder may act over an owner. */
6
+ function ownershipAllows({ user, permissions, safety }) {
11
7
  let owner;
12
8
  try {
13
9
  owner = safety.ownerStatus({ principalId: user.principalId });
@@ -15,34 +11,44 @@ export function userActionAllowed({ user, action, permissions, caller, safety },
15
11
  catch {
16
12
  return false;
17
13
  }
18
- if ((owner !== 'owner' && owner !== 'operator') || (owner === 'owner' && !permissions.includes('*')))
19
- return false;
20
- const self = caller.user.id === user.principalId;
21
- if (action === 'delete')
22
- return hasAdminGrant({ permissions, permission: 'user.manage' }) && caller.canManageUserTrash === true && !self && safety.seededOwnerPrincipalId !== null && user.principalId !== safety.seededOwnerPrincipalId;
23
- if (action === 'disable' && (user.status !== 'active' || safety.seededOwnerPrincipalId === null || user.principalId === safety.seededOwnerPrincipalId))
24
- return false;
25
- if (action === 'enable' && user.status !== 'disabled')
26
- return false;
27
- if (action === 'reset' && (safety.seededOwnerPrincipalId === null || (user.principalId === safety.seededOwnerPrincipalId && !self)))
14
+ return owner === 'operator' || (owner === 'owner' && permissions.includes('*'));
15
+ }
16
+ const userManage = (c) => hasAdminGrant({ permissions: c.permissions, permission: 'user.manage' });
17
+ /** The seeded owner must be known, and the target must not be it. */
18
+ const targetsNonSeeded = (c) => c.seeded !== null && c.user.principalId !== c.seeded;
19
+ // A Map (not an object literal) so a malformed action such as "constructor" cannot resolve to an
20
+ // Object.prototype member; any action without a rule of its own needs user.manage.
21
+ const ACTION_RULES = new Map([
22
+ ['delete', c => userManage(c) && c.caller.canManageUserTrash === true && !c.self && targetsNonSeeded(c)],
23
+ ['disable', c => c.user.status === 'active' && targetsNonSeeded(c) && userManage(c)],
24
+ ['enable', c => c.user.status === 'disabled' && userManage(c)],
25
+ // The seeded owner may reset its own password, but nobody else may reset it.
26
+ ['reset', c => c.seeded !== null && (c.self || c.user.principalId !== c.seeded) && userManage(c)],
27
+ ['grants', c => hasAdminGrant({ permissions: c.permissions, permission: 'role.manage' })],
28
+ ['email', c => (c.self && hasAdminGrant({ permissions: c.permissions, permission: 'member.manage' })) || userManage(c)],
29
+ ]);
30
+ /** Server trash authority is a required additional check. This portable UI also requires
31
+ * user.manage: member.manage by itself must never reveal actions over operators.
32
+ * Unknown ownership never becomes permission. The member.manage self-email exception does not
33
+ * confer any credential, status or grant authority over other operators. */
34
+ export function userActionAllowed({ user, action, permissions, caller, safety }, _optional = {}) {
35
+ if (!caller || !ownershipAllows({ user, permissions, safety }))
28
36
  return false;
29
- if (action === 'grants')
30
- return hasAdminGrant({ permissions, permission: 'role.manage' });
31
- if (action === 'email' && self && hasAdminGrant({ permissions, permission: 'member.manage' }))
32
- return true;
33
- return hasAdminGrant({ permissions, permission: 'user.manage' });
37
+ const context = { user, permissions, caller, seeded: safety.seededOwnerPrincipalId, self: caller.user.id === user.principalId };
38
+ return (ACTION_RULES.get(action) ?? userManage)(context);
34
39
  }
40
+ const PEOPLE_ERROR_COPY = Object.freeze({
41
+ FORBIDDEN: 'You do not have permission to do that.', GRANT_EXCEEDS_ISSUER: 'You cannot grant a permission you do not hold.',
42
+ RESOURCE_CONFLICT: 'That username is already in use.', OWNER_REQUIRED: 'The workspace must keep at least one active owner.',
43
+ SELF_DELETE: 'You cannot delete your own account.', USER_IN_TRASH: 'This user is in the Trash; restore them first.',
44
+ USERNAME_IN_TRASH: 'A user with this username is in the Trash; restore or delete them permanently first.',
45
+ });
35
46
  export function describePeopleError({ error, fallback }, _optional = {}) {
36
- const copy = {
37
- FORBIDDEN: 'You do not have permission to do that.', GRANT_EXCEEDS_ISSUER: 'You cannot grant a permission you do not hold.',
38
- RESOURCE_CONFLICT: 'That username is already in use.', OWNER_REQUIRED: 'The workspace must keep at least one active owner.',
39
- SELF_DELETE: 'You cannot delete your own account.', USER_IN_TRASH: 'This user is in the Trash; restore them first.',
40
- USERNAME_IN_TRASH: 'A user with this username is in the Trash; restore or delete them permanently first.',
41
- };
47
+ const code = errorCode({ error });
42
48
  // Own keys only: a server code such as "constructor" must not resolve to an Object.prototype member.
43
- if (error && typeof error === 'object' && 'code' in error && typeof error.code === 'string' && Object.hasOwn(copy, error.code))
44
- return copy[error.code];
45
- return error instanceof Error && error.message ? error.message : fallback;
49
+ if (code !== undefined && Object.hasOwn(PEOPLE_ERROR_COPY, code))
50
+ return PEOPLE_ERROR_COPY[code];
51
+ return errorMessageOr({ error, fallback });
46
52
  }
47
53
  export function grantLabel({ ids, records }, _optional = {}) {
48
54
  // Unknown grant ids stay visible: dropping them would conceal dangling references.
@@ -1,5 +1,5 @@
1
1
  import type { AdminMember } from '../../models.js';
2
2
  export declare function MemberRow({ member }: {
3
3
  member: AdminMember;
4
- }, _optional?: Record<string, never>): import("react").JSX.Element;
4
+ }): import("react").JSX.Element;
5
5
  //# sourceMappingURL=MemberRow.d.ts.map
@@ -1,8 +1,18 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { Badge, Button, Menu, Notice, Spinner } from '@jini-ai/ui-kit/react';
3
3
  import { useMemberRow } from '../hooks/MembersPage.hooks.js';
4
- export function MemberRow({ member }, _optional = {}) {
4
+ /** The expanded detail cell: loading wins over an error, and an error over the loaded record. */
5
+ function MemberDetail({ v }) {
6
+ if (v.loading)
7
+ return _jsx(Spinner, { label: "Loading detail\u2026" });
8
+ if (v.s.detailError)
9
+ return _jsx(Notice, { tone: "danger", children: v.s.detailError });
10
+ if (!v.detail)
11
+ return null;
12
+ return _jsxs("dl", { children: [_jsx("dt", { children: "ID" }), _jsx("dd", { children: v.detail.id }), _jsx("dt", { children: "Email verified" }), _jsx("dd", { children: v.verified }), _jsx("dt", { children: "Updated" }), _jsx("dd", { children: v.detail.updatedAt }), _jsx("dt", { children: "Version" }), _jsx("dd", { children: v.detail.version })] });
13
+ }
14
+ export function MemberRow({ member }) {
5
15
  const v = useMemberRow({ member });
6
- return _jsxs(_Fragment, { children: [_jsxs("tr", { children: [_jsx("td", { children: _jsx(Button, { onPress: v.toggle, attrs: { 'aria-expanded': v.expanded }, children: member.email }) }), _jsx("td", { children: member.name ?? '—' }), _jsx("td", { children: _jsx(Badge, { children: member.status }) }), _jsx("td", { children: v.created }), _jsxs("td", { children: [v.items.length ? _jsx(Menu, { label: v.menuLabel, items: v.items }) : null, v.rs.error ? _jsx(Notice, { tone: "danger", children: v.rs.error }) : null, v.rs.notice ? _jsx(Notice, { children: v.rs.notice }) : null] })] }), v.expanded ? _jsx("tr", { children: _jsx("td", { colSpan: 5, children: v.loading ? _jsx(Spinner, { label: "Loading detail\u2026" }) : v.s.detailError ? _jsx(Notice, { tone: "danger", children: v.s.detailError }) : v.detail ? _jsxs("dl", { children: [_jsx("dt", { children: "ID" }), _jsx("dd", { children: v.detail.id }), _jsx("dt", { children: "Email verified" }), _jsx("dd", { children: v.verified }), _jsx("dt", { children: "Updated" }), _jsx("dd", { children: v.detail.updatedAt }), _jsx("dt", { children: "Version" }), _jsx("dd", { children: v.detail.version })] }) : null }) }) : null] });
16
+ return _jsxs(_Fragment, { children: [_jsxs("tr", { children: [_jsx("td", { children: _jsx(Button, { onPress: v.toggle, attrs: { 'aria-expanded': v.expanded }, children: member.email }) }), _jsx("td", { children: member.name ?? '—' }), _jsx("td", { children: _jsx(Badge, { children: member.status }) }), _jsx("td", { children: v.created }), _jsxs("td", { children: [v.items.length ? _jsx(Menu, { label: v.menuLabel, items: v.items }) : null, v.rs.error ? _jsx(Notice, { tone: "danger", children: v.rs.error }) : null, v.rs.notice ? _jsx(Notice, { children: v.rs.notice }) : null] })] }), v.expanded ? _jsx("tr", { children: _jsx("td", { colSpan: 5, children: _jsx(MemberDetail, { v: v }) }) }) : null] });
7
17
  }
8
18
  //# sourceMappingURL=MemberRow.js.map
@@ -1,33 +1,18 @@
1
- import type { ComponentType, LazyExoticComponent, ReactNode } from 'react';
1
+ import { type AuthScope } from './AuthScope.hooks.js';
2
+ export { useAuthScope } from './AuthScope.hooks.js';
3
+ export type { AuthScope, AuthPageProps } from './AuthScope.hooks.js';
4
+ import type { ReactNode } from 'react';
2
5
  import type { AdminInstance } from '@jini-ai/admin/core/module';
3
- import type { AuthApiPort } from '../../ports.js';
4
6
  import { authModule } from '../../auth.module.js';
5
- import type { AdminAccount } from "../../models.js";
6
- export interface AuthScope {
7
- readonly api: AuthApiPort;
8
- readonly permissions: readonly string[];
9
- }
10
- export interface AuthPageProps {
11
- readonly tabs: Readonly<Record<string, LazyExoticComponent<ComponentType<{
12
- readonly params?: Readonly<Record<string, unknown>>;
13
- }>>>>;
14
- readonly description: ReturnType<AdminInstance['describe']>['pages'][number];
15
- readonly title?: string;
16
- readonly initialUsername?: string;
17
- readonly onLogin: (required: {
18
- user: AdminAccount;
19
- }, optional?: Record<string, never>) => void;
20
- }
21
- export declare function useAuthScope(_required?: Record<string, never>, _optional?: Record<string, never>): AuthScope;
22
7
  /** Descriptor identity must be the exact enriched object supplied to createAdmin. */
23
8
  export declare function createAuthBinding({ module }: {
24
9
  module: typeof authModule;
25
10
  }, _optional?: Record<string, never>): {
26
11
  pages: {
27
12
  auth: {
28
- Page: LazyExoticComponent<typeof import("../pages/LoginPage.js").default>;
13
+ Page: import("react").LazyExoticComponent<typeof import("../pages/LoginPage.js").default>;
29
14
  tabs: {
30
- auth: LazyExoticComponent<typeof import("../tabs/AuthTab.js").default>;
15
+ auth: import("react").LazyExoticComponent<typeof import("../tabs/AuthTab.js").default>;
31
16
  };
32
17
  };
33
18
  };
@@ -1,12 +1,7 @@
1
- import { createContext, createElement, lazy, useContext } from 'react';
1
+ import { ScopeContext } from './AuthScope.hooks.js';
2
+ export { useAuthScope } from './AuthScope.hooks.js';
3
+ import { createElement, lazy } from 'react';
2
4
  import { authModule } from '../../auth.module.js';
3
- const ScopeContext = createContext(null);
4
- export function useAuthScope(_required = {}, _optional = {}) {
5
- const scope = useContext(ScopeContext);
6
- if (!scope)
7
- throw new Error('Auth admin scope is unavailable.');
8
- return scope;
9
- }
10
5
  /** Descriptor identity must be the exact enriched object supplied to createAdmin. */
11
6
  export function createAuthBinding({ module }, _optional = {}) {
12
7
  const pages = { auth: { Page: lazy(() => import('../pages/LoginPage.js')), tabs: { auth: lazy(() => import('../tabs/AuthTab.js')) } } };
@@ -0,0 +1,22 @@
1
+ import type { ComponentType, LazyExoticComponent } from 'react';
2
+ import type { AdminInstance } from '@jini-ai/admin/core/module';
3
+ import type { AuthApiPort } from '../../ports.js';
4
+ import type { AdminAccount } from '../../models.js';
5
+ export interface AuthScope {
6
+ readonly api: AuthApiPort;
7
+ readonly permissions: readonly string[];
8
+ }
9
+ export declare const ScopeContext: import("react").Context<AuthScope | null>;
10
+ export interface AuthPageProps {
11
+ readonly tabs: Readonly<Record<string, LazyExoticComponent<ComponentType<{
12
+ readonly params?: Readonly<Record<string, unknown>>;
13
+ }>>>>;
14
+ readonly description: ReturnType<AdminInstance['describe']>['pages'][number];
15
+ readonly title?: string;
16
+ readonly initialUsername?: string;
17
+ readonly onLogin: (required: {
18
+ user: AdminAccount;
19
+ }, optional?: Record<string, never>) => void;
20
+ }
21
+ export declare function useAuthScope(_required?: Record<string, never>, _optional?: Record<string, never>): AuthScope;
22
+ //# sourceMappingURL=AuthScope.hooks.d.ts.map
@@ -0,0 +1,10 @@
1
+ // Scope and page contracts must not import the lazy page/binding assembly.
2
+ import { createContext, useContext } from 'react';
3
+ export const ScopeContext = createContext(null);
4
+ export function useAuthScope(_required = {}, _optional = {}) {
5
+ const scope = useContext(ScopeContext);
6
+ if (!scope)
7
+ throw new Error('Auth admin scope is unavailable.');
8
+ return scope;
9
+ }
10
+ //# sourceMappingURL=AuthScope.hooks.js.map
@@ -1,7 +1,7 @@
1
1
  import type { FormEvent } from 'react';
2
2
  import type { AuthState } from '../../models.js';
3
3
  import type { AuthController } from '../../controllers/auth.controller.js';
4
- import type { AuthPageProps } from './AuthBinding.hooks.js';
4
+ import type { AuthPageProps } from './AuthScope.hooks.js';
5
5
  export interface AuthView {
6
6
  controller: AuthController;
7
7
  state: AuthState;
@@ -1,6 +1,6 @@
1
1
  import { createElement, createContext, useContext, useMemo } from 'react';
2
2
  import { createAuthController } from '../../controllers/auth.controller.js';
3
- import { useAuthScope } from './AuthBinding.hooks.js';
3
+ import { useAuthScope } from './AuthScope.hooks.js';
4
4
  import { usePeopleController } from './PeopleController.hooks.js';
5
5
  export const AuthViewContext = createContext(null);
6
6
  export function useLoginPage(props, _optional = {}) {
@@ -1,29 +1,18 @@
1
- import type { ComponentType, LazyExoticComponent, ReactNode } from 'react';
1
+ import { type MembersScope } from './MembersScope.hooks.js';
2
+ export { useMembersScope } from './MembersScope.hooks.js';
3
+ export type { MembersScope, MembersPageProps } from './MembersScope.hooks.js';
4
+ import type { ReactNode } from 'react';
2
5
  import type { AdminInstance } from '@jini-ai/admin/core/module';
3
- import type { MembersApiPort } from '../../ports.js';
4
6
  import { membersModule } from '../../members.module.js';
5
- import type { MembersRefreshPort } from "../../ports.js";
6
- export interface MembersScope {
7
- readonly api: MembersApiPort;
8
- readonly permissions: readonly string[];
9
- }
10
- export interface MembersPageProps {
11
- readonly tabs: Readonly<Record<string, LazyExoticComponent<ComponentType<{
12
- readonly params?: Readonly<Record<string, unknown>>;
13
- }>>>>;
14
- readonly description: ReturnType<AdminInstance['describe']>['pages'][number];
15
- readonly refresh?: MembersRefreshPort;
16
- }
17
- export declare function useMembersScope(_required?: Record<string, never>, _optional?: Record<string, never>): MembersScope;
18
7
  /** Descriptor identity must be the exact enriched object supplied to createAdmin. */
19
8
  export declare function createMembersBinding({ module }: {
20
9
  module: typeof membersModule;
21
10
  }, _optional?: Record<string, never>): {
22
11
  pages: {
23
12
  members: {
24
- Page: LazyExoticComponent<typeof import("../pages/MembersPage.js").default>;
13
+ Page: import("react").LazyExoticComponent<typeof import("../pages/MembersPage.js").default>;
25
14
  tabs: {
26
- members: LazyExoticComponent<typeof import("../tabs/MembersTab.js").default>;
15
+ members: import("react").LazyExoticComponent<typeof import("../tabs/MembersTab.js").default>;
27
16
  };
28
17
  };
29
18
  };
@@ -1,12 +1,7 @@
1
- import { createContext, createElement, lazy, useContext } from 'react';
1
+ import { ScopeContext } from './MembersScope.hooks.js';
2
+ export { useMembersScope } from './MembersScope.hooks.js';
3
+ import { createElement, lazy } from 'react';
2
4
  import { membersModule } from '../../members.module.js';
3
- const ScopeContext = createContext(null);
4
- export function useMembersScope(_required = {}, _optional = {}) {
5
- const scope = useContext(ScopeContext);
6
- if (!scope)
7
- throw new Error('Members admin scope is unavailable.');
8
- return scope;
9
- }
10
5
  /** Descriptor identity must be the exact enriched object supplied to createAdmin. */
11
6
  export function createMembersBinding({ module }, _optional = {}) {
12
7
  const pages = { members: { Page: lazy(() => import('../pages/MembersPage.js')), tabs: { members: lazy(() => import('../tabs/MembersTab.js')) } } };
@@ -1,7 +1,7 @@
1
1
  import type { MembersState, AdminMember } from '../../models.js';
2
2
  import type { MembersController } from '../../controllers/members.controller.js';
3
3
  import type { MenuItem } from '@jini-ai/ui-kit/react';
4
- import type { MembersPageProps } from './MembersBinding.hooks.js';
4
+ import type { MembersPageProps } from './MembersScope.hooks.js';
5
5
  export interface MembersView {
6
6
  controller: MembersController;
7
7
  state: MembersState;
@@ -1,7 +1,7 @@
1
1
  import { createElement, createContext, useContext, useMemo } from 'react';
2
2
  import { createMembersController } from '../../controllers/members.controller.js';
3
3
  import { hasAdminGrant } from '../../people.rules.js';
4
- import { useMembersScope } from './MembersBinding.hooks.js';
4
+ import { useMembersScope } from './MembersScope.hooks.js';
5
5
  import { usePeopleController } from './PeopleController.hooks.js';
6
6
  export const MembersViewContext = createContext(null);
7
7
  export function useMembersView(_required = {}, _optional = {}) { const v = useContext(MembersViewContext); if (!v)
@@ -0,0 +1,17 @@
1
+ import type { ComponentType, LazyExoticComponent } from 'react';
2
+ import type { AdminInstance } from '@jini-ai/admin/core/module';
3
+ import type { MembersApiPort, MembersRefreshPort } from '../../ports.js';
4
+ export interface MembersScope {
5
+ readonly api: MembersApiPort;
6
+ readonly permissions: readonly string[];
7
+ }
8
+ export declare const ScopeContext: import("react").Context<MembersScope | null>;
9
+ export interface MembersPageProps {
10
+ readonly tabs: Readonly<Record<string, LazyExoticComponent<ComponentType<{
11
+ readonly params?: Readonly<Record<string, unknown>>;
12
+ }>>>>;
13
+ readonly description: ReturnType<AdminInstance['describe']>['pages'][number];
14
+ readonly refresh?: MembersRefreshPort;
15
+ }
16
+ export declare function useMembersScope(_required?: Record<string, never>, _optional?: Record<string, never>): MembersScope;
17
+ //# sourceMappingURL=MembersScope.hooks.d.ts.map
@@ -0,0 +1,10 @@
1
+ // Scope and page contracts must not import the lazy page/binding assembly.
2
+ import { createContext, useContext } from 'react';
3
+ export const ScopeContext = createContext(null);
4
+ export function useMembersScope(_required = {}, _optional = {}) {
5
+ const scope = useContext(ScopeContext);
6
+ if (!scope)
7
+ throw new Error('Members admin scope is unavailable.');
8
+ return scope;
9
+ }
10
+ //# sourceMappingURL=MembersScope.hooks.js.map
@@ -1,33 +1,19 @@
1
- import type { ComponentType, LazyExoticComponent, ReactNode } from 'react';
1
+ import { type RolesScope } from './RolesScope.hooks.js';
2
+ export { useRolesScope } from './RolesScope.hooks.js';
3
+ export type { RolesScope, RolesTabProps, RolesPageProps } from './RolesScope.hooks.js';
4
+ import type { ReactNode } from 'react';
2
5
  import type { AdminInstance } from '@jini-ai/admin/core/module';
3
6
  import { rolesModule } from '../../roles.module.js';
4
- import type { RolesApiPort } from '../../ports.js';
5
- export interface RolesScope {
6
- readonly api: RolesApiPort;
7
- readonly permissions: readonly string[];
8
- }
9
- export interface RolesTabProps {
10
- readonly params?: Readonly<Record<string, unknown>>;
11
- }
12
- export interface RolesPageProps {
13
- readonly tabs: Readonly<Record<string, LazyExoticComponent<ComponentType<RolesTabProps>>>>;
14
- readonly description: ReturnType<AdminInstance['describe']>['pages'][number];
15
- readonly requestedTab?: string;
16
- readonly onTabChange?: (required: {
17
- tab: string;
18
- }, optional?: Record<string, never>) => void;
19
- }
20
- export declare function useRolesScope(_required?: Record<string, never>, _optional?: Record<string, never>): RolesScope;
21
7
  /** A private scope binds only this module's declared ports. No admin React internals are needed. */
22
8
  export declare function createRolesBinding({ module }: {
23
9
  module: typeof rolesModule;
24
10
  }, _optional?: Record<string, never>): {
25
11
  pages: {
26
12
  roles: {
27
- Page: LazyExoticComponent<typeof import("../pages/RolesPage.js").default>;
13
+ Page: import("react").LazyExoticComponent<typeof import("../pages/RolesPage.js").default>;
28
14
  tabs: {
29
- roles: LazyExoticComponent<typeof import("../tabs/RolesTab.js").default>;
30
- policies: LazyExoticComponent<typeof import("../tabs/PoliciesTab.js").default>;
15
+ roles: import("react").LazyExoticComponent<typeof import("../tabs/RolesTab.js").default>;
16
+ policies: import("react").LazyExoticComponent<typeof import("../tabs/PoliciesTab.js").default>;
31
17
  };
32
18
  };
33
19
  };
@@ -1,12 +1,7 @@
1
- import { createContext, createElement, lazy, useContext } from 'react';
1
+ import { ScopeContext } from './RolesScope.hooks.js';
2
+ export { useRolesScope } from './RolesScope.hooks.js';
3
+ import { createElement, lazy } from 'react';
2
4
  import { rolesModule } from '../../roles.module.js';
3
- const ScopeContext = createContext(null);
4
- export function useRolesScope(_required = {}, _optional = {}) {
5
- const scope = useContext(ScopeContext);
6
- if (!scope)
7
- throw new Error('Roles admin scope is unavailable.');
8
- return scope;
9
- }
10
5
  /** A private scope binds only this module's declared ports. No admin React internals are needed. */
11
6
  export function createRolesBinding({ module }, _optional = {}) {
12
7
  const pages = { roles: { Page: lazy(() => import('../pages/RolesPage.js')), tabs: {
@@ -1,6 +1,6 @@
1
1
  import { useCallback, useEffect, useState, useSyncExternalStore } from 'react';
2
2
  import { createRolesController } from '../../controllers/roles.controller.js';
3
- import { useRolesScope } from './RolesBinding.hooks.js';
3
+ import { useRolesScope } from './RolesScope.hooks.js';
4
4
  /** Controller creation belongs to the effect, so StrictMode replay gets a fresh instance.
5
5
  * Both tabs read the page-owned controller; neither tab starts its own duplicate fetches. */
6
6
  export function useRolesController(_required = {}, _optional = {}) {
@@ -1,4 +1,4 @@
1
- import type { RolesPageProps } from './RolesBinding.hooks.js';
1
+ import type { RolesPageProps } from './RolesScope.hooks.js';
2
2
  import type { RolesController } from '../../controllers/roles.controller.js';
3
3
  import type { RolesState } from '../../models.js';
4
4
  import type { ConfirmDialogProps } from '@jini-ai/ui-kit/react';
@@ -51,7 +51,7 @@ export declare function useRolesPage(props: RolesPageProps, _optional?: Record<s
51
51
  items: {
52
52
  id: string;
53
53
  label: string;
54
- content: import("react").FunctionComponentElement<import("./RolesBinding.hooks.js").RolesTabProps | (import("./RolesBinding.hooks.js").RolesTabProps & import("react").RefAttributes<import("react").Component<import("./RolesBinding.hooks.js").RolesTabProps, any, any>>)> | null;
54
+ content: import("react").FunctionComponentElement<import("./RolesScope.hooks.js").RolesTabProps | (import("./RolesScope.hooks.js").RolesTabProps & import("react").RefAttributes<import("react").Component<import("./RolesScope.hooks.js").RolesTabProps, any, any>>)> | null;
55
55
  }[];
56
56
  tab: string;
57
57
  confirm: ConfirmDialogProps;