/**
* modules/scope-groups.ts — named, reusable, possibly-nested scope member collections as their
* own Papyrus-native registered module. A scope group's own members (added/removed here) are
* referenced by Docs/Rules/Playbooks' own explicit scope (docs/rules/playbooks.add_group etc.,
* see ../artifact/artifact-scope-store.ts) -- "explicit scope can include nested scopes".
* Mirrors modules/projects.ts's own shape (list/resolve/register) for the catalog half, plus
* real membership management and a real delete (deleteScopeGroup, unlike ProjectRegistryStore
* which has no delete path at all -- see papyrus task 1716b34b).
*/
import type { ArtifactScopeStore } from "../artifact/artifact-scope-store.ts";
import { SCOPE_GROUP_LIST_MAX_RESULTS } from "../constants.ts";
import type { OperationDefinition } from "../module-registry.ts";
import { resolveProjectReference } from "../project-registry/project-registry.ts";
import type { ProjectRegistryStore } from "../project-registry/project-registry-store.ts";
import { deleteScopeGroup } from "../scope-group/delete-scope-group.ts";
import { resolveScopeGroupReference, type ScopeMemberRef } from "../scope-group/scope-group.ts";
import type { ScopeGroupStore } from "../scope-group/scope-group-store.ts";
import { type OperationInput, optionalNumber, optionalString, optionalStringArray, string } from "./operation-input.ts";
const MODULE_ID = "scope_groups";
export const SCOPE_GROUPS_OPERATION_NAMES = [
"scope_groups.list",
"scope_groups.resolve",
"scope_groups.register",
"scope_groups.show",
"scope_groups.add_member",
"scope_groups.remove_member",
"scope_groups.delete",
] as const;
function resolveMember(
registry: ProjectRegistryStore,
scopeGroups: ScopeGroupStore,
memberType: string,
memberReference: string,
): ScopeMemberRef {
if (memberType === "project") return { type: "project", id: resolveProjectReference(registry, memberReference).id };
if (memberType === "group") return { type: "group", id: resolveScopeGroupReference(scopeGroups, memberReference).id };
throw new Error('member_type must be "project" or "group"');
}
export function scopeGroupsOperations(
scopeGroups: ScopeGroupStore,
registry: ProjectRegistryStore,
artifactScopes: ArtifactScopeStore,
): OperationDefinition[] {
const define = (name: string, execute: (input: Input) => Output): OperationDefinition => ({
name,
moduleId: MODULE_ID,
execute,
});
return [
define("scope_groups.list", (input: OperationInput) => {
const limit = optionalNumber(input, "limit") ?? 20;
if (!Number.isInteger(limit) || limit < 1 || limit > SCOPE_GROUP_LIST_MAX_RESULTS) {
throw new Error(`scope group list limit must be between 1 and ${SCOPE_GROUP_LIST_MAX_RESULTS}`);
}
return scopeGroups.groups(optionalString(input, "query"), limit);
}),
define("scope_groups.resolve", (input: OperationInput) => resolveScopeGroupReference(scopeGroups, string(input, "reference"))),
define("scope_groups.register", (input: OperationInput) =>
scopeGroups.registerGroup({
name: optionalString(input, "name"),
aliases: optionalStringArray(input, "aliases"),
existingId: optionalString(input, "existing_id"),
}),
),
define("scope_groups.show", (input: OperationInput) => {
const group = resolveScopeGroupReference(scopeGroups, string(input, "group"));
return { group, members: scopeGroups.members(group.id) };
}),
define("scope_groups.add_member", (input: OperationInput) => {
const group = resolveScopeGroupReference(scopeGroups, string(input, "group"));
const member = resolveMember(registry, scopeGroups, string(input, "member_type"), string(input, "member_reference"));
scopeGroups.addMember(group.id, member);
return { group, members: scopeGroups.members(group.id) };
}),
define("scope_groups.remove_member", (input: OperationInput) => {
const group = resolveScopeGroupReference(scopeGroups, string(input, "group"));
const member = resolveMember(registry, scopeGroups, string(input, "member_type"), string(input, "member_reference"));
scopeGroups.removeMember(group.id, member);
return { group, members: scopeGroups.members(group.id) };
}),
define("scope_groups.delete", (input: OperationInput) => {
const group = resolveScopeGroupReference(scopeGroups, string(input, "group"));
deleteScopeGroup(scopeGroups, artifactScopes, group.id);
return { deleted: true, id: group.id };
}),
];
}