mirror of https://github.com/requarks/wiki
parent
ff4a5bc6e6
commit
1c6e71eebf
@ -0,0 +1,15 @@
|
||||
CREATE TABLE "pageRenderQueue" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"allowScripts" boolean DEFAULT false NOT NULL,
|
||||
"allowStyles" boolean DEFAULT false NOT NULL,
|
||||
"createdAt" timestamp DEFAULT now() NOT NULL,
|
||||
"updatedAt" timestamp DEFAULT now() NOT NULL,
|
||||
"pageId" uuid NOT NULL UNIQUE,
|
||||
"siteId" uuid NOT NULL,
|
||||
"requestedById" uuid
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX "pageRenderQueue_createdAt_idx" ON "pageRenderQueue" ("createdAt");--> statement-breakpoint
|
||||
ALTER TABLE "pageRenderQueue" ADD CONSTRAINT "pageRenderQueue_pageId_pages_id_fkey" FOREIGN KEY ("pageId") REFERENCES "pages"("id") ON DELETE CASCADE;--> statement-breakpoint
|
||||
ALTER TABLE "pageRenderQueue" ADD CONSTRAINT "pageRenderQueue_siteId_sites_id_fkey" FOREIGN KEY ("siteId") REFERENCES "sites"("id");--> statement-breakpoint
|
||||
ALTER TABLE "pageRenderQueue" ADD CONSTRAINT "pageRenderQueue_requestedById_users_id_fkey" FOREIGN KEY ("requestedById") REFERENCES "users"("id") ON DELETE SET NULL;
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,53 @@
|
||||
import type { PoolClient } from 'pg'
|
||||
|
||||
/**
|
||||
* A `pg_notify` sender for one LISTEN/NOTIFY client.
|
||||
*
|
||||
* Sending is fire-and-forget by design — see `createNotifier` for why that has to be arranged rather
|
||||
* than simply left unawaited.
|
||||
*/
|
||||
export interface Notifier {
|
||||
/** Queue a notification behind whatever is already going out. Never throws. */
|
||||
send(channel: string, payload: string): void
|
||||
/** Resolves once everything queued so far has gone out, for an orderly shutdown. */
|
||||
drained(): Promise<void>
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the notifications sent on a dedicated LISTEN/NOTIFY client.
|
||||
*
|
||||
* Three modules hold such a client — the event bus (`core/db.ts`), the scheduler and collaborative
|
||||
* editing — and all three publish from places that cannot wait for a round trip to postgres: an
|
||||
* Emittery listener, a job being picked up, a Yjs handler reacting to a keystroke. So none of them
|
||||
* awaits the `pg_notify`.
|
||||
*
|
||||
* Handing an unawaited query to a client that is already running one is exactly what `pg` deprecated
|
||||
* in 8.x and removes in 9.0. It queues them internally today, which is why this went unnoticed: the
|
||||
* only symptom is a `DeprecationWarning`, and `util.deprecate` emits it once per process however often
|
||||
* it happens. Queueing them here instead costs nothing — the round trips were already serialized, only
|
||||
* silently and on the way out.
|
||||
*
|
||||
* Every notification carries its own `catch`, rather than one at the end of the chain: a failure to
|
||||
* publish belongs to the message that failed, and must not stop the ones behind it from going out.
|
||||
*
|
||||
* @param client Read on each send, since the client is opened after this is built and dropped at
|
||||
* shutdown. A notification sent while there is none is discarded.
|
||||
* @param label What these notifications are, for the log line when one cannot be sent
|
||||
*/
|
||||
export function createNotifier(client: () => PoolClient | null, label: string): Notifier {
|
||||
let tail: Promise<void> = Promise.resolve()
|
||||
return {
|
||||
send(channel: string, payload: string): void {
|
||||
tail = tail.then(async () => {
|
||||
try {
|
||||
await client()?.query('SELECT pg_notify($1, $2)', [channel, payload])
|
||||
} catch (err: any) {
|
||||
WIKI.logger.warn(`Failed to publish a ${label} notification: ${err.message}`)
|
||||
}
|
||||
})
|
||||
},
|
||||
drained(): Promise<void> {
|
||||
return tail
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Render every page waiting in the render queue.
|
||||
*
|
||||
* Queued whenever a page is added to `pageRenderQueue` — by an explicit re-render, or by an approved
|
||||
* suggestion that arrived without its HTML. The work is deliberately not split across jobs: rendering
|
||||
* means driving a headless browser, and the whole point of draining the queue in one task is that
|
||||
* there is one browser and it renders one page at a time. A run that finds the queue empty (a second
|
||||
* job for a batch this one already swept) returns without launching anything.
|
||||
*/
|
||||
export async function task(): Promise<void> {
|
||||
await WIKI.models.rendering.drainQueue()
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
/**
|
||||
* The message a failed API request should be reported with.
|
||||
*
|
||||
* The server's own, when it sent one — every `/_api` failure comes back as
|
||||
* `{ ok, error, statusCode, message }` — and ky's description of the request otherwise, which is all
|
||||
* there is for a failure that never reached a route.
|
||||
*
|
||||
* Read off `err.data`, never `err.response`: ky parses the body itself to fill `data` before it throws,
|
||||
* and that consumes it, so `err.response.json()` fails with "Body has already been read". Caught and
|
||||
* discarded — as it was everywhere this replaces — that failure is indistinguishable from a response
|
||||
* with no message, and the server's explanation gets quietly replaced by ky's generic "Request failed
|
||||
* with status code 503". Which is why a wrong password, a name already taken and a missing extension
|
||||
* all used to read the same.
|
||||
*
|
||||
* Synchronous, unlike the per-file helpers it replaces: with the body already parsed there is nothing
|
||||
* left to wait for, so callers read it straight out of the catch.
|
||||
*
|
||||
* @param {Error} err The thrown error — ky's `HTTPError`, or anything else that reached the catch
|
||||
* @param {string} [fallback] Shown when neither the server nor ky offered anything
|
||||
* @returns {string|undefined} What to put in front of the user
|
||||
*/
|
||||
export function apiErrorMessage(err, fallback) {
|
||||
return err?.data?.message || err?.message || fallback
|
||||
}
|
||||
Loading…
Reference in new issue