wiki-formant 0.86.1 → 0.86.3

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/scripts.js CHANGED
@@ -1,138 +1,144 @@
1
- // scripts.ts — what a wiki's direct-database scripts share: the client, the
2
- // store over it, the block builders, the shape check and the revision write.
1
+ // scripts.ts — what a wiki's seed and sweep scripts share: a client on the
2
+ // app's database that rolls back on a throw and expires the wiki's cache tag
3
+ // after a write, the store over it, the block builders, and the two writes
4
+ // every script makes: a revision over a page it read, and an upsert.
3
5
  //
4
- // Node-only. A script bypasses the app's write routes, so it bypasses their
5
- // validation and their cache busting too; both are here so no script can skip
6
- // them. radix-wiki's seed-utils.mjs and caper's wiki-utils.ts had each written
7
- // this half, and each had a piece the other lacked: the shape check, the
8
- // write-tracking pool and the rollback on one side, the upsert that records a
9
- // revision only when something changed on the other.
10
- import { execFileSync } from 'node:child_process';
6
+ // radix-wiki's `seed-utils.mjs` and caper's `wiki-utils.ts` had each written
7
+ // these, in different languages, with different halves of the same guards.
8
+ // Node-only, like the bins: `pg` is the app's, imported when a client is made.
11
9
  import { randomUUID } from 'node:crypto';
12
- import { bump } from './versioning.js';
10
+ import { execFileSync } from 'node:child_process';
13
11
  import { wikiStore } from './store.js';
12
+ import { bump } from './versioning.js';
14
13
  import { validateLinkGroups, validateReferenceItems } from './validation.js';
15
- // ---- block builders ---------------------------------------------------------
16
14
  export const uid = () => randomUUID();
17
- export const content = (html) => ({ id: uid(), type: 'content', text: html });
18
- /**
19
- * A notice. Always pass `text`: the variant's standard message lives in the
20
- * renderer, not the row, so a banner without it is invisible to search.
21
- */
15
+ export const content = (text) => ({ id: uid(), type: 'content', text });
16
+ /** Always pass `text`: the renderer's default message lives in React, and search indexes only what the row holds. */
22
17
  export const banner = (variant, text) => ({ id: uid(), type: 'banner', variant, text });
23
- /** A facts panel leading an article: `table` is the inner `<table>` HTML. */
18
+ /** A facts panel that leads an article: `table` is the inner `<table>` HTML. */
24
19
  export const infobox = (table) => ({ id: uid(), type: 'infobox', blocks: [content(table)] });
25
- // ---- the shape check --------------------------------------------------------
26
20
  /**
27
- * Throw on a link-grid link or reference list whose URL fields are mis-shaped.
28
- *
29
- * The app's validator rejects these, and scripts never reach it. The views drop
30
- * an entry whose URL fails rather than rendering it inert, so a `{ label, url }`
31
- * link (the shape is `{ label, href }`) shows as a heading with no pills and
32
- * nothing reports it. A throw inside `withClient` is a rollback and a failed run.
21
+ * Refuse block content whose URL-bearing leaves are mis-shaped, before it
22
+ * reaches the database. `LinkGridView` and `ReferencesView` drop an entry whose
23
+ * URL fails `safeLinkHref` rather than rendering it inert, so a one-field slip
24
+ * (`url` for a link, whose shape is `{ label, href }`) shows as a heading with
25
+ * no pills and nothing reports it. Throws, so `withClient` rolls back.
33
26
  */
