@byline/admin 1.9.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/dist/index.d.ts CHANGED
@@ -22,6 +22,7 @@
22
22
  */
23
23
  export { registerAdminAbilities } from './abilities.js';
24
24
  export { assertAdminActor, requireAdminActor } from './lib/assert-admin-actor.js';
25
+ export { type Command, type CreateCommandAuthSpec, type CreateCommandHandlerArgs, type CreateCommandSpec, createCommand, } from './lib/create-command.js';
25
26
  export * from './modules/admin-account/index.js';
26
27
  export * from './modules/admin-permissions/index.js';
27
28
  export * from './modules/admin-roles/index.js';
package/dist/index.js CHANGED
@@ -22,6 +22,7 @@
22
22
  */
23
23
  export { registerAdminAbilities } from './abilities.js';
24
24
  export { assertAdminActor, requireAdminActor } from './lib/assert-admin-actor.js';
25
+ export { createCommand, } from './lib/create-command.js';
25
26
  export * from './modules/admin-account/index.js';
26
27
  export * from './modules/admin-permissions/index.js';
27
28
  export * from './modules/admin-roles/index.js';
@@ -0,0 +1,77 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+ import type { AdminAuth, RequestContext } from '@byline/auth';
9
+ import type { ZodType } from 'zod';
10
+ /**
11
+ * `createCommand` — the wrapper that folds the four-step admin command
12
+ * contract (validate → authorise → invoke → shape) into a single
13
+ * declaration.
14
+ *
15
+ * Implements Phase 1 of `docs/CORE-COMPOSITION.md`. Today's scope is
16
+ * `@byline/admin`-internal: it gates against admin actor identity using
17
+ * the existing `assertAdminActor` / `requireAdminActor` helpers, which
18
+ * inherit the super-admin bypass from `AdminAuth.assertAbility`.
19
+ *
20
+ * The `auth` slot is a discriminated union:
21
+ *
22
+ * - `{ ability }` — full admin gate. Requires an `AdminAuth`
23
+ * actor holding the named ability. Maps to
24
+ * `assertAdminActor`.
25
+ * - `{ authenticated }` — identity gate only. Requires an `AdminAuth`
26
+ * actor but does not assert any ability. Used
27
+ * by self-service commands in `admin-account`
28
+ * where the security property is "you may
29
+ * only mutate your own row" and the target
30
+ * id is sourced from `actor.id`.
31
+ *
32
+ * The handler receives an args object so it can cherry-pick what it
33
+ * needs without positional ordering — `context` for downstream calls
34
+ * that need the full request context, `input` (already Zod-parsed),
35
+ * `deps` (typed by the module), and `actor` (already narrowed to
36
+ * `AdminAuth` by the auth step).
37
+ *
38
+ * The returned command preserves today's `(context, input, deps) =>
39
+ * Promise<Output>` signature so existing server-fn call sites keep
40
+ * working without change.
41
+ *
42
+ * Collection-document operations (create / update / delete / status /
43
+ * upload) are gated through a separate helper, `assertActorCanPerform`,
44
+ * which fires inside the `document-lifecycle` service functions in
45
+ * `@byline/core`. They do not flow through this wrapper today; if the
46
+ * two enforcement paths ever converge, the `auth` discriminator can
47
+ * grow a `collection` variant without breaking existing call sites.
48
+ */
49
+ export type CreateCommandAuthSpec = {
50
+ readonly ability: string;
51
+ readonly authenticated?: never;
52
+ } | {
53
+ readonly authenticated: true;
54
+ readonly ability?: never;
55
+ };
56
+ export interface CreateCommandHandlerArgs<TInput, TDeps> {
57
+ readonly context: RequestContext;
58
+ readonly input: TInput;
59
+ readonly deps: TDeps;
60
+ readonly actor: AdminAuth;
61
+ }
62
+ export interface CreateCommandSpec<TInput, TOutput, TDeps> {
63
+ /**
64
+ * Stable identifier for the command, used in error messages and
65
+ * future telemetry (Phase 1 of `CORE-COMPOSITION.md` calls out
66
+ * uniform logging as a downstream benefit of the wrapper).
67
+ */
68
+ readonly method: string;
69
+ readonly auth: CreateCommandAuthSpec;
70
+ readonly schemas: {
71
+ readonly input: ZodType<TInput>;
72
+ readonly output: ZodType<TOutput>;
73
+ };
74
+ readonly handler: (args: CreateCommandHandlerArgs<TInput, TDeps>) => Promise<TOutput> | TOutput;
75
+ }
76
+ export type Command<_TInput, TOutput, TDeps> = (context: RequestContext | undefined, input: unknown, deps: TDeps) => Promise<TOutput>;
77
+ export declare function createCommand<TInput, TOutput, TDeps>(spec: CreateCommandSpec<TInput, TOutput, TDeps>): Command<TInput, TOutput, TDeps>;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+ import { assertAdminActor, requireAdminActor } from './assert-admin-actor.js';
9
+ export function createCommand(spec) {
10
+ return async function command(context, input, deps) {
11
+ const parsed = spec.schemas.input.parse(input ?? {});
12
+ const actor = spec.auth.ability !== undefined
13
+ ? assertAdminActor(context, spec.auth.ability)
14
+ : requireAdminActor(context, spec.method);
15
+ // `context` is non-null after the auth step — both helpers throw
16
+ // `ERR_UNAUTHENTICATED` when the request context is missing.
17
+ const result = await spec.handler({
18
+ context: context,
19
+ input: parsed,
20
+ deps,
21
+ actor,
22
+ });
23
+ return spec.schemas.output.parse(result);
24
+ };
25
+ }
@@ -5,25 +5,23 @@
5
5
  *
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
- import type { RequestContext } from '@byline/auth';
8
+ import { type Command } from '../../lib/create-command.js';
9
9
  import type { AdminStore } from '../../store.js';
