@musnows/scriverse 0.5.6 → 0.5.7

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/app.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import express from "express";
2
+ import JSZip from "jszip";
2
3
  import multer from "multer";
3
4
  import mammoth from "mammoth";
4
5
  import { randomUUID } from "node:crypto";
@@ -6,6 +7,7 @@ import { dirname, extname, join } from "node:path";
6
7
  import { existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync } from "node:fs";
7
8
  import { rm } from "node:fs/promises";
8
9
  import { tmpdir } from "node:os";
10
+ import { pipeline } from "node:stream/promises";
9
11
  import { z, ZodError } from "zod";
10
12
  import { AttachmentStorage } from "./attachment-storage.js";
11
13
  import { AiManager } from "./ai.js";
@@ -1855,15 +1857,29 @@ export function createRuntime(options) {
1855
1857
  const query = parse(z.string().trim().min(1).max(500), request.query.q);
1856
1858
  data(response, store.search(request.params.workId, query));
1857
1859
  });
1858
- app.get("/api/works/:workId/export", (request, response) => {
1860
+ app.get("/api/works/:workId/export", async (request, response) => {
1859
1861
  const format = parse(z.enum(["json", "txt", "markdown"]), request.query.format ?? "json");
1860
1862
  if (format === "json") {
1861
1863
  response.setHeader("Content-Disposition", `attachment; filename=novel-${request.params.workId}.json`);
1862
1864
  data(response, store.exportWork(request.params.workId));
1863
1865
  return;
1864
1866
  }
1865
- response.type(format === "txt" ? "text/plain" : "text/markdown");
1866
- response.setHeader("Content-Disposition", `attachment; filename=novel-${request.params.workId}.${format === "markdown" ? "md" : "txt"}`);
1867
+ if (format === "markdown") {
1868
+ const exportName = `novel-${request.params.workId}`;
1869
+ const archive = new JSZip();
1870
+ archive.file(`${exportName}.md`, store.exportText(request.params.workId, format));
1871
+ response.type("application/zip");
1872
+ response.setHeader("Content-Disposition", `attachment; filename=${exportName}.zip`);
1873
+ await pipeline(archive.generateNodeStream({
1874
+ type: "nodebuffer",
1875
+ streamFiles: true,
1876
+ compression: "DEFLATE",
1877
+ compressionOptions: { level: 6 }
1878
+ }), response);
1879
+ return;
1880
+ }
1881
+ response.type("text/plain");
1882
+ response.setHeader("Content-Disposition", `attachment; filename=novel-${request.params.workId}.txt`);
1867
1883
  response.send(store.exportText(request.params.workId, format));
1868
1884
  });
1869
1885
  app.get("/api/works/:workId/audit-logs", (request, response) => {
@@ -1929,6 +1945,12 @@ export function createRuntime(options) {
1929
1945
  app.use((_request, _response, next) => next(new AppError(404, "ROUTE_NOT_FOUND", "请求的接口不存在")));
1930
1946
  app.use((error, request, response, _next) => {
1931
1947
  const commonFields = { method: request.method, path: sanitizeRequestPath(request.path), error: sanitizeError(error) };
1948
+ if (response.headersSent || response.destroyed) {
1949
+ logger.warn("http.request.response_stream_failed", commonFields);
1950
+ if (!response.destroyed)
1951
+ response.destroy(error instanceof Error ? error : undefined);
1952
+ return;
1953
+ }
1932
1954
  if (error instanceof ZodError) {
1933
1955
  logger.warn("http.request.validation_failed", { ...commonFields, issuePaths: error.issues.map((issue) => issue.path.join(".")) });
1934
1956
  response.status(400).json({