@molecule/api-resource-wiki-page
API resource · resource-wiki-page · API (Node) · v1.0.5 · Apache-2.0
Wiki page CRUD with breadcrumbs + search indexing
npm install @molecule/api-resource-wiki-pageHow it works
@molecule/api-resource-wiki-page is an API resource: the routes, validation and storage for resource-wiki-page, built on the database and auth cores so it runs on whichever providers your app has bonded.
import { createWikiPageRouter } from '@molecule/api-resource-wiki-page'
app.use('/pages', createWikiPageRouter())Works with: @molecule/api-bonds-default-express, @molecule/api-database, @molecule/api-i18n, @molecule/api-middleware-validation, @molecule/api-search, @molecule/api-slug
Reference
@molecule/api-resource-wiki-page — wiki/docs page CRUD with slug,
nested parents (breadcrumbs), space-scoped permissions, and optional
search-bond indexing.
Extracted from the wiki flagship.
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
Quick Start
import { createWikiPageRouter } from '@molecule/api-resource-wiki-page'
app.use('/pages', createWikiPageRouter())
import { createPage, getBreadcrumbs } from '@molecule/api-resource-wiki-page'
const page = await createPage({
space_id: spaceId,
slug: 'getting-started',
title: 'Getting started',
body: '# Welcome',
})
const crumbs = await getBreadcrumbs(page.id)
Type
resource
Installation
npm install @molecule/api-resource-wiki-page @molecule/api-bonds-default-express @molecule/api-database @molecule/api-i18n @molecule/api-middleware-validation @molecule/api-search @molecule/api-slug express zod
npm install -D @types/express
API
Interfaces
WikiPageRow
Database row shape for a wiki page record.
interface WikiPageRow {
id: string
space_id: string
parent_id: string | null
slug: string
title: string
body: string
position: number
is_published: boolean
created_at: string | Date
updated_at: string | Date
}
WikiSpaceRow
Database row shape for a wiki space record.
interface WikiSpaceRow {
id: string
owner_id: string
is_public?: boolean
visibility?: string
}
WikiPageBreadcrumb
Breadcrumb entry used when rendering a wiki page's ancestor trail.
interface WikiPageBreadcrumb {
id: string
slug: string
title: string
}
Functions
createWikiPageRouter()
Creates and returns the Express router with all wiki-page CRUD routes.
function createWikiPageRouter(): Router
getAccessibleSpace(spaceId, userId)
Resolve a space row + verify the user has access.
function getAccessibleSpace(spaceId: string, userId: string): Promise<WikiSpaceRow | null>
listPagesInSpace(spaceId, opts?)
List pages in a space, optionally filtered by parent and publish status.
function listPagesInSpace(
spaceId: string,
opts?: { parent_id?: string | null; is_published?: boolean },
): Promise<WikiPageRow[]>
getPageById(pageId)
Fetch a single wiki page by its primary-key ID.
function getPageById(pageId: string): Promise<WikiPageRow | null>
getPageBySlug(spaceId, slug)
Fetch a single wiki page by its space and URL slug.
function getPageBySlug(spaceId: string, slug: string): Promise<WikiPageRow | null>
getBreadcrumbs(pageId, maxDepth?)
Walk parent chain to build breadcrumbs (root → page).
function getBreadcrumbs(pageId: string, maxDepth?: number): Promise<WikiPageBreadcrumb[]>
createPage(data)
Create a new wiki page and trigger search indexing.
function createPage(data: {
space_id: string
parent_id?: string | null
slug: string
title: string
body?: string
position?: number
is_published?: boolean
}): Promise<WikiPageRow>
updatePage(pageId, patch)
Apply a partial patch to a wiki page and re-index it in search.
function updatePage(pageId: string, patch: Partial<WikiPageRow>): Promise<WikiPageRow | null>
deletePage(pageId)
Recursively delete a wiki page and all its children.
function deletePage(pageId: string): Promise<boolean>
countPagesInSpace(spaceId)
Count total pages belonging to a given space.
function countPagesInSpace(spaceId: string): Promise<number>
slugify(input)
Converts an arbitrary string into a URL-safe lowercase slug.
function slugify(input: string): string
Constants
slugRegex
Regex that matches a valid wiki-page slug: lowercase alphanumeric with interior hyphens.
const slugRegex: RegExp
wikiPageCreateSchema
Zod schema for validating wiki-page creation payloads.
const wikiPageCreateSchema: z.ZodObject<
{
space_id: z.ZodString
parent_id: z.ZodOptional<z.ZodNullable<z.ZodString>>
slug: z.ZodOptional<z.ZodString>
title: z.ZodString
body: z.ZodOptional<z.ZodString>
position: z.ZodOptional<z.ZodNumber>
is_published: z.ZodOptional<z.ZodBoolean>
},
z.core.$strip
>
wikiPageUpdateSchema
Zod schema for validating wiki-page update payloads.
const wikiPageUpdateSchema: z.ZodObject<
{
parent_id: z.ZodOptional<z.ZodNullable<z.ZodString>>
slug: z.ZodOptional<z.ZodString>
title: z.ZodOptional<z.ZodString>
body: z.ZodOptional<z.ZodString>
position: z.ZodOptional<z.ZodNumber>
is_published: z.ZodOptional<z.ZodBoolean>
},
z.core.$strip
>
wikiPageQuerySchema
Zod schema for validating wiki-page list/query parameters.
const wikiPageQuerySchema: z.ZodObject<
{
space_id: z.ZodOptional<z.ZodString>
parent_id: z.ZodOptional<z.ZodNullable<z.ZodString>>
is_published: z.ZodOptional<z.ZodCoercedBoolean<unknown>>
q: z.ZodOptional<z.ZodString>
},
z.core.$strip
>
Injection Notes
Requirements
Peer dependencies:
@molecule/api-bonds-default-express^1.0.1@molecule/api-database^1.0.1@molecule/api-i18n^1.0.1@molecule/api-middleware-validation^1.0.1@molecule/api-search^1.0.1@molecule/api-slug^1.0.0express^5.0.0zod^4.0.0
Runtime Dependencies
@molecule/api-bonds-default-express@molecule/api-database@molecule/api-i18n@molecule/api-middleware-validation@molecule/api-search@molecule/api-slugexpresszod
Session-auth prerequisite: every route reads the caller via
requireUser(res) (res.locals.session.userId, 401 fail-closed) — mount
createWikiPageRouter() behind your global auth middleware. There is no
routes/requestHandlerMap export; this package ships the Express router
factory shown above.
Access is space-scoped through getAccessibleSpace: a caller may READ a page
when they OWN its wiki_spaces row or the space is is_public. WRITES
(create/update/delete) are OWNER-ONLY — the write handlers additionally
require space.owner_id === userId, so a non-owner's write is refused (404)
even in a public space. If you need collaborative editing across a space,
wrap the router with your own role gate.
This package ships no space CRUD: create/seed the wiki_spaces row yourself
before creating pages. DELETE /:id is recursive (children first). If a
search provider is bonded via @molecule/api-search, creates and updates
index content automatically.
Tables: src/__setup__/wiki_pages.sql creates wiki_spaces and
wiki_pages. An mlcl-scaffolded API replays __setup__/*.sql on migrate (a CREATE TABLE IF NOT EXISTS, so a table the app's own migrations already create keeps only its own columns: ALTER in the ones it lacks); anywhere else run it once — nothing at runtime creates them.
E2E Tests
Integration checklist — drive the real UI (live preview, no mocks), adapt
each item to this app's actual wiki/docs screens/flows, and check every box
off one by one. A box you can't check is an integration bug to fix — not a
skip. Reality check on versioning: this resource stores ONE current body per
page (updatePage is an in-place updateById) — there is NO built-in
revision history, revert, or diff, and no per-page author column (only
created_at/updated_at). So verify edits persist, and confirm the app does
NOT present a "version history / restore / last edited by" UI it cannot back
(that needs the app's own versions table — this resource will not supply it):
- Creating a page persists title + slug + body and renders: submit them in the editor → it returns (201) and reopening the page shows the title and body. An omitted slug is derived from the title (slugify); the (space, slug) pair is unique, so a duplicate slug in the same space is rejected rather than silently overwriting the existing page.
- Editing the body persists in place and re-renders: change the body → save → reopen/reload shows the new text. Because nothing records the prior version, the old text is GONE — a "restore previous revision" control that does nothing (or 404s) is the bug, not an acceptable no-op.
- Hierarchy renders and stays consistent: a page created with a
parent_idnests under its parent in the tree, and its breadcrumbs (GET /:id/breadcrumbs) list the full root → page trail in order; deleting a parent recursively removes its children (they vanish), never orphaning them. - Draft vs published is honored: an unpublished page (is_published=false) is excluded when the list is filtered to published pages and appears once published — the reader-facing view never shows a draft it filtered out.
- Authorization — reads are space-scoped, writes are owner-only: a private space's pages are NOT readable by a non-owner (GET /:id → 404, existence not leaked); a public space's pages are readable by any signed-in user, yet a NON-owner's create/edit/delete is still refused (404) and nothing changes — a read-only viewer cannot mutate. Logged out, every route is refused (401).