10
- import type { AccountResponse } from './schemas.js';
10
+ import type { AccountResponse, ChangeAccountPasswordRequest, GetAccountRequest, UpdateAccountRequest } from './schemas.js';
11
11
  /**
12
12
  * Transport-agnostic commands for admin-account self-service.
13
13
  *
14
- * Same shape as the other admin module commands (`*-users`, `*-roles`,
15
- * `*-permissions`) with one deliberate difference: enforcement uses
16
- * `requireAdminActor` rather than `assertAdminActor`. There is no
17
- * ability key to gate against the security property is "you may
18
- * only mutate your own row," and these commands enforce it
19
- * structurally by sourcing the target id from `actor.id` rather than
20
- * from the request payload. A request with an `id` field would have
21
- * no way to express "operate on someone else" because the schemas
22
- * don't accept one.
14
+ * Same `createCommand` shape as the other admin modules, with one
15
+ * deliberate difference: `auth` is `{ authenticated: true }` rather than
16
+ * `{ ability }`. There is no ability key to gate against — the security
17
+ * property is "you may only mutate your own row," enforced structurally
18
+ * by sourcing the target id from `actor.id` rather than from the request
19
+ * payload. The request schemas do not accept an `id` field, so a caller
20
+ * has no way to express "operate on someone else."
23
21
  */
24
22
  export interface AdminAccountCommandDeps {
25
23
  store: AdminStore;
26
24
  }
27
- export declare function getAccountCommand(context: RequestContext | undefined, input: unknown, deps: AdminAccountCommandDeps): Promise<AccountResponse>;
28
- export declare function updateAccountCommand(context: RequestContext | undefined, input: unknown, deps: AdminAccountCommandDeps): Promise<AccountResponse>;
29
- export declare function changeAccountPasswordCommand(context: RequestContext | undefined, input: unknown, deps: AdminAccountCommandDeps): Promise<AccountResponse>;
25
+ export declare const getAccountCommand: Command<GetAccountRequest, AccountResponse, AdminAccountCommandDeps>;
26
+ export declare const updateAccountCommand: Command<UpdateAccountRequest, AccountResponse, AdminAccountCommandDeps>;
27
+ export declare const changeAccountPasswordCommand: Command<ChangeAccountPasswordRequest, AccountResponse, AdminAccountCommandDeps>;
@@ -5,31 +5,28 @@
5
5
  *
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
- import { requireAdminActor } from '../../lib/assert-admin-actor.js';
8
+ import { createCommand } from '../../lib/create-command.js';
9
9
  import { adminUserResponseSchema } from '../admin-users/schemas.js';
10
10
  import { changeAccountPasswordRequestSchema, getAccountRequestSchema, updateAccountRequestSchema, } from './schemas.js';
11
11
  import { AdminAccountService } from './service.js';
12
12
  function serviceOf(deps) {
13
13
  return new AdminAccountService({ repo: deps.store.adminUsers });
14
14
  }
15
- export async function getAccountCommand(context, input, deps) {
16
- // No-op parse — `getAccountRequestSchema` is `{}.strict()` so it
17
- // rejects stray payloads but yields no usable data. The schema is
18
- // validated for shape consistency with the other commands.
19
- getAccountRequestSchema.parse(input ?? {});
20
- const actor = requireAdminActor(context, 'reading own admin account');
21
- const result = await serviceOf(deps).getAccount(actor.id);
22
- return adminUserResponseSchema.parse(result);
23
- }
24
- export async function updateAccountCommand(context, input, deps) {
25
- const parsed = updateAccountRequestSchema.parse(input);
26
- const actor = requireAdminActor(context, 'updating own admin account');
27
- const result = await serviceOf(deps).updateAccount(actor.id, parsed);
28
- return adminUserResponseSchema.parse(result);
29
- }
30
- export async function changeAccountPasswordCommand(context, input, deps) {
31
- const parsed = changeAccountPasswordRequestSchema.parse(input);
32
- const actor = requireAdminActor(context, 'changing own admin password');
33
- const result = await serviceOf(deps).changePassword(actor.id, parsed);
34
- return adminUserResponseSchema.parse(result);
35
- }
15
+ export const getAccountCommand = createCommand({
16
+ method: 'getAccount',
17
+ auth: { authenticated: true },
18
+ schemas: { input: getAccountRequestSchema, output: adminUserResponseSchema },
19
+ handler: ({ deps, actor }) => serviceOf(deps).getAccount(actor.id),
20
+ });
21
+ export const updateAccountCommand = createCommand({
22
+ method: 'updateAccount',
23
+ auth: { authenticated: true },
24
+ schemas: { input: updateAccountRequestSchema, output: adminUserResponseSchema },
25
+ handler: ({ input, deps, actor }) => serviceOf(deps).updateAccount(actor.id, input),
26
+ });
27
+ export const changeAccountPasswordCommand = createCommand({
28
+ method: 'changeAccountPassword',
29
+ auth: { authenticated: true },
30
+ schemas: { input: changeAccountPasswordRequestSchema, output: adminUserResponseSchema },
31
+ handler: ({ input, deps, actor }) => serviceOf(deps).changePassword(actor.id, input),
32
+ });
@@ -5,15 +5,17 @@
5
5
  *
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
- import type { AbilityRegistry, RequestContext } from '@byline/auth';
8
+ import type { AbilityRegistry } from '@byline/auth';
9
+ import { type Command } from '../../lib/create-command.js';
9
10
  import type { AdminStore } from '../../store.js';