34
27
  export function assertLinkShapes(blocks, where = 'content') {
35
28
  const bad = [];
36
29
  const leaf = (b, path) => {
37
- if (!b || typeof b !== 'object')
30
+ const block = b;
31
+ if (!block || typeof block !== 'object')
38
32
  return;
39
- if (b.type === 'linkGrid')
40
- for (const [gi, g] of (b.groups ?? []).entries())
41
- for (const [li, l] of (g?.links ?? []).entries())
42
- if (!validateLinkGroups([{ id: '', heading: '', links: [l] }]))
33
+ if (block.type === 'linkGrid') {
34
+ for (const [gi, g] of (block.groups ?? []).entries()) {
35
+ for (const [li, l] of (g?.links ?? []).entries()) {
36
+ if (!validateLinkGroups([{ id: '', heading: '', links: [l] }])) {
43
37
  bad.push(`${path}.groups[${gi}].links[${li}] "${l?.label ?? '(no label)'}" has fields {${Object.keys(l ?? {}).sort().join(', ')}} -- LinkGridLink is { label, href }`);
44
- if (b.type === 'references' && !validateReferenceItems(b.items))
38
+ }
39
+ }
40
+ }
41
+ }
42
+ if (block.type === 'references' && !validateReferenceItems(block.items))
45
43
  bad.push(`${path}.items -- ReferenceItem is { id, text, url? }`);
46
44
  };
47
- (Array.isArray(blocks) ? blocks : []).forEach((b, i) => {
45
+ blocks.forEach((b, i) => {
48
46
  leaf(b, `[${i}]`);
49
- if (b?.type === 'infobox')
50
- (b.blocks ?? []).forEach((n, j) => leaf(n, `[${i}].blocks[${j}]`));
51
- if (b?.type === 'columns')
52
- (b.columns ?? []).forEach((c, ci) => (c?.blocks ?? []).forEach((n, j) => leaf(n, `[${i}].columns[${ci}].blocks[${j}]`)));
47
+ const block = b;
48
+ if (block?.type === 'infobox')
49
+ (block.blocks ?? []).forEach((n, j) => leaf(n, `[${i}].blocks[${j}]`));
50
+ if (block?.type === 'columns') {
51
+ (block.columns ?? []).forEach((c, ci) => (c?.blocks ?? []).forEach((n, j) => leaf(n, `[${i}].columns[${ci}].blocks[${j}]`)));
52
+ }
53
53
  });
54
54
  if (bad.length)
55
55
  throw new Error(`${where}: ${bad.length} mis-shaped link(s)\n ${bad.join('\n ')}`);
56
56
  }
57
- // ---- the client and the cache -----------------------------------------------
58
- /**
59
- * Mark everything cached under `tag` stale on the project `cwd` is linked to.
60
- * A script's write reaches no route that would bust it, and pages hold a day.
61
- */
62
- export function invalidate(tag, cwd = process.cwd()) {
57
+ /** `vercel cache invalidate --tag`, reported either way. */
58
+ export function invalidate(tag, cwd) {
63
59
  try {
64
- execFileSync('vercel', ['cache', 'invalidate', '--tag', tag, '--yes'], { cwd, stdio: 'ignore' });
60
+ execFileSync('vercel', ['cache', 'invalidate', '--tag', tag, '--yes'], { stdio: 'ignore', ...(cwd ? { cwd } : {}) });
65
61
  console.log(`cache: ${tag} tag invalidated`);
66
62
  }
67
63
  catch (e) {
68
- console.warn(`cache: ${tag} tag NOT invalidated (${e.message}); run \`vercel cache invalidate --tag ${tag}\` from ${cwd}`);
64
+ console.warn(`cache: ${tag} tag NOT invalidated (${e.message}); run \`vercel cache invalidate --tag ${tag}\``);
69
65
  }
70
66
  }
71
67
  /**
72
- * Run `fn` on a connected client, then release it and its pool. A throw rolls
73
- * back the open transaction and fails the process: every script writes to the
74
- * live database, and a half-applied edit must never pass for a success. A run
75
- * that wrote anything ends by invalidating `tag`.
68
+ * Run `fn` against a connected client, then release the client and the pool.
69
+ * A throw rolls back whatever transaction was open and fails the process:
70
+ * every one of these scripts writes to the live database, so a half-applied
71
+ * edit must never be left behind and never look like a success. A run that
72
+ * wrote anything ends by expiring the wiki's cache tag, because a script
73
+ * bypasses the routes that purge the pages they change.
76
74
  */
77
- export async function withClient(fn, { tag = 'wiki', cwd = process.cwd() } = {}) {
78
- const { default: pg } = await import('pg');
79
- const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL, max: 1, ssl: { rejectUnauthorized: false } });
80
- const conn = await pool.connect();
75
+ export async function withClient(fn, { tag = 'wiki', cwd, url } = {}) {
76
+ if (!url && !process.env.DATABASE_URL) {
77
+ try {
78
+ process.loadEnvFile('.env');
79
+ }
80
+ catch {
81
+ // reported below
82
+ }
83
+ }
84
+ const connectionString = url ?? process.env.DATABASE_URL;
85
+ if (!connectionString)
86
+ throw new Error('no DATABASE_URL in the environment or ./.env');
87
+ const { default: pg } = (await import('pg'));
88
+ const pool = new pg.Pool({ connectionString, max: 1, ssl: { rejectUnauthorized: false } });
89
+ const client = await pool.connect();
81
90
  let wrote = false;
82
- const client = {
83
- query: (text, values) => {
84
- // Anywhere in the text: the store's writes are data-modifying CTEs.
85
- if (/\b(insert\s+into|update\s+\S+\s+set|delete\s+from)\b/i.test(text))
86
- wrote = true;
87
- return conn.query(text, values);
88
- },
91
+ const query = client.query.bind(client);
92
+ client.query = (text, values) => {
93
+ if (/\b(insert\s+into|update\s+\S+\s+set|delete\s+from)\b/i.test(text))
94
+ wrote = true;
95
+ return query(text, values);
89
96
  };
90
97
  try {
91
98
  return await fn(client);
92
99
  }
93
100
  catch (e) {
94
- await conn.query('ROLLBACK').catch(() => { });
101
+ await client.query('ROLLBACK').catch(() => { });
95
102
  console.error('ERROR:', e.message);
96
103
  process.exitCode = 1;
97
104
  return undefined;
98
105
  }
99
106
  finally {
100
- conn.release();
107
+ client.release();
101
108
  await pool.end();
102
- // A run that throws part-way may have committed earlier writes.
103
109
  if (wrote)
104
110
  invalidate(tag, cwd);
105
111
  }
106
112
  }
107
- // ---- reads and writes through the app's store ---------------------------------
108
- /** The app's own store over a script's client, so a script writes what the editor writes. */
113
+ /** The app's own store over a script's client, so a script writes exactly what the editor writes. */
109
114
  export const storeFor = (client, siteId = '') => wikiStore({ sql: async (q, ...v) => (await client.query(q, v)).rows, siteId });
110
115
  /** A page row by path, every column: what `writeRevision` takes. Null when there is none. */
111
- export const readPage = (client, tagPath, slug, siteId = '') => storeFor(client, siteId).get(tagPath, slug);
116
+ export const readPage = (client, tagPath, slug, siteId) => storeFor(client, siteId).get(tagPath, slug);
112
117
  /**
113
- * Write edited blocks back to a page row from `readPage` as one revision. The
114
- * save names the version it read, so an edit landing in between is refused
115
- * (409) rather than overwritten. Returns the new version.
118
+ * Write edited blocks back to a page from `readPage` as one revision, through
119
+ * the store: the page and its revision in one statement, the version bumped
120
+ * from the stored one by `change`. The save names the version it read, so an
121
+ * edit landing in between is refused (409) instead of overwritten. Returns the
122
+ * new version.
116
123
  */
117
- export async function writeRevision(client, page, blocks, { change = 'minor', message, verified = false, dry = false, metadata, authorId = null } = {}) {
124
+ export async function writeRevision(client, page, blocks, { change = 'minor', message, verified = false, dry = false, metadata, authorId = null, siteId } = {}) {
118
125
  if (dry)
119
126
  return bump(page.version, change);
120
127
  assertLinkShapes(blocks, page.title);
121
- const { page: saved } = await storeFor(client, page.siteId).save({
128
+ const { page: saved } = await storeFor(client, siteId).save({
122
129
  tagPath: page.tagPath, slug: page.slug, content: blocks, metadata,
123
130
  change, message, verified, authorId, baseVersion: page.version,
124
131
  });
125
132
  return saved.version;
126
133
  }
127
134
  /**
128
- * Create a page or update it in place, keyed by site, tag path and slug. A
129
- * change to its title or blocks writes a revision; a re-run that changes
130
- * nothing writes none. `change` records that bump instead of the diff's, and
131
- * always writes one.
135
+ * Create a page, or replace its title and blocks: a change writes a revision
136
+ * bumped from the page's own version, a re-run that changes nothing writes
137
+ * none, and a stated `change` always records one.
132
138
  */
133
139
  export async function upsertWikiPage(client, input) {
134
- const { siteId = '', ...save } = input;
135
- assertLinkShapes(save.content, `${save.tagPath}/${save.slug}`);
140
+ assertLinkShapes(input.content, `${input.tagPath}/${input.slug}`);
141
+ const { siteId, ...save } = input;
136
142
  const { created } = await storeFor(client, siteId).save(save);
137
143
  return created ? 'created' : 'updated';
138
144
  }
@@ -1,4 +1,3 @@
1
- import { type BlockTreeShape } from './blocks.js';
2
1
  /**
3
2
  * An href acceptable to persist: http, https, mailto, tel, or a relative path or
4
3
  * fragment. Everything else — `javascript:`, `data:`, `vbscript:`, a
@@ -87,45 +86,39 @@ export declare function duplicateBlockIds<B extends {
87
86
  type: string;
88
87
  id: string;
89
88
  }>(block: B, newId?: () => string): B;
90
- type Checked = Record<string, unknown>;
89
+ /** A leaf check over the stored record; `core` is the package's check for the core types. */
90
+ export type LeafCheck = (block: Record<string, unknown>, core: (block: Record<string, unknown>) => boolean) => boolean;
91
91
  export interface BlockModelOptions<B extends {
92
92
  id: string;
93
93
  type: string;
94
94
  }> {
95
- /** What inserting each type makes. Its keys are the types this wiki knows. */
95
+ /** A new block of every type, without its id: `{ ...coreBlockDefaults(), daoTimeline: () => … }`. */
96
96
  defaults: Record<B['type'], () => Omit<B, 'id'>>;
97
- /**
98
- * The leaf check for each native type, and for a core type the wiki narrows.
99
- * `core` is the core check, for a narrowing that adds to it rather than
100
- * replacing it. A type with no entry gets the core check.
101
- */
102
- checks?: Partial<Record<B['type'], (block: Checked, core: (block: Checked) => boolean) => boolean>>;
103
- /** As `coreAtomicValidator`'s. */
97
+ /** The wiki's own leaf checks, and any core check it narrows (`list: (b, core) => core(b) && isSection(b.under)`). */
98
+ checks?: Partial<Record<B['type'], LeafCheck>>;
99
+ /** What `coreAtomicValidator` takes. */
104
100
  pageId?: (id: string) => boolean;
105
101
  maxRows?: number;
106
102
  }
107
- export interface BlockModel<B extends {
108
- id: string;
109
- type: string;
110
- }> {
111
- /** Every type, in the order `defaults` lists them. */
112
- types: B['type'][];
113
- /** Every type but the two containers: what may nest inside one. */
114
- atomicTypes: B['type'][];
115
- shape: BlockTreeShape<B>;
116
- createBlock: (type: B['type']) => B;
117
- duplicateBlock: (block: B) => B;
118
- validateBlocks: (content: unknown) => content is B[];
119
- blockIssues: (content: unknown) => BlockIssue[];
120
- }
121
103
  /**
122
- * A wiki's block model from its type set: the new-block factories and the leaf
123
- * checks are its own, and everything derived from them is this. Each wiki had
124
- * derived the same seven things by hand, and two kept a list of nestable types
125
- * that drifted from the types the write path accepted.
104
+ * Everything a wiki derives from its block set: the shape every tree walk
105
+ * takes, the types, a new block, a copy with fresh ids, and the validator
106
+ * with its issues. Three wikis had assembled these from the same seven
107
+ * primitives, each a hundred lines; a fourth kind of block now costs a default
108
+ * and a check.
126
109
  */
127
110
  export declare function defineBlocks<B extends {
128
111
  id: string;
129
112
  type: string;
130
- }>(opts: BlockModelOptions<B>): BlockModel<B>;
131
- export {};
113
+ }>(opts: BlockModelOptions<B>): {
114
+ shape: import("./blocks.js").BlockTreeShape<B>;
115
+ types: B["type"][];
116
+ atomicTypes: B["type"][];
117
+ isAtomic: (type: string) => boolean;
118
+ createBlock: (type: B["type"]) => B;
119
+ duplicateBlock: (block: B) => B;
120
+ validateBlocks: (content: unknown) => content is B[];
121
+ validateBlock: (block: unknown) => boolean;
122
+ validateAtomicBlock: (block: unknown) => boolean;
123
+ blockIssues: (content: unknown) => BlockIssue[];
124
+ };
@@ -242,29 +242,35 @@ export function duplicateBlockIds(block, newId = () => crypto.randomUUID()) {
242
242
  return { ...block, id: newId() };
243
243
  }
244
244
  /**
245
- * A wiki's block model from its type set: the new-block factories and the leaf
246
- * checks are its own, and everything derived from them is this. Each wiki had
247
- * derived the same seven things by hand, and two kept a list of nestable types
248
- * that drifted from the types the write path accepted.
245
+ * Everything a wiki derives from its block set: the shape every tree walk
246
+ * takes, the types, a new block, a copy with fresh ids, and the validator
247
+ * with its issues. Three wikis had assembled these from the same seven
248
+ * primitives, each a hundred lines; a fourth kind of block now costs a default
249
+ * and a check.
249
250
  */
250
251
  export function defineBlocks(opts) {
251
252
  const { defaults, checks = {}, pageId, maxRows } = opts;
252
253
  const types = Object.keys(defaults);
253
- const atomicTypes = types.filter(t => t !== 'columns' && t !== 'infobox');
254
- const core = coreAtomicValidator({ pageId, maxRows });
255
- const own = checks;
256
- const { validateBlocks, blockIssues } = createBlockValidator({
257
- isKnownType: t => Object.hasOwn(defaults, t),
258
- isAtomicType: t => atomicTypes.includes(t),
259
- validateAtomic: b => (own[b.type] ?? core)(b, core),
254
+ const core = coreAtomicValidator({ ...(pageId ? { pageId } : {}), ...(maxRows ? { maxRows } : {}) });
255
+ const isAtomic = (type) => type !== 'columns' && type !== 'infobox';
256
+ const validator = createBlockValidator({
257
+ isKnownType: t => t in defaults,
258
+ isAtomicType: isAtomic,
259
+ validateAtomic: b => {
260
+ const own = checks[b.type];
261
+ return own ? own(b, core) : core(b);
262
+ },
260
263
  });
261
264
  return {
262
- types,
263
- atomicTypes,
264
265
  shape: coreBlockShape(),
265
- createBlock: type => ({ id: crypto.randomUUID(), ...defaults[type]() }),
266
- duplicateBlock: block => duplicateBlockIds(block),
267
- validateBlocks: (content) => validateBlocks(content),
268
- blockIssues,
266
+ types,
267
+ atomicTypes: types.filter(isAtomic),
268
+ isAtomic,
269
+ createBlock: (type) => ({ id: crypto.randomUUID(), ...defaults[type]() }),
270
+ duplicateBlock: (block) => duplicateBlockIds(block),
271
+ validateBlocks: (content) => validator.validateBlocks(content),
272
+ validateBlock: validator.validateBlock,
273
+ validateAtomicBlock: validator.validateAtomicBlock,
274
+ blockIssues: validator.blockIssues,
269
275
  };
270
276
  }
@@ -150,29 +150,32 @@ export declare function descriptorHandler(body: unknown, opts?: {
150
150
  /** The whole `<mcp-url>/server-card` route, projected from server.json. */
151
151
  export declare const serverCardHandler: (manifest: ServerManifest) => ((request: Request) => Response);
152
152
  export interface WellKnownDocs {
153
- agentCard: unknown;
154
- mcpManifest: unknown;
155
- openapi: unknown;
156
- /**
157
- * The registry's key material, read from the environment by default. `null`
158
- * serves no proof: a tenant of a host whose namespace is the host's own.
159
- */
153
+ /** Served at both `agent.json` and `agent-card.json`. */
154
+ agentCard?: unknown;
155
+ /** `mcp.json`. */
156
+ mcpManifest?: unknown;
157
+ /** `openapi.json`. */
158
+ openapi?: unknown;
159
+ /** The registry proof, `mcp-registry-auth`: a key, or a handler; defaults to the environment's key. Pass null to serve none. */
160
160
  registryAuth?: {
161
161
  publicKey?: string;
162
162
  keyType?: string;
163
- } | null;
163
+ } | (() => Response) | null;
164
164
  }
165
165
  /**
166
- * The `/.well-known/[doc]` route: the agent card at `agent.json` and
167
- * `agent-card.json` (A2A v0.3 renamed it; older clients ask for the first), the
168
- * MCP manifest at `mcp.json`, the OpenAPI spec at `openapi.json` and the
169
- * registry's domain proof at `mcp-registry-auth`. Every other name is a 404.
166
+ * The whole `/.well-known/` directory as one dynamic route,
167
+ * `src/app/.well-known/[doc]/route.ts`:
170
168
  *
171
- * Each app served these as five or six one-line routes re-exporting each other;
172
- * one dynamic segment holds them all, and a rewrite puts `/openapi.json` on it.
169
+ * export const GET = wellKnownHandler({ agentCard: CARD, mcpManifest: MANIFEST, openapi: SPEC });
170
+ *
171
+ * Four repos carried five one-line route files each, twenty in all, and one
172
+ * of them answered a path the others 404'd. A document the app does not pass
173
+ * is a 404 here too, so an origin without an MCP server still serves its card.
173
174
  */
174
175
  export declare function wellKnownHandler(docs: WellKnownDocs): (request: Request, context: {
175
176
  params: Promise<{
176
177
  doc: string;
177
- }>;
178
+ }> | {
179
+ doc: string;
180
+ };
178
181
  }) => Promise<Response>;
@@ -1,6 +1,5 @@
1
1
  // well-known.ts — the descriptors an agent finds before it knows anything else
2
- // about an origin: the A2A Agent Card, the MCP manifest and the registry's
3
- // domain proof, and the one route that serves them.
2
+ // about an origin: the A2A Agent Card and the MCP registry's domain proof.
4
3
  //
5
4
  // All three apps here serve both, and all three had written them out. The
6
5
  // registry-auth handler was identical bar a domain named in a comment; the
@@ -220,27 +219,30 @@ export function descriptorHandler(body, opts = {}) {
220
219
  /** The whole `<mcp-url>/server-card` route, projected from server.json. */
221
220
  export const serverCardHandler = (manifest) => descriptorHandler(serverCard(manifest, MCP_PROTOCOL_VERSIONS));
222
221
  /**
223
- * The `/.well-known/[doc]` route: the agent card at `agent.json` and
224
- * `agent-card.json` (A2A v0.3 renamed it; older clients ask for the first), the
225
- * MCP manifest at `mcp.json`, the OpenAPI spec at `openapi.json` and the
226
- * registry's domain proof at `mcp-registry-auth`. Every other name is a 404.
222
+ * The whole `/.well-known/` directory as one dynamic route,
223
+ * `src/app/.well-known/[doc]/route.ts`:
227
224
  *
228
- * Each app served these as five or six one-line routes re-exporting each other;
229
- * one dynamic segment holds them all, and a rewrite puts `/openapi.json` on it.
225
+ * export const GET = wellKnownHandler({ agentCard: CARD, mcpManifest: MANIFEST, openapi: SPEC });
226
+ *
227
+ * Four repos carried five one-line route files each, twenty in all, and one
228
+ * of them answered a path the others 404'd. A document the app does not pass
229
+ * is a 404 here too, so an origin without an MCP server still serves its card.
230
230
  */
231
231
  export function wellKnownHandler(docs) {
232
- const { registryAuth = {} } = docs;
233
- const bodies = {
234
- 'agent.json': docs.agentCard,
235
- 'agent-card.json': docs.agentCard,
236
- 'mcp.json': docs.mcpManifest,
237
- 'openapi.json': docs.openapi,
232
+ const auth = docs.registryAuth;
233
+ const registry = auth === null ? null
234
+ : typeof auth === 'function' ? auth
235
+ : registryAuthHandler(auth?.publicKey ?? process.env.MCP_REGISTRY_PUBLIC_KEY, auth?.keyType ?? process.env.MCP_REGISTRY_KEY_TYPE);
236
+ const served = {
237
+ 'agent.json': docs.agentCard === undefined ? undefined : descriptorHandler(docs.agentCard),
238
+ 'agent-card.json': docs.agentCard === undefined ? undefined : descriptorHandler(docs.agentCard),
239
+ 'mcp.json': docs.mcpManifest === undefined ? undefined : descriptorHandler(docs.mcpManifest),
240
+ 'openapi.json': docs.openapi === undefined ? undefined : descriptorHandler(docs.openapi),
241
+ 'mcp-registry-auth': registry ?? undefined,
238
242
  };
239
243
  return async (request, { params }) => {
240
244
  const { doc } = await params;
241
- if (doc === 'mcp-registry-auth' && registryAuth) {
242
- return registryAuthHandler(registryAuth.publicKey ?? process.env.MCP_REGISTRY_PUBLIC_KEY, registryAuth.keyType ?? process.env.MCP_REGISTRY_KEY_TYPE)();
243
- }
244
- return Object.hasOwn(bodies, doc) ? descriptorResponse(request, bodies[doc]) : new Response('Not found', { status: 404 });
245
+ const handler = Object.hasOwn(served, doc) ? served[doc] : undefined;
246
+ return handler ? handler(request) : new Response('Not found', { status: 404 });
245
247
  };
246
248
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wiki-formant",
3
- "version": "0.86.1",
3
+ "version": "0.86.3",
4
4
  "description": "The portable half of a wiki: derived taxonomy and facet controls, a version-negotiating MCP transport, markdown twins, block rendering, a rich-text editor engine, conditional-GET plumbing, and the headers and CSP every origin sends.",
5
5
  "type": "module",
6
6
  "license": "MIT",