toeverything/AFFiNE · error · ExpectToPublishDoc
expect_to_publish_doc
expect_to_publish_doc
Error message
Expected to publish a doc, not a Space.
What it means
publishDoc rejects any call where docId === workspaceId. In AFFiNE the workspace root id equals the workspace id and the root is a Space container, not a publishable page, so publishing 'the workspace id itself' is always a client mistake and is refused with ExpectToPublishDoc before any permission check.
Solutions
- Pass the actual page id: assert docId !== workspaceId in the client before calling publishDoc
- If the target really is the workspace root, pick a child page — the root itself cannot be published
- Check where the client sources docId from; it must come from a page/doc object, not the workspace
Example fix
// before
await publishDoc(ws.id, ws.id, mode); // root is a Space -> EXPECT_TO_PUBLISH_DOC
// after
const page = pickPageToPublish(ws); // a real page id, never the workspace id
if (page.id === ws.id) throw new Error('cannot publish the workspace root');
await publishDoc(ws.id, page.id, mode); Defensive patterns
Strategy: validation
Validate before calling
// Never publish the workspace root
function assertPublishablePageId(workspaceId: string, docId: string) {
if (!docId || docId === workspaceId) {
throw new Error('docId must be a page id, not the workspace id');
}
}
assertPublishablePageId(ws.id, docId);
await publishDoc(ws.id, docId, mode); Type guard
function isExpectToPublishDoc(e: unknown): boolean {
return (
typeof e === 'object' && e !== null &&
(e as { extensions?: { code?: string } }).extensions?.code === 'expect_to_publish_doc'
);
} Try / catch
try {
await publishDoc(ws.id, docId, mode);
} catch (e) {
if (isExpectToPublishDoc(e)) {
promptUserToSelectAnActualPage(); // root can never be published
} else throw e;
} Prevention
- Source docId only from page/doc objects, never from workspace objects
- Disable the publish action on the workspace root node in the UI
- Add an id inequality assert in shared client helpers around all doc mutations
When it happens
Trigger: Calling publishDoc(workspaceId, docId) with docId set to the workspace id — typically a client passing the root page id, defaulting docId to the workspace id, or reading the id from the wrong field of a workspace object.
Common situations: Porting older client code where the root page was addressed by the workspace id; constructing the mutation arguments from a workspace object instead of a page object.
Related errors
- expect_to_revoke_public_doc
- doc_default_role_can_not_be_owner
- expect_to_grant_doc_user_roles
- expect_to_revoke_doc_user_roles
- expect_to_update_doc_user_role
AI-assisted analysis of toeverything/AFFiNE@b6de0ad51b (2026-08-18).
Data as JSON: /api/errors/9f754ec2408cdc51.
Report an issue: GitHub.
Appendix: source
Thrown at packages/backend/server/src/core/workspaces/resolvers/doc.ts:431
@Mutation(() => DocType)
async publishDoc(
@CurrentUser() user: CurrentUser,
@Args('workspaceId') workspaceId: string,
@Args('docId') docId: string,
@Args({
name: 'mode',
type: () => PublicDocMode,
nullable: true,
defaultValue: PublicDocMode.Page,
})
mode: PublicDocMode
) {
if (workspaceId === docId) {
this.logger.error('Expect to publish doc, but it is a workspace', {
workspaceId,
docId,
});
throw new ExpectToPublishDoc();
}
await this.ac.user(user.id).doc(workspaceId, docId).assert('Doc.Publish');
await this.inviteQuota.assertWorkspaceActionAllowed({
actorUserId: user.id,
workspaceId,
docId,
action: 'publishDoc',
});
const doc = await this.models.doc.publish(workspaceId, docId, mode);
this.event.emit('doc.public_state.changed', { workspaceId, docId });
this.logger.log(
`Publish page ${docId} with mode ${mode} in workspace ${workspaceId}`
);
return doc;View on GitHub (pinned to b6de0ad51b)