10
- import type { GetRoleAbilitiesResponse, ListRegisteredAbilitiesResponse, SetRoleAbilitiesResponse, WhoHasAbilityResponse } from './schemas.js';
11
+ import type { GetRoleAbilitiesRequest, GetRoleAbilitiesResponse, ListRegisteredAbilitiesRequest, ListRegisteredAbilitiesResponse, SetRoleAbilitiesRequest, SetRoleAbilitiesResponse, WhoHasAbilityRequest, WhoHasAbilityResponse } from './schemas.js';
11
12
  /**
12
13
  * Transport-agnostic commands for the admin-permissions inspector.
13
14
  *
14
- * Same four-step shape as the other modules — Zod-validate, assert the
15
- * admin actor + ability, call the service, validate the output. The
16
- * inspector commands all gate on `admin.permissions.read`.
15
+ * Built through `createCommand`, which folds the four standard steps
16
+ * (validate → assert admin actor + ability invoke service validate
17
+ * output) into one declaration. All inspector reads gate on
18
+ * `admin.permissions.read`; the write gates on `admin.permissions.update`.
17
19
  *
18
20
  * Deps include the `AbilityRegistry` alongside the `AdminStore` because
19
21
  * the inspector reads the registered abilities directly from the
@@ -23,7 +25,7 @@ export interface AdminPermissionsCommandDeps {
23
25
  store: AdminStore;
24
26
  abilities: AbilityRegistry;
25
27
  }
26
- export declare function listRegisteredAbilitiesCommand(context: RequestContext | undefined, input: unknown, deps: AdminPermissionsCommandDeps): Promise<ListRegisteredAbilitiesResponse>;
27
- export declare function whoHasAbilityCommand(context: RequestContext | undefined, input: unknown, deps: AdminPermissionsCommandDeps): Promise<WhoHasAbilityResponse>;
28
- export declare function getRoleAbilitiesCommand(context: RequestContext | undefined, input: unknown, deps: AdminPermissionsCommandDeps): Promise<GetRoleAbilitiesResponse>;
29
- export declare function setRoleAbilitiesCommand(context: RequestContext | undefined, input: unknown, deps: AdminPermissionsCommandDeps): Promise<SetRoleAbilitiesResponse>;
28
+ export declare const listRegisteredAbilitiesCommand: Command<ListRegisteredAbilitiesRequest, ListRegisteredAbilitiesResponse, AdminPermissionsCommandDeps>;
29
+ export declare const whoHasAbilityCommand: Command<WhoHasAbilityRequest, WhoHasAbilityResponse, AdminPermissionsCommandDeps>;
30
+ export declare const getRoleAbilitiesCommand: Command<GetRoleAbilitiesRequest, GetRoleAbilitiesResponse, AdminPermissionsCommandDeps>;
31
+ export declare const setRoleAbilitiesCommand: Command<SetRoleAbilitiesRequest, SetRoleAbilitiesResponse, AdminPermissionsCommandDeps>;
@@ -5,34 +5,37 @@
5
5
  *
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
- import { assertAdminActor } from '../../lib/assert-admin-actor.js';
8
+ import { createCommand } from '../../lib/create-command.js';
9
9
  import { ADMIN_PERMISSIONS_ABILITIES } from './abilities.js';
10
10
  import { getRoleAbilitiesRequestSchema, getRoleAbilitiesResponseSchema, listRegisteredAbilitiesRequestSchema, listRegisteredAbilitiesResponseSchema, setRoleAbilitiesRequestSchema, setRoleAbilitiesResponseSchema, whoHasAbilityRequestSchema, whoHasAbilityResponseSchema, } from './schemas.js';
11
11
  import { AdminPermissionsService } from './service.js';
12
12
  function serviceOf(deps) {
13
13
  return new AdminPermissionsService({ store: deps.store, abilities: deps.abilities });
14
14
  }
15
- export async function listRegisteredAbilitiesCommand(context, input, deps) {
16
- listRegisteredAbilitiesRequestSchema.parse(input ?? {});
17
- assertAdminActor(context, ADMIN_PERMISSIONS_ABILITIES.read);
18
- const result = serviceOf(deps).listRegisteredAbilities();
19
- return listRegisteredAbilitiesResponseSchema.parse(result);
20
- }
21
- export async function whoHasAbilityCommand(context, input, deps) {
22
- const parsed = whoHasAbilityRequestSchema.parse(input);
23
- assertAdminActor(context, ADMIN_PERMISSIONS_ABILITIES.read);
24
- const result = await serviceOf(deps).whoHasAbility(parsed);
25
- return whoHasAbilityResponseSchema.parse(result);
26
- }
27
- export async function getRoleAbilitiesCommand(context, input, deps) {
28
- const parsed = getRoleAbilitiesRequestSchema.parse(input);
29
- assertAdminActor(context, ADMIN_PERMISSIONS_ABILITIES.read);
30
- const result = await serviceOf(deps).getRoleAbilities(parsed);
31
- return getRoleAbilitiesResponseSchema.parse(result);
32
- }
33
- export async function setRoleAbilitiesCommand(context, input, deps) {
34
- const parsed = setRoleAbilitiesRequestSchema.parse(input);
35
- assertAdminActor(context, ADMIN_PERMISSIONS_ABILITIES.update);
36
- const result = await serviceOf(deps).setRoleAbilities(parsed);
37
- return setRoleAbilitiesResponseSchema.parse(result);
38
- }
15
+ export const listRegisteredAbilitiesCommand = createCommand({
16
+ method: 'listRegisteredAbilities',
17
+ auth: { ability: ADMIN_PERMISSIONS_ABILITIES.read },
18
+ schemas: {
19
+ input: listRegisteredAbilitiesRequestSchema,
20
+ output: listRegisteredAbilitiesResponseSchema,
21
+ },
22
+ handler: ({ deps }) => serviceOf(deps).listRegisteredAbilities(),
23
+ });
24
+ export const whoHasAbilityCommand = createCommand({
25
+ method: 'whoHasAbility',
26
+ auth: { ability: ADMIN_PERMISSIONS_ABILITIES.read },
27
+ schemas: { input: whoHasAbilityRequestSchema, output: whoHasAbilityResponseSchema },
28
+ handler: ({ input, deps }) => serviceOf(deps).whoHasAbility(input),
29
+ });
30
+ export const getRoleAbilitiesCommand = createCommand({
31
+ method: 'getRoleAbilities',
32
+ auth: { ability: ADMIN_PERMISSIONS_ABILITIES.read },
33
+ schemas: { input: getRoleAbilitiesRequestSchema, output: getRoleAbilitiesResponseSchema },
34
+ handler: ({ input, deps }) => serviceOf(deps).getRoleAbilities(input),
35
+ });
36
+ export const setRoleAbilitiesCommand = createCommand({
37
+ method: 'setRoleAbilities',
38
+ auth: { ability: ADMIN_PERMISSIONS_ABILITIES.update },
39
+ schemas: { input: setRoleAbilitiesRequestSchema, output: setRoleAbilitiesResponseSchema },
40
+ handler: ({ input, deps }) => serviceOf(deps).setRoleAbilities(input),
41
+ });
@@ -5,19 +5,15 @@
5
5
  *
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
- import type { RequestContext } from '@byline/auth';
8
+ import { type Command } from '../../lib/create-command.js';
9
9
  import type { AdminStore } from '../../store.js';
10
- import type { AdminRoleListResponse, AdminRoleResponse, OkResponse, UserRolesResponse } from './schemas.js';
10
+ import type { AdminRoleListResponse, AdminRoleResponse, CreateAdminRoleRequest, DeleteAdminRoleRequest, GetAdminRoleRequest, GetRolesForUserRequest, ListAdminRolesRequest, OkResponse, ReorderAdminRolesRequest, SetRolesForUserRequest, UpdateAdminRoleRequest, UserRolesResponse } from './schemas.js';
11
11
  /**
12
12
  * Transport-agnostic commands for the admin-roles module.
13
13
  *
14
- * Every command follows the same four steps as `admin-users`:
15
- * 1. `schema.parse(input)` Zod-validate.
16
- * 2. `assertAdminActor(context, ability)` require an `AdminAuth`
17
- * actor holding the specific ability.
18
- * 3. Call the `AdminRolesService` method with the validated input.
19
- * 4. Parse the response through its output schema (catches
20
- * schema/DTO drift in tests).
14
+ * Every command goes through `createCommand`, which folds the four
15
+ * standard steps (validate assert admin actor + ability → invoke
16
+ * service → validate output) into one declaration.
21
17
  *
22
18
  * Reorder uses the `update` ability — see `abilities.ts` for the
23
19
  * rationale (same trust level as content updates; splitting it would
@@ -26,11 +22,11 @@ import type { AdminRoleListResponse, AdminRoleResponse, OkResponse, UserRolesRes
26
22
  export interface AdminRolesCommandDeps {
27
23
  store: AdminStore;
28
24
  }
29
- export declare function listAdminRolesCommand(context: RequestContext | undefined, input: unknown, deps: AdminRolesCommandDeps): Promise<AdminRoleListResponse>;
30
- export declare function getAdminRoleCommand(context: RequestContext | undefined, input: unknown, deps: AdminRolesCommandDeps): Promise<AdminRoleResponse>;
31
- export declare function createAdminRoleCommand(context: RequestContext | undefined, input: unknown, deps: AdminRolesCommandDeps): Promise<AdminRoleResponse>;
32
- export declare function updateAdminRoleCommand(context: RequestContext | undefined, input: unknown, deps: AdminRolesCommandDeps): Promise<AdminRoleResponse>;
33
- export declare function deleteAdminRoleCommand(context: RequestContext | undefined, input: unknown, deps: AdminRolesCommandDeps): Promise<OkResponse>;
34
- export declare function reorderAdminRolesCommand(context: RequestContext | undefined, input: unknown, deps: AdminRolesCommandDeps): Promise<OkResponse>;
35
- export declare function getRolesForUserCommand(context: RequestContext | undefined, input: unknown, deps: AdminRolesCommandDeps): Promise<UserRolesResponse>;
36
- export declare function setRolesForUserCommand(context: RequestContext | undefined, input: unknown, deps: AdminRolesCommandDeps): Promise<UserRolesResponse>;
25
+ export declare const listAdminRolesCommand: Command<ListAdminRolesRequest, AdminRoleListResponse, AdminRolesCommandDeps>;
26
+ export declare const getAdminRoleCommand: Command<GetAdminRoleRequest, AdminRoleResponse, AdminRolesCommandDeps>;
27
+ export declare const createAdminRoleCommand: Command<CreateAdminRoleRequest, AdminRoleResponse, AdminRolesCommandDeps>;
28
+ export declare const updateAdminRoleCommand: Command<UpdateAdminRoleRequest, AdminRoleResponse, AdminRolesCommandDeps>;
29
+ export declare const deleteAdminRoleCommand: Command<DeleteAdminRoleRequest, OkResponse, AdminRolesCommandDeps>;
30
+ export declare const reorderAdminRolesCommand: Command<ReorderAdminRolesRequest, OkResponse, AdminRolesCommandDeps>;
31
+ export declare const getRolesForUserCommand: Command<GetRolesForUserRequest, UserRolesResponse, AdminRolesCommandDeps>;
32
+ export declare const setRolesForUserCommand: Command<SetRolesForUserRequest, UserRolesResponse, AdminRolesCommandDeps>;
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
- import { assertAdminActor } from '../../lib/assert-admin-actor.js';
8
+ import { createCommand } from '../../lib/create-command.js';
9
9
  import { ADMIN_USERS_ABILITIES } from '../admin-users/abilities.js';
10
10
  import { ADMIN_ROLES_ABILITIES } from './abilities.js';
11
11
  import { adminRoleListResponseSchema, adminRoleResponseSchema, createAdminRoleRequestSchema, deleteAdminRoleRequestSchema, getAdminRoleRequestSchema, getRolesForUserRequestSchema, listAdminRolesRequestSchema, okResponseSchema, reorderAdminRolesRequestSchema, setRolesForUserRequestSchema, updateAdminRoleRequestSchema, userRolesResponseSchema, } from './schemas.js';
@@ -13,57 +13,63 @@ import { AdminRolesService } from './service.js';
13
13
  function serviceOf(deps) {
14
14
  return new AdminRolesService({ store: deps.store });
15
15
  }
16
- export async function listAdminRolesCommand(context, input, deps) {
17
- listAdminRolesRequestSchema.parse(input ?? {});
18
- assertAdminActor(context, ADMIN_ROLES_ABILITIES.read);
19
- const result = await serviceOf(deps).listRoles();
20
- return adminRoleListResponseSchema.parse(result);
21
- }
22
- export async function getAdminRoleCommand(context, input, deps) {
23
- const parsed = getAdminRoleRequestSchema.parse(input);
24
- assertAdminActor(context, ADMIN_ROLES_ABILITIES.read);
25
- const result = await serviceOf(deps).getRole(parsed);
26
- return adminRoleResponseSchema.parse(result);
27
- }
28
- export async function createAdminRoleCommand(context, input, deps) {
29
- const parsed = createAdminRoleRequestSchema.parse(input);
30
- assertAdminActor(context, ADMIN_ROLES_ABILITIES.create);
31
- const result = await serviceOf(deps).createRole(parsed);
32
- return adminRoleResponseSchema.parse(result);
33
- }
34
- export async function updateAdminRoleCommand(context, input, deps) {
35
- const parsed = updateAdminRoleRequestSchema.parse(input);
36
- assertAdminActor(context, ADMIN_ROLES_ABILITIES.update);
37
- const result = await serviceOf(deps).updateRole(parsed);
38
- return adminRoleResponseSchema.parse(result);
39
- }
40
- export async function deleteAdminRoleCommand(context, input, deps) {
41
- const parsed = deleteAdminRoleRequestSchema.parse(input);
42
- assertAdminActor(context, ADMIN_ROLES_ABILITIES.delete);
43
- await serviceOf(deps).deleteRole(parsed);
44
- return okResponseSchema.parse({ ok: true });
45
- }
46
- export async function reorderAdminRolesCommand(context, input, deps) {
47
- const parsed = reorderAdminRolesRequestSchema.parse(input);
48
- assertAdminActor(context, ADMIN_ROLES_ABILITIES.update);
49
- await serviceOf(deps).reorderRoles(parsed);
50
- return okResponseSchema.parse({ ok: true });
51
- }
52
- export async function getRolesForUserCommand(context, input, deps) {
53
- const parsed = getRolesForUserRequestSchema.parse(input);
16
+ export const listAdminRolesCommand = createCommand({
17
+ method: 'listAdminRoles',
18
+ auth: { ability: ADMIN_ROLES_ABILITIES.read },
19
+ schemas: { input: listAdminRolesRequestSchema, output: adminRoleListResponseSchema },
20
+ handler: ({ deps }) => serviceOf(deps).listRoles(),
21
+ });
22
+ export const getAdminRoleCommand = createCommand({
23
+ method: 'getAdminRole',
24
+ auth: { ability: ADMIN_ROLES_ABILITIES.read },
25
+ schemas: { input: getAdminRoleRequestSchema, output: adminRoleResponseSchema },
26
+ handler: ({ input, deps }) => serviceOf(deps).getRole(input),
27
+ });
28
+ export const createAdminRoleCommand = createCommand({
29
+ method: 'createAdminRole',
30
+ auth: { ability: ADMIN_ROLES_ABILITIES.create },
31
+ schemas: { input: createAdminRoleRequestSchema, output: adminRoleResponseSchema },
32
+ handler: ({ input, deps }) => serviceOf(deps).createRole(input),
33
+ });
34
+ export const updateAdminRoleCommand = createCommand({
35
+ method: 'updateAdminRole',
36
+ auth: { ability: ADMIN_ROLES_ABILITIES.update },
37
+ schemas: { input: updateAdminRoleRequestSchema, output: adminRoleResponseSchema },
38
+ handler: ({ input, deps }) => serviceOf(deps).updateRole(input),
39
+ });
40
+ export const deleteAdminRoleCommand = createCommand({
41
+ method: 'deleteAdminRole',
42
+ auth: { ability: ADMIN_ROLES_ABILITIES.delete },
43
+ schemas: { input: deleteAdminRoleRequestSchema, output: okResponseSchema },
44
+ handler: async ({ input, deps }) => {
45
+ await serviceOf(deps).deleteRole(input);
46
+ return { ok: true };
47
+ },
48
+ });
49
+ export const reorderAdminRolesCommand = createCommand({
50
+ method: 'reorderAdminRoles',
51
+ auth: { ability: ADMIN_ROLES_ABILITIES.update },
52
+ schemas: { input: reorderAdminRolesRequestSchema, output: okResponseSchema },
53
+ handler: async ({ input, deps }) => {
54
+ await serviceOf(deps).reorderRoles(input);
55
+ return { ok: true };
56
+ },
57
+ });
58
+ export const getRolesForUserCommand = createCommand({
59
+ method: 'getRolesForUser',
54
60
  // Reading a user's role assignments requires read access to admin
55
61
  // users — the data is fundamentally about that user.
56
- assertAdminActor(context, ADMIN_USERS_ABILITIES.read);
57
- const result = await serviceOf(deps).getRolesForUser(parsed);
58
- return userRolesResponseSchema.parse(result);
59
- }
60
- export async function setRolesForUserCommand(context, input, deps) {
61
- const parsed = setRolesForUserRequestSchema.parse(input);
62
+ auth: { ability: ADMIN_USERS_ABILITIES.read },
63
+ schemas: { input: getRolesForUserRequestSchema, output: userRolesResponseSchema },
64
+ handler: ({ input, deps }) => serviceOf(deps).getRolesForUser(input),
65
+ });
66
+ export const setRolesForUserCommand = createCommand({
67
+ method: 'setRolesForUser',
62
68
  // Editing a user's role-set is at the same trust level as updating
63
69
  // their other admin fields. Roll into `admin.users.update` rather
64
70
  // than minting a separate `admin.users.assignRoles` key — the role
65
71
  // editor's checkbox tree would otherwise need both.
66
- assertAdminActor(context, ADMIN_USERS_ABILITIES.update);
67
- const result = await serviceOf(deps).setRolesForUser(parsed);
68
- return userRolesResponseSchema.parse(result);
69
- }
72
+ auth: { ability: ADMIN_USERS_ABILITIES.update },
73
+ schemas: { input: setRolesForUserRequestSchema, output: userRolesResponseSchema },
74
+ handler: ({ input, deps }) => serviceOf(deps).setRolesForUser(input),
75
+ });
@@ -5,27 +5,17 @@
5
5
  *
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
- import type { RequestContext } from '@byline/auth';
8
+ import { type Command } from '../../lib/create-command.js';
9
9
  import type { AdminStore } from '../../store.js';
10
- import type { AdminUserListResponse, AdminUserResponse, OkResponse } from './schemas.js';
10
+ import type { AdminUserListResponse, AdminUserResponse, CreateAdminUserRequest, DeleteAdminUserRequest, DisableAdminUserRequest, EnableAdminUserRequest, GetAdminUserRequest, ListAdminUsersRequest, OkResponse, SetAdminUserPasswordRequest, UpdateAdminUserRequest } from './schemas.js';
11
11
  /**
12
12
  * Transport-agnostic commands for the admin-users module.
13
13
  *
14
- * Each command is a plain exported function not a class method — to
15
- * match Byline's existing `document-lifecycle` shape. Every command
16
- * follows the same four steps in the same order:
17
- *
18
- * 1. `schema.parse(input)` Zod-validate and normalise the raw input.
19
- * Throws `ZodError` on invalid shape; transport adapters translate
20
- * that into a 400-ish response.
21
- * 2. `assertAdminActor(context, ability)` — require an `AdminAuth`
22
- * actor holding the specific ability. Throws `ERR_UNAUTHENTICATED`
23
- * or `ERR_FORBIDDEN`.
24
- * 3. Call the `AdminUsersService` method with the validated input
25
- * (plus the actor where an invariant needs it).
26
- * 4. Parse the response through its output schema. In production the
27
- * check is redundant with the DTO's type; in tests it catches
28
- * drift between schema and DTO early.
14
+ * Each command is built through `createCommand`, which folds the four
15
+ * standard steps (Zod-validate input assert admin actor + ability →
16
+ * call the service Zod-validate output) into a single declaration.
17
+ * The wrapper preserves the historical `(context, input, deps)` call
18
+ * signature so server fns keep working without change.
29
19
  *
30
20
  * The `deps` argument holds the `AdminStore`. The webapp wraps these in
31
21
  * server fns that supply `deps` from the application's singleton store;
@@ -34,11 +24,11 @@ import type { AdminUserListResponse, AdminUserResponse, OkResponse } from './sch
34
24
  export interface AdminUsersCommandDeps {
35
25
  store: AdminStore;
36
26
  }
37
- export declare function listAdminUsersCommand(context: RequestContext | undefined, input: unknown, deps: AdminUsersCommandDeps): Promise<AdminUserListResponse>;
38
- export declare function getAdminUserCommand(context: RequestContext | undefined, input: unknown, deps: AdminUsersCommandDeps): Promise<AdminUserResponse>;
39
- export declare function createAdminUserCommand(context: RequestContext | undefined, input: unknown, deps: AdminUsersCommandDeps): Promise<AdminUserResponse>;
40
- export declare function updateAdminUserCommand(context: RequestContext | undefined, input: unknown, deps: AdminUsersCommandDeps): Promise<AdminUserResponse>;
41
- export declare function setAdminUserPasswordCommand(context: RequestContext | undefined, input: unknown, deps: AdminUsersCommandDeps): Promise<AdminUserResponse>;
42
- export declare function enableAdminUserCommand(context: RequestContext | undefined, input: unknown, deps: AdminUsersCommandDeps): Promise<OkResponse>;
43
- export declare function disableAdminUserCommand(context: RequestContext | undefined, input: unknown, deps: AdminUsersCommandDeps): Promise<OkResponse>;
44
- export declare function deleteAdminUserCommand(context: RequestContext | undefined, input: unknown, deps: AdminUsersCommandDeps): Promise<OkResponse>;
27
+ export declare const listAdminUsersCommand: Command<ListAdminUsersRequest, AdminUserListResponse, AdminUsersCommandDeps>;
28
+ export declare const getAdminUserCommand: Command<GetAdminUserRequest, AdminUserResponse, AdminUsersCommandDeps>;
29
+ export declare const createAdminUserCommand: Command<CreateAdminUserRequest, AdminUserResponse, AdminUsersCommandDeps>;
30
+ export declare const updateAdminUserCommand: Command<UpdateAdminUserRequest, AdminUserResponse, AdminUsersCommandDeps>;
31
+ export declare const setAdminUserPasswordCommand: Command<SetAdminUserPasswordRequest, AdminUserResponse, AdminUsersCommandDeps>;
32
+ export declare const enableAdminUserCommand: Command<EnableAdminUserRequest, OkResponse, AdminUsersCommandDeps>;
33
+ export declare const disableAdminUserCommand: Command<DisableAdminUserRequest, OkResponse, AdminUsersCommandDeps>;
34
+ export declare const deleteAdminUserCommand: Command<DeleteAdminUserRequest, OkResponse, AdminUsersCommandDeps>;
@@ -5,58 +5,67 @@
5
5
  *
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
- import { assertAdminActor } from '../../lib/assert-admin-actor.js';
8
+ import { createCommand } from '../../lib/create-command.js';
9
9
  import { ADMIN_USERS_ABILITIES } from './abilities.js';
10
10
  import { adminUserListResponseSchema, adminUserResponseSchema, createAdminUserRequestSchema, deleteAdminUserRequestSchema, disableAdminUserRequestSchema, enableAdminUserRequestSchema, getAdminUserRequestSchema, listAdminUsersRequestSchema, okResponseSchema, setAdminUserPasswordRequestSchema, updateAdminUserRequestSchema, } from './schemas.js';
11
11
  import { AdminUsersService } from './service.js';
12
12
  function serviceOf(deps) {
13
13
  return new AdminUsersService({ repo: deps.store.adminUsers });
14
14
  }
15
- export async function listAdminUsersCommand(context, input, deps) {
16
- const parsed = listAdminUsersRequestSchema.parse(input ?? {});
17
- assertAdminActor(context, ADMIN_USERS_ABILITIES.read);
18
- const result = await serviceOf(deps).listUsers(parsed);
19
- return adminUserListResponseSchema.parse(result);
20
- }
21
- export async function getAdminUserCommand(context, input, deps) {
22
- const parsed = getAdminUserRequestSchema.parse(input);
23
- assertAdminActor(context, ADMIN_USERS_ABILITIES.read);
24
- const result = await serviceOf(deps).getUser(parsed);
25
- return adminUserResponseSchema.parse(result);
26
- }
27
- export async function createAdminUserCommand(context, input, deps) {
28
- const parsed = createAdminUserRequestSchema.parse(input);
29
- assertAdminActor(context, ADMIN_USERS_ABILITIES.create);
30
- const result = await serviceOf(deps).createUser(parsed);
31
- return adminUserResponseSchema.parse(result);
32
- }
33
- export async function updateAdminUserCommand(context, input, deps) {
34
- const parsed = updateAdminUserRequestSchema.parse(input);
35
- assertAdminActor(context, ADMIN_USERS_ABILITIES.update);
36
- const result = await serviceOf(deps).updateUser(parsed);
37
- return adminUserResponseSchema.parse(result);
38
- }
39
- export async function setAdminUserPasswordCommand(context, input, deps) {
40
- const parsed = setAdminUserPasswordRequestSchema.parse(input);
41
- assertAdminActor(context, ADMIN_USERS_ABILITIES.changePassword);
42
- const result = await serviceOf(deps).setPassword(parsed);
43
- return adminUserResponseSchema.parse(result);
44
- }
45
- export async function enableAdminUserCommand(context, input, deps) {
46
- const parsed = enableAdminUserRequestSchema.parse(input);
47
- assertAdminActor(context, ADMIN_USERS_ABILITIES.update);
48
- await serviceOf(deps).enableUser(parsed);
49
- return okResponseSchema.parse({ ok: true });
50
- }
51
- export async function disableAdminUserCommand(context, input, deps) {
52
- const parsed = disableAdminUserRequestSchema.parse(input);
53
- const actor = assertAdminActor(context, ADMIN_USERS_ABILITIES.update);
54
- await serviceOf(deps).disableUser(actor, parsed);
55
- return okResponseSchema.parse({ ok: true });
56
- }
57
- export async function deleteAdminUserCommand(context, input, deps) {
58
- const parsed = deleteAdminUserRequestSchema.parse(input);
59
- const actor = assertAdminActor(context, ADMIN_USERS_ABILITIES.delete);
60
- await serviceOf(deps).deleteUser(actor, parsed);
61
- return okResponseSchema.parse({ ok: true });
62
- }
15
+ export const listAdminUsersCommand = createCommand({
16
+ method: 'listAdminUsers',
17
+ auth: { ability: ADMIN_USERS_ABILITIES.read },
18
+ schemas: { input: listAdminUsersRequestSchema, output: adminUserListResponseSchema },
19
+ handler: ({ input, deps }) => serviceOf(deps).listUsers(input),
20
+ });
21
+ export const getAdminUserCommand = createCommand({
22
+ method: 'getAdminUser',
23
+ auth: { ability: ADMIN_USERS_ABILITIES.read },
24
+ schemas: { input: getAdminUserRequestSchema, output: adminUserResponseSchema },
25
+ handler: ({ input, deps }) => serviceOf(deps).getUser(input),
26
+ });
27
+ export const createAdminUserCommand = createCommand({
28
+ method: 'createAdminUser',
29
+ auth: { ability: ADMIN_USERS_ABILITIES.create },
30
+ schemas: { input: createAdminUserRequestSchema, output: adminUserResponseSchema },
31
+ handler: ({ input, deps }) => serviceOf(deps).createUser(input),
32
+ });
33
+ export const updateAdminUserCommand = createCommand({
34
+ method: 'updateAdminUser',
35
+ auth: { ability: ADMIN_USERS_ABILITIES.update },
36
+ schemas: { input: updateAdminUserRequestSchema, output: adminUserResponseSchema },
37
+ handler: ({ input, deps }) => serviceOf(deps).updateUser(input),
38
+ });
39
+ export const setAdminUserPasswordCommand = createCommand({
40
+ method: 'setAdminUserPassword',
41
+ auth: { ability: ADMIN_USERS_ABILITIES.changePassword },
42
+ schemas: { input: setAdminUserPasswordRequestSchema, output: adminUserResponseSchema },
43
+ handler: ({ input, deps }) => serviceOf(deps).setPassword(input),
44
+ });
45
+ export const enableAdminUserCommand = createCommand({
46
+ method: 'enableAdminUser',
47
+ auth: { ability: ADMIN_USERS_ABILITIES.update },
48
+ schemas: { input: enableAdminUserRequestSchema, output: okResponseSchema },
49
+ handler: async ({ input, deps }) => {
50
+ await serviceOf(deps).enableUser(input);
51
+ return { ok: true };
52
+ },
53
+ });
54
+ export const disableAdminUserCommand = createCommand({
55
+ method: 'disableAdminUser',
56
+ auth: { ability: ADMIN_USERS_ABILITIES.update },
57
+ schemas: { input: disableAdminUserRequestSchema, output: okResponseSchema },
58
+ handler: async ({ input, deps, actor }) => {
59
+ await serviceOf(deps).disableUser(actor, input);
60
+ return { ok: true };
61
+ },
62
+ });
63
+ export const deleteAdminUserCommand = createCommand({
64
+ method: 'deleteAdminUser',
65
+ auth: { ability: ADMIN_USERS_ABILITIES.delete },
66
+ schemas: { input: deleteAdminUserRequestSchema, output: okResponseSchema },
67
+ handler: async ({ input, deps, actor }) => {
68
+ await serviceOf(deps).deleteUser(actor, input);
69
+ return { ok: true };
70
+ },
71
+ });
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@byline/admin",
3
3
  "private": false,
4
4
  "license": "MPL-2.0",
5
- "version": "1.9.1",
5
+ "version": "1.10.0",
6
6
  "engines": {
7
7
  "node": ">=20.9.0"
8
8
  },
@@ -68,8 +68,8 @@
68
68
  "jose": "^6.2.3",
69
69
  "uuid": "^14.0.0",
70
70
  "zod": "^4.4.2",
71
- "@byline/auth": "1.9.1",
72
- "@byline/core": "1.9.1"
71
+ "@byline/auth": "1.10.0",
72
+ "@byline/core": "1.10.0"
73
73
  },
74
74
  "devDependencies": {
75
75
  "@biomejs/biome": "2.4.14",