Files
MorphDoc/packages/application/src/application-service.ts
T

125 lines
3.0 KiB
TypeScript

import {
MarkdownDocumentParseError,
renderMarkdown
} from "@md-to-pdf/renderer";
import {
createThemeRegistry,
type ThemeRegistryOptions
} from "./theme-registry.js";
import {
createImageResourceResolver,
type ImageResolutionContext,
type ImageResourceResolverOptions
} from "./image-resources.js";
export const MAXIMUM_MARKDOWN_LENGTH = 1_500_000;
export interface MarkdownRenderRequest {
markdown?: unknown;
language?: unknown;
resources?: unknown;
}
export class ApplicationRequestError extends Error {
constructor(
readonly statusCode: 400 | 404 | 413,
readonly code: string,
message: string
) {
super(message);
this.name = "ApplicationRequestError";
}
}
export interface ApplicationServiceOptions
extends ThemeRegistryOptions,
ImageResourceResolverOptions {}
export function createApplicationService(
options: ApplicationServiceOptions
) {
const themes = createThemeRegistry(options);
const resolveImages = createImageResourceResolver(options);
async function render(
request: MarkdownRenderRequest,
context: ImageResolutionContext = {}
) {
const { markdown, language, resources } = request;
if (typeof markdown !== "string") {
throw new ApplicationRequestError(
400,
"INVALID_MARKDOWN",
"markdown 必须是字符串"
);
}
if (markdown.length > MAXIMUM_MARKDOWN_LENGTH) {
throw new ApplicationRequestError(
413,
"MARKDOWN_TOO_LARGE",
"Markdown 内容不能超过 1.5 MB"
);
}
if (language !== undefined && typeof language !== "string") {
throw new ApplicationRequestError(
400,
"INVALID_LANGUAGE",
"language 必须是字符串"
);
}
try {
const images = await resolveImages(markdown, resources, context);
return renderMarkdown(markdown, {
...(language ? { language } : {}),
imageSourceMap: images.sources,
warnings: images.warnings
});
} catch (error) {
if (error instanceof MarkdownDocumentParseError) {
throw new ApplicationRequestError(
400,
error.code,
error.message
);
}
if (error instanceof Error) {
const tooLarge = /超过|大小/u.test(error.message);
throw new ApplicationRequestError(
tooLarge ? 413 : 400,
tooLarge
? "IMAGE_RESOURCES_TOO_LARGE"
: "INVALID_IMAGE_RESOURCES",
error.message
);
}
throw error;
}
}
async function listThemes() {
return {
themes: (await themes.list()).map(({ manifest, source }) => ({
id: manifest.id,
name: manifest.name,
version: manifest.version,
description: manifest.description,
bundled: manifest.bundled,
source
}))
};
}
return {
getThemeAsset: themes.getAsset,
getThemeCss: themes.getCss,
invalidateThemes: themes.invalidate,
listThemes,
render
};
}
export type ApplicationService = ReturnType<
typeof createApplicationService
>;