/**
* modules/docs.ts — Docs as a Papyrus-native registered module
* (step 5, continued, of the incremental refactor in
* reducing-papyrus-consumer-change-amplification-with-modules--pvdo).
*
* Imports only src/docs/docs-service.ts's Doc functions, which are already generic
* ArtifactStore-based with no other module's concrete class dependency.
*/
import { summarizeArtifact } from "../artifact/artifact.ts";
import type { ArtifactScopeStore } from "../artifact/artifact-scope-store.ts";
import type { ArtifactStore } from "../artifact/artifact-store.ts";
import type { AuthorityRegistry } from "../authority-registry.ts";
import {
addDocGroup,
addDocProject,
assignDocumentProject,
createDocument,
type DocumentRelation,
docScope,
linkDocument,
listDocuments,
removeDocGroup,
removeDocProject,
replaceDocGroups,
replaceDocProjects,
setDocGlobal,
setDocNone,
showDocument,
transitionDocument,
updateDocument,
} from "../docs/docs-service.ts";
import type { OperationDefinition } from "../module-registry.ts";
import type { ProjectRegistryStore } from "../project-registry/project-registry-store.ts";
import type { ScopeGroupStore } from "../scope-group/scope-group-store.ts";
import { type OperationInput, optionalBoolean, optionalNumber, optionalString, string } from "./operation-input.ts";
const MODULE_ID = "docs";
const eventContext = (input: OperationInput) => ({
actor: optionalString(input, "actor"),
source: optionalString(input, "source"),
sessionId: optionalString(input, "session_id") ?? optionalString(input, "sessionId"),
});
/**
* applicable=true switches project_root's meaning from exact-membership audit listing to
* applicable listing (global Docs plus Docs whose bounded membership includes this project) --
* see ListFilter's own doc comment on projectRoot vs applicableToProjectRoot for why these are
* two distinct, non-overlapping query modes rather than one.
*/
const artifactFilter = (input: OperationInput) => {
const projectRoot = optionalString(input, "project_root");
const applicable = optionalBoolean(input, "applicable") === true;
if (applicable && projectRoot === undefined) throw new Error("applicable requires project_root");
return {
status: optionalString(input, "status"),
text: optionalString(input, "text"),
limit: optionalNumber(input, "limit"),
...(applicable ? { applicableToProjectRoot: projectRoot } : { projectRoot }),
};
};
/** Registers every docs.* operation against the shared ArtifactStore port. Behavior is unchanged from the prior inline handlers in src/service.ts. */
/** This module's own operation names, the single source of truth src/service.ts's EXPECTED_OPERATION_NAMES spreads in rather than re-listing by hand. */
export const DOCS_OPERATION_NAMES = [
"docs.create",
"docs.list",
"docs.show",
"docs.activate",
"docs.archive",
"docs.reopen",
"docs.link",
"docs.assign_project",
"docs.scope",
"docs.set_global",
"docs.set_none",
"docs.add_project",
"docs.remove_project",
"docs.add_group",
"docs.remove_group",
"docs.replace_groups",
"docs.replace_projects",
"docs.update",
] as const;
export function docsOperations(
artifacts: ArtifactStore,
scopes: ArtifactScopeStore,
authority: AuthorityRegistry,
registry: ProjectRegistryStore,
scopeGroups: ScopeGroupStore,
): OperationDefinition[] {
const define = (name: string, execute: (input: Input) => Output): OperationDefinition => ({
name,
moduleId: MODULE_ID,
execute,
});
return [
define("docs.create", (input: OperationInput) =>
createDocument(
artifacts,
scopes,
{
title: string(input, "title"),
body: optionalString(input, "body"),
subtype: optionalString(input, "subtype"),
labels: input.labels as string[] | undefined,
extra: input.extra as Record | undefined,
templateId: optionalString(input, "template_id") ?? optionalString(input, "templateId"),
projectRoot: optionalString(input, "project_root"),
projectReferences: input.projects as string[] | undefined,
},
authority,
eventContext(input),
registry,
),
),
define("docs.list", (input: OperationInput) => {
const docs = listDocuments(artifacts, scopes, artifactFilter(input));
return optionalBoolean(input, "full") === true ? docs : docs.map(summarizeArtifact);
}),
define("docs.show", (input: OperationInput) => showDocument(artifacts, string(input, "id"))),
define("docs.activate", (input: OperationInput) =>
transitionDocument(artifacts, string(input, "id"), "activate", authority, eventContext(input)),
),
define("docs.archive", (input: OperationInput) =>
transitionDocument(artifacts, string(input, "id"), "archive", authority, eventContext(input)),
),
define("docs.reopen", (input: OperationInput) =>
transitionDocument(artifacts, string(input, "id"), "reopen", authority, eventContext(input)),
),
define("docs.link", (input: OperationInput) =>
linkDocument(
artifacts,
string(input, "id"),
string(input, "relation") as DocumentRelation,
string(input, "target_id"),
authority,
eventContext(input),
),
),
define("docs.assign_project", (input: OperationInput) =>
assignDocumentProject(artifacts, scopes, string(input, "id"), optionalString(input, "project_root")),
),
define("docs.scope", (input: OperationInput) => docScope(artifacts, scopes, string(input, "id"))),
define("docs.set_global", (input: OperationInput) => setDocGlobal(artifacts, scopes, string(input, "id"))),
define("docs.set_none", (input: OperationInput) => setDocNone(artifacts, scopes, string(input, "id"))),
define("docs.add_project", (input: OperationInput) =>
addDocProject(artifacts, scopes, registry, string(input, "id"), string(input, "project")),
),
define("docs.remove_project", (input: OperationInput) =>
removeDocProject(artifacts, scopes, registry, string(input, "id"), string(input, "project")),
),
define("docs.replace_projects", (input: OperationInput) =>
replaceDocProjects(artifacts, scopes, registry, string(input, "id"), (input.projects as string[] | undefined) ?? []),
),
define("docs.add_group", (input: OperationInput) =>
addDocGroup(artifacts, scopes, scopeGroups, string(input, "id"), string(input, "group")),
),
define("docs.remove_group", (input: OperationInput) =>
removeDocGroup(artifacts, scopes, scopeGroups, string(input, "id"), string(input, "group")),
),
define("docs.replace_groups", (input: OperationInput) =>
replaceDocGroups(artifacts, scopes, scopeGroups, string(input, "id"), (input.groups as string[] | undefined) ?? []),
),
define("docs.update", (input: OperationInput) =>
updateDocument(
artifacts,
string(input, "id"),
{
title: optionalString(input, "title"),
body: optionalString(input, "body"),
labels: input.labels as string[] | undefined,
},
authority,
eventContext(input),
),
),
];
}