@tstdl/base 0.93.128 → 0.93.129

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.
@@ -156,9 +156,9 @@ describe('ApiClient', () => {
156
156
  expect(mockHttpClient.rawRequest.mock.calls.at(-1)[0].credentials).toBe('include');
157
157
  await client.withBustCache();
158
158
  expect(mockHttpClient.rawRequest.mock.calls.at(-1)[0].context[bustCache]).toBe(true);
159
- expect(Client.getEndpointResource('withCredentials')).toContain('with-credentials');
160
- expect(Client.getEndpointUrl('withCredentials').href).toBe('http://baseurl/api/v1/features/with-credentials');
161
- expect(client.getEndpointUrl('withCredentials').href).toBe('http://localhost/api/v1/features/with-credentials');
159
+ expect(Client.getEndpointResource('withCredentials')).not.toContain('withCredentials');
160
+ expect(Client.getEndpointUrl('withCredentials').href).toBe('http://baseurl/api/v1/features');
161
+ expect(client.getEndpointUrl('withCredentials').href).toBe('http://localhost/api/v1/features');
162
162
  });
163
163
  it('should handle Server Sent Events and DataStream', async () => {
164
164
  const apiDefinition = defineApi({
@@ -185,10 +185,10 @@ describe('ApiClient', () => {
185
185
  const client = new Client(mockHttpClient);
186
186
  const sse = await client.events();
187
187
  expect(sse).toBeInstanceOf(ServerSentEvents);
188
- expect(EventSourceMock).toHaveBeenCalledWith(expect.stringContaining('sse/events'), expect.any(Object));
188
+ expect(EventSourceMock).toHaveBeenCalledWith(expect.stringContaining('sse'), expect.any(Object));
189
189
  const stream = await client.stream();
190
190
  expect(stream).toBeInstanceOf(Object); // It's an Observable
191
- expect(EventSourceMock).toHaveBeenCalledWith(expect.stringContaining('sse/stream'), expect.any(Object));
191
+ expect(EventSourceMock).toHaveBeenCalledWith(expect.stringContaining('sse'), expect.any(Object));
192
192
  vi.unstubAllGlobals();
193
193
  });
194
194
  });
package/api/types.d.ts CHANGED
@@ -54,7 +54,7 @@ export type ApiEndpointDefinition = {
54
54
  *
55
55
  * results in
56
56
  * ```ts
57
- * ${endpoint.rootResource ?? api.ressource}/${endpoint.resource}
57
+ * ${endpoint.rootResource ?? api.resource}/${endpoint.resource}
58
58
  * ```
59
59
  * @default name of endpoint property
60
60
  */
package/api/types.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { objectEntries } from '../utils/object/object.js';
2
- import { hyphenate } from '../utils/string/index.js';
3
- import { isFunction, isUndefined } from '../utils/type-guards.js';
2
+ import { isFunction } from '../utils/type-guards.js';
4
3
  import { resolveValueOrProvider } from '../utils/value-or-provider.js';
5
4
  export function defineApi(definition) {
6
5
  return definition;
@@ -16,11 +15,5 @@ export function normalizedApiDefinitionEndpoints(apiDefinitionEndpoints) {
16
15
  return Object.fromEntries(entries);
17
16
  }
18
17
  export function normalizedApiDefinitionEndpointsEntries(apiDefinition) {
19
- return objectEntries(apiDefinition).map(([key, def]) => {
20
- const endpoint = resolveValueOrProvider(def);
21
- if (isUndefined(endpoint.resource)) {
22
- endpoint.resource = hyphenate(key);
23
- }
24
- return [key, endpoint];
25
- });
18
+ return objectEntries(apiDefinition).map(([key, def]) => [key, resolveValueOrProvider(def)]);
26
19
  }
@@ -6,7 +6,6 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
6
6
  };
7
7
  import sharp, {} from 'sharp';
8
8
  import { match } from 'ts-pattern';
9
- import { injectGenkit } from '../../../ai/genkit/index.js';
10
9
  import { ForbiddenError } from '../../../errors/forbidden.error.js';
11
10
  import { NotImplementedError } from '../../../errors/not-implemented.error.js';
12
11
  import { getMimeType, getMimeTypeExtensions, mimeTypes } from '../../../file/index.js';
@@ -20,14 +19,15 @@ import { digest } from '../../../utils/cryptography.js';
20
19
  import { currentTimestamp } from '../../../utils/date-time.js';
21
20
  import { getRandomString } from '../../../utils/random.js';
22
21
  import { readableStreamFromPromise, readBinaryStream } from '../../../utils/stream/index.js';
23
- import { isNotReadableStream, isNotUint8Array, isUint8Array } from '../../../utils/type-guards.js';
22
+ import { isNotReadableStream, isNotUint8Array, isString, isUint8Array } from '../../../utils/type-guards.js';
24
23
  import { millisecondsPerMinute } from '../../../utils/units.js';
25
24
  import { Document } from '../../models/index.js';
26
25
  import { DocumentManagementConfiguration } from '../module.js';
26
+ import { DocumentManagementAncillaryService } from './document-management-ancillary.service.js';
27
27
  import { DocumentManagementSingleton } from './singleton.js';
28
28
  let DocumentFileService = class DocumentFileService extends Transactional {
29
29
  #configuration = inject(DocumentManagementConfiguration);
30
- #genkit = injectGenkit();
30
+ #documentManagementAncillaryService = inject(DocumentManagementAncillaryService);
31
31
  #fileObjectStorage = inject(ObjectStorage, this.#configuration.fileObjectStorageModule);
32
32
  #filePreviewObjectStorage = inject(ObjectStorage, this.#configuration.filePreviewObjectStorageModule);
33
33
  #fileUploadObjectStorage = inject(ObjectStorage, { module: this.#configuration.fileUploadObjectStorageModule /* , configuration: { lifecycle: { expiration: { after: 5 * secondsPerMinute } } } */ });
@@ -86,7 +86,10 @@ let DocumentFileService = class DocumentFileService extends Transactional {
86
86
  return this.#fileObjectStorage.getContentStream(objectKey);
87
87
  }
88
88
  async getContentUrl(document, download = false) {
89
- return await this.getDocumentFileContentObjectUrl(document, document.title ?? document.id, download);
89
+ const resolvedFilename = await this.#documentManagementAncillaryService.getDocumentFilename(document);
90
+ const fileExtension = getMimeTypeExtensions(document.mimeType)[0] ?? 'bin';
91
+ const filename = isString(resolvedFilename) ? `${resolvedFilename}.${fileExtension}` : resolvedFilename.fullname;
92
+ return await this.getDocumentFileContentObjectUrl(document, filename, download);
90
93
  }
91
94
  /** Gets the underlying object storage object for the document file */
92
95
  async getObject(document) {
@@ -132,14 +135,12 @@ let DocumentFileService = class DocumentFileService extends Transactional {
132
135
  await this.#filePreviewObjectStorage.uploadObject(key, image, { contentLength: image.length, contentType: 'image/webp' });
133
136
  }
134
137
  }
135
- async getDocumentFileContentObjectUrl(document, title, download) {
138
+ async getDocumentFileContentObjectUrl(document, filename, download) {
136
139
  const key = getObjectKey(document.id);
137
- const fileExtension = getMimeTypeExtensions(document.mimeType)[0] ?? 'bin';
138
140
  const disposition = download ? 'attachment' : 'inline';
139
- const filename = `${title}.${fileExtension}`;
140
141
  return await this.#fileObjectStorage.getDownloadUrl(key, currentTimestamp() + (5 * millisecondsPerMinute), {
141
- 'Response-Content-Type': document.mimeType,
142
- 'Response-Content-Disposition': `${disposition}; filename = "${encodeURIComponent(filename)}"`,
142
+ 'Content-Type': document.mimeType,
143
+ 'Content-Disposition': `${disposition}; filename = "${encodeURIComponent(filename)}"`,
143
144
  });
144
145
  }
145
146
  };
@@ -1,5 +1,9 @@
1
- import type { DocumentCollection } from '../../models/index.js';
1
+ import type { Document, DocumentCollection } from '../../models/index.js';
2
2
  import type { DocumentCollectionMetadata } from '../../service-models/index.js';
3
+ export type DocumentFileNameResult = string | {
4
+ /** The full name of the document, including extension. */
5
+ fullname: string;
6
+ };
3
7
  export declare abstract class DocumentManagementAncillaryService {
4
8
  /**
5
9
  * Resolves application-specific metadata for a list of document collections.
@@ -7,4 +11,11 @@ export declare abstract class DocumentManagementAncillaryService {
7
11
  * @returns A promise that resolves to an array of DocumentCollectionMetadata, corresponding to the input collections.
8
12
  */
9
13
  abstract resolveMetadata(tenantId: string, collections: DocumentCollection[]): DocumentCollectionMetadata[] | Promise<DocumentCollectionMetadata[]>;
14
+ /**
15
+ * Resolve the file name for a document, which will be used when downloading the document file. The file extension is automatically determined based on the document's MIME type, so the returned file name should not include the extension.
16
+ * @param document The Document entity for which to resolve the file name.
17
+ * @param title An optional title provided by the api call.
18
+ * @returns The resolved file name for the document, without the file extension.
19
+ */
20
+ getDocumentFilename(document: Document): DocumentFileNameResult | Promise<DocumentFileNameResult>;
10
21
  }
@@ -1,2 +1,11 @@
1
1
  export class DocumentManagementAncillaryService {
2
+ /**
3
+ * Resolve the file name for a document, which will be used when downloading the document file. The file extension is automatically determined based on the document's MIME type, so the returned file name should not include the extension.
4
+ * @param document The Document entity for which to resolve the file name.
5
+ * @param title An optional title provided by the api call.
6
+ * @returns The resolved file name for the document, without the file extension.
7
+ */
8
+ getDocumentFilename(document) {
9
+ return document.title ?? document.originalFileName ?? document.id;
10
+ }
2
11
  }
@@ -12,7 +12,7 @@ export declare class TemporaryFile implements AsyncDisposable {
12
12
  /**
13
13
  * Prevents the temporary file from being deleted on disposal.
14
14
  */
15
- keep(): void;
15
+ keep(): this;
16
16
  read(): Promise<Uint8Array>;
17
17
  readText(): Promise<string>;
18
18
  readStream(): ReadableStream<Uint8Array>;
@@ -25,5 +25,6 @@ export declare class TemporaryFile implements AsyncDisposable {
25
25
  moveTo(path: string, keep?: boolean): Promise<void>;
26
26
  delete(): Promise<void>;
27
27
  size(): Promise<number>;
28
+ dispose(): Promise<void>;
28
29
  [Symbol.asyncDispose](): Promise<void>;
29
30
  }
@@ -36,6 +36,7 @@ export class TemporaryFile {
36
36
  */
37
37
  keep() {
38
38
  this.#keep = true;
39
+ return this;
39
40
  }
40
41
  async read() {
41
42
  return await readFile(this.#path);
@@ -69,7 +70,7 @@ export class TemporaryFile {
69
70
  const result = await stat(this.#path);
70
71
  return result.size;
71
72
  }
72
- async [Symbol.asyncDispose]() {
73
+ async dispose() {
73
74
  if (this.#keep) {
74
75
  return;
75
76
  }
@@ -78,4 +79,7 @@ export class TemporaryFile {
78
79
  }
79
80
  catch { }
80
81
  }
82
+ async [Symbol.asyncDispose]() {
83
+ await this.dispose();
84
+ }
81
85
  }
@@ -240,16 +240,16 @@ let S3ObjectStorage = S3ObjectStorage_1 = class S3ObjectStorage extends ObjectSt
240
240
  async getDownloadUrl(key, expirationTimestamp, responseHeaders) {
241
241
  const bucketKey = this.getBucketKey(key);
242
242
  const expiration = getExpiration(expirationTimestamp);
243
- const expiresHeader = responseHeaders?.['Expires'];
243
+ const expiresHeader = responseHeaders?.['Expires'] ?? responseHeaders?.['Response-Expires'];
244
244
  const mappedExpiresHeader = isDefined(expiresHeader) ? (isDate(expiresHeader) ? expiresHeader : new Date(expiresHeader)) : undefined;
245
245
  return await getSignedUrl(this.client, new GetObjectCommand({
246
246
  Bucket: this.bucket,
247
247
  Key: bucketKey,
248
- ResponseContentType: responseHeaders?.['Content-Type'],
249
- ResponseContentDisposition: responseHeaders?.['Content-Disposition'],
250
- ResponseCacheControl: responseHeaders?.['Cache-Control'],
251
- ResponseContentLanguage: responseHeaders?.['Content-Language'],
252
- ResponseContentEncoding: responseHeaders?.['Content-Encoding'],
248
+ ResponseContentType: responseHeaders?.['Content-Type'] ?? responseHeaders?.['Response-Content-Type'],
249
+ ResponseContentDisposition: responseHeaders?.['Content-Disposition'] ?? responseHeaders?.['Response-Content-Disposition'],
250
+ ResponseCacheControl: responseHeaders?.['Cache-Control'] ?? responseHeaders?.['Response-Cache-Control'],
251
+ ResponseContentLanguage: responseHeaders?.['Content-Language'] ?? responseHeaders?.['Response-Content-Language'],
252
+ ResponseContentEncoding: responseHeaders?.['Content-Encoding'] ?? responseHeaders?.['Response-Content-Encoding'],
253
253
  ResponseExpires: mappedExpiresHeader,
254
254
  }), { expiresIn: expiration });
255
255
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tstdl/base",
3
- "version": "0.93.128",
3
+ "version": "0.93.129",
4
4
  "author": "Patrick Hein",
5
5
  "publishConfig": {
6
6
  "access": "public"
package/pdf/utils.d.ts CHANGED
@@ -1,6 +1,23 @@
1
- export declare function getPdfPageCount(file: string | Uint8Array | ReadableStream<Uint8Array>): Promise<number>;
2
- export declare function mergePdfs(pdfs: (string | Uint8Array | ReadableStream<Uint8Array>)[]): Promise<Uint8Array>;
3
- export declare function mergePdfsStream(pdfs: (string | Uint8Array | ReadableStream<Uint8Array>)[]): Promise<ReadableStream<Uint8Array>>;
1
+ import { TemporaryFile } from '../file/server/temporary-file.js';
2
+ export type PdfInput = string | {
3
+ file: string;
4
+ } | Uint8Array | ReadableStream<Uint8Array>;
5
+ export type PdfInputWithPageRange = string | {
6
+ file: string;
7
+ } | Uint8Array | ReadableStream<Uint8Array> | {
8
+ input: PdfInput;
9
+ pages: PageRange[];
10
+ };
11
+ export type PageRange = number | {
12
+ from?: number;
13
+ to?: number;
14
+ };
15
+ export declare function getPdfPageCount(file: PdfInput): Promise<number>;
16
+ export declare function extractPdfPages(file: PdfInput, pages: PageRange[]): Promise<TemporaryFile>;
17
+ export declare function mergePdfs(sourceFiles: PdfInputWithPageRange[]): Promise<TemporaryFile>;
18
+ export declare function overlayPdfs(base: PdfInput, overlay: PdfInput, options?: {
19
+ repeat?: boolean;
20
+ }): Promise<TemporaryFile>;
4
21
  /**
5
22
  * Convert a PDF page to an image.
6
23
  * @param file The PDF file to convert.
@@ -10,3 +27,7 @@ export declare function mergePdfsStream(pdfs: (string | Uint8Array | ReadableStr
10
27
  * @returns The converted image as a byte array.
11
28
  */
12
29
  export declare function pdfToImage(file: string | Uint8Array | ReadableStream<Uint8Array>, page: number, size: number, format: 'png' | 'jpeg' | 'tiff' | 'ps' | 'eps' | 'pdf' | 'svg'): Promise<Uint8Array>;
30
+ export declare function isInputWithPageRange(input: PdfInputWithPageRange): input is {
31
+ input: PdfInput;
32
+ pages: PageRange[];
33
+ };
package/pdf/utils.js CHANGED
@@ -51,15 +51,15 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
51
51
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
52
  });
53
53
  import { TemporaryFile } from '../file/server/temporary-file.js';
54
- import { spawnCommand } from '../process/spawn.js';
55
- import { isNotString, isString } from '../utils/type-guards.js';
54
+ import { spawnCommand, spawnWaitReadCommand } from '../process/spawn.js';
55
+ import { hasOwnProperty } from '../utils/object/object.js';
56
+ import { isNotString, isNumber, isObject, isReadableStream, isString, isUint8Array, isUndefined } from '../utils/type-guards.js';
56
57
  export async function getPdfPageCount(file) {
57
58
  const env_1 = { stack: [], error: void 0, hasError: false };
58
59
  try {
59
- const fileIsPath = isString(file);
60
- const tmpFile = __addDisposableResource(env_1, fileIsPath ? undefined : await TemporaryFile.from(file), true);
61
- const path = fileIsPath ? file : tmpFile.path;
62
- const process = await spawnCommand('qpdf', ['--show-npages', path]);
60
+ const stack = __addDisposableResource(env_1, new AsyncDisposableStack(), true);
61
+ const [sourceFile] = await getPdfSourceFiles([file], stack);
62
+ const process = await spawnCommand('qpdf', ['--show-npages', sourceFile]);
63
63
  const { code } = await process.wait();
64
64
  if (code != 0) {
65
65
  const errorOutput = await process.readError();
@@ -78,14 +78,19 @@ export async function getPdfPageCount(file) {
78
78
  await result_1;
79
79
  }
80
80
  }
81
- export async function mergePdfs(pdfs) {
81
+ export async function extractPdfPages(file, pages) {
82
82
  const env_2 = { stack: [], error: void 0, hasError: false };
83
83
  try {
84
84
  const stack = __addDisposableResource(env_2, new AsyncDisposableStack(), true);
85
- const resultFile = __addDisposableResource(env_2, TemporaryFile.create(), true);
86
- const sourceFiles = await getPdfSourceFiles(pdfs, stack);
87
- await pdfunite(sourceFiles, resultFile);
88
- return await resultFile.read();
85
+ const [sourceFile] = await getPdfSourceFiles([file], stack);
86
+ const resultFile = TemporaryFile.create();
87
+ const pagesString = toQpdfPageRangeString(pages);
88
+ const processResult = await spawnWaitReadCommand('string', 'qpdf', ['--empty', '--pages', sourceFile, pagesString, '--', resultFile.path]);
89
+ if (processResult.code != 0) {
90
+ await resultFile.dispose();
91
+ throw new Error(processResult.error.trim());
92
+ }
93
+ return resultFile;
89
94
  }
90
95
  catch (e_2) {
91
96
  env_2.error = e_2;
@@ -97,14 +102,26 @@ export async function mergePdfs(pdfs) {
97
102
  await result_2;
98
103
  }
99
104
  }
100
- export async function mergePdfsStream(pdfs) {
105
+ export async function mergePdfs(sourceFiles) {
101
106
  const env_3 = { stack: [], error: void 0, hasError: false };
102
107
  try {
103
108
  const stack = __addDisposableResource(env_3, new AsyncDisposableStack(), true);
104
109
  const resultFile = __addDisposableResource(env_3, TemporaryFile.create(), true);
105
- const sourceFiles = await getPdfSourceFiles(pdfs, stack);
106
- await pdfunite(sourceFiles, resultFile);
107
- return resultFile.readStream();
110
+ const sourceFilePaths = await getPdfSourceFiles(sourceFiles, stack);
111
+ const pages = sourceFiles.map((source) => isInputWithPageRange(source) ? source.pages : undefined);
112
+ const sourceArguments = sourceFilePaths.flatMap((path, index) => {
113
+ const pageRanges = pages[index];
114
+ if (isUndefined(pageRanges)) {
115
+ return [path];
116
+ }
117
+ const pagesString = toQpdfPageRangeString(pageRanges);
118
+ return [path, pagesString];
119
+ });
120
+ const processResult = await spawnWaitReadCommand('string', 'qpdf', ['--empty', '--pages', ...sourceArguments, '--', resultFile.path]);
121
+ if (processResult.code != 0) {
122
+ throw new Error(processResult.error);
123
+ }
124
+ return resultFile;
108
125
  }
109
126
  catch (e_3) {
110
127
  env_3.error = e_3;
@@ -116,22 +133,32 @@ export async function mergePdfsStream(pdfs) {
116
133
  await result_3;
117
134
  }
118
135
  }
119
- async function getPdfSourceFiles(pdfs, stack) {
120
- return await Promise.all(pdfs.map(async (pdf) => {
121
- if (isString(pdf)) {
122
- return pdf;
136
+ export async function overlayPdfs(base, overlay, options) {
137
+ const env_4 = { stack: [], error: void 0, hasError: false };
138
+ try {
139
+ const stack = __addDisposableResource(env_4, new AsyncDisposableStack(), true);
140
+ const [baseFilePath, overlayFilePath] = await getPdfSourceFiles([base, overlay], stack);
141
+ const resultFile = TemporaryFile.create();
142
+ stack.use(resultFile);
143
+ const args = [baseFilePath, '--overlay', overlayFilePath];
144
+ if (options?.repeat) {
145
+ args.push('--repeat=1');
123
146
  }
124
- const tmpFile = await TemporaryFile.from(pdf);
125
- stack.use(tmpFile);
126
- return tmpFile.path;
127
- }));
128
- }
129
- async function pdfunite(sourceFiles, resultFile) {
130
- const process = await spawnCommand('pdfunite', [...sourceFiles, resultFile.path]);
131
- const { code } = await process.wait();
132
- if (code != 0) {
133
- const errorOutput = await process.readError();
134
- throw new Error(errorOutput);
147
+ args.push('--', resultFile.path);
148
+ const processResult = await spawnWaitReadCommand('string', 'qpdf', args);
149
+ if (processResult.code != 0) {
150
+ throw new Error(processResult.error);
151
+ }
152
+ return resultFile;
153
+ }
154
+ catch (e_4) {
155
+ env_4.error = e_4;
156
+ env_4.hasError = true;
157
+ }
158
+ finally {
159
+ const result_4 = __disposeResources(env_4);
160
+ if (result_4)
161
+ await result_4;
135
162
  }
136
163
  }
137
164
  /**
@@ -151,3 +178,35 @@ export async function pdfToImage(file, page, size, format) {
151
178
  }
152
179
  return await process.readOutputBytes();
153
180
  }
181
+ function toQpdfPageRangeString(pages) {
182
+ return pages.map((page) => {
183
+ if (isNumber(page)) {
184
+ return String(page);
185
+ }
186
+ else {
187
+ const from = page.from ?? '1';
188
+ const to = page.to ?? 'z';
189
+ return `${from}-${to}`;
190
+ }
191
+ }).join(',');
192
+ }
193
+ async function getPdfSourceFiles(input, stack) {
194
+ return await Promise.all(input.map(async (pdf) => {
195
+ if (isUint8Array(pdf) || isReadableStream(pdf)) {
196
+ const tmpFile = await TemporaryFile.from(pdf);
197
+ stack.use(tmpFile);
198
+ return tmpFile.path;
199
+ }
200
+ if (isString(pdf)) {
201
+ return pdf;
202
+ }
203
+ if (isInputWithPageRange(pdf)) {
204
+ const [path] = await getPdfSourceFiles([pdf.input], stack);
205
+ return path;
206
+ }
207
+ return pdf.file;
208
+ }));
209
+ }
210
+ export function isInputWithPageRange(input) {
211
+ return isObject(input) && hasOwnProperty(input, 'input') && hasOwnProperty(input, 'pages');
212
+ }
@@ -18,7 +18,7 @@ export type SpawnOptions = {
18
18
  workingDirectory?: string;
19
19
  environment?: Record<string, string>;
20
20
  };
21
- export type SpawnCommandResult = TransformStream<Uint8Array, Uint8Array> & {
21
+ export type SpawnCommandResult = TransformStream<Uint8Array, Uint8Array<ArrayBuffer>> & {
22
22
  process: ChildProcessWithoutNullStreams;
23
23
  stderr: ReadableStream<Uint8Array>;
24
24
  write(chunk: ReadableStream<Uint8Array> | Uint8Array | string, options?: StreamPipeOptions): Promise<void>;
@@ -43,6 +43,11 @@ export type TypstRenderOptions = {
43
43
  * One (or multiple comma-separated) PDF standards that Typst will enforce conformance with.
44
44
  */
45
45
  pdfStandard?: LiteralUnion<'1.4' | '1.5' | '1.6' | '1.7' | '2.0' | 'a-1b' | 'a-1a' | 'a-2b' | 'a-2u' | 'a-2a' | 'a-3b' | 'a-3u' | 'a-3a' | 'a-4' | 'a-4f' | 'a-4e' | 'ua-1', string>;
46
+ /**
47
+ * A reference file to use when rendering to docx format. This allows customizing styles, fonts, etc. in the generated docx file. The file should be a valid docx file that serves as a template for the output.
48
+ * The reference file can be generated with `pandoc --print-default-data-file reference.docx`
49
+ */
50
+ docxReferenceFile?: string;
46
51
  };
47
52
  /**
48
53
  * Renders Typst source code to a file in the specified format.
package/renderer/typst.js CHANGED
@@ -14,10 +14,9 @@ import { isDefined, isNumber, isString } from '../utils/type-guards.js';
14
14
  export async function renderTypst(source, options) {
15
15
  const format = options?.format ?? 'pdf';
16
16
  const command = (format == 'docx') ? 'pandoc' : 'typst';
17
- let args = (format == 'docx')
18
- ? ['--from', 'typst', '--to', 'docx', '--output']
19
- : ['compile', '--format', format];
17
+ let args = [];
20
18
  if (command == 'typst') {
19
+ args = ['compile', '--format', format];
21
20
  if (isDefined(options?.root)) {
22
21
  args.push('--root', options.root);
23
22
  }
@@ -45,14 +44,19 @@ export async function renderTypst(source, options) {
45
44
  args.push('--pages', pageParts.join(','));
46
45
  }
47
46
  }
48
- args.push('-', '-');
47
+ else if (command == 'pandoc') {
48
+ args = ['--from', 'typst', '--to', 'docx', '--output', '-'];
49
+ if (isDefined(options?.docxReferenceFile)) {
50
+ args.push('--reference-doc', options.docxReferenceFile);
51
+ }
52
+ }
49
53
  const process = await spawnCommand(command, args);
50
54
  const [{ code, output, error }] = await Promise.all([
51
55
  process.waitRead('binary', { throwOnNonZeroExitCode: false }),
52
56
  process.write(source),
53
57
  ]);
54
58
  const errorString = decodeText(error);
55
- if (code !== 0) {
59
+ if (code != 0) {
56
60
  throw new Error(`
57
61
  Typst compilation failed with exit code ${code}.\n
58
62
  Error Output:\n${errorString}