@tstdl/base 0.93.66 → 0.93.68
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/file/server/temporary-file.d.ts +13 -2
- package/file/server/temporary-file.js +35 -7
- package/latex/index.d.ts +1 -0
- package/latex/index.js +1 -0
- package/latex/render.d.ts +19 -0
- package/latex/render.js +95 -0
- package/package.json +2 -1
- package/pdf/utils.js +1 -1
- package/process/spawn.d.ts +15 -5
- package/process/spawn.js +12 -5
- package/utils/date-time.d.ts +2 -2
- package/utils/date-time.js +2 -2
|
@@ -1,17 +1,28 @@
|
|
|
1
|
+
/** Represents a temporary file on the filesystem which is automatically deleted on disposal. */
|
|
1
2
|
export declare class TemporaryFile implements AsyncDisposable {
|
|
2
3
|
#private;
|
|
3
4
|
get path(): string;
|
|
4
|
-
static create(): TemporaryFile;
|
|
5
|
+
static create(extension?: string): TemporaryFile;
|
|
5
6
|
/**
|
|
6
7
|
* Use an existing file as a temporary file which gets deleted on disposal.
|
|
7
8
|
* @param path path to adopt
|
|
8
9
|
*/
|
|
9
10
|
static adopt(path: string): TemporaryFile;
|
|
10
|
-
static from(content: string | Uint8Array | ReadableStream<Uint8Array
|
|
11
|
+
static from(content: string | Uint8Array | ReadableStream<Uint8Array>, extension?: string): Promise<TemporaryFile>;
|
|
12
|
+
/**
|
|
13
|
+
* Prevents the temporary file from being deleted on disposal.
|
|
14
|
+
*/
|
|
15
|
+
keep(): void;
|
|
11
16
|
read(): Promise<Uint8Array>;
|
|
12
17
|
readText(): Promise<string>;
|
|
13
18
|
readStream(): ReadableStream<Uint8Array>;
|
|
14
19
|
write(content: string | Uint8Array | ReadableStream<Uint8Array>): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Moves the file to a new location.
|
|
22
|
+
* @param path The new path for the file.
|
|
23
|
+
* @param keep If true, prevents the file from being deleted on disposal.
|
|
24
|
+
*/
|
|
25
|
+
moveTo(path: string, keep?: boolean): Promise<void>;
|
|
15
26
|
delete(): Promise<void>;
|
|
16
27
|
size(): Promise<number>;
|
|
17
28
|
[Symbol.asyncDispose](): Promise<void>;
|
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import { createReadStream } from 'node:fs';
|
|
2
|
-
import { readFile, stat, unlink, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { readFile, rename, stat, unlink, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
4
|
import { Readable } from 'node:stream';
|
|
5
|
+
import { isDefined } from '../../utils/type-guards.js';
|
|
6
|
+
/** Represents a temporary file on the filesystem which is automatically deleted on disposal. */
|
|
5
7
|
export class TemporaryFile {
|
|
6
8
|
#path = `${tmpdir()}/${crypto.randomUUID()}`;
|
|
9
|
+
#keep = false;
|
|
7
10
|
get path() {
|
|
8
11
|
return this.#path;
|
|
9
12
|
}
|
|
10
|
-
static create() {
|
|
11
|
-
|
|
13
|
+
static create(extension) {
|
|
14
|
+
const file = new TemporaryFile();
|
|
15
|
+
if (isDefined(extension)) {
|
|
16
|
+
file.#path += extension.startsWith('.') ? extension : `.${extension}`;
|
|
17
|
+
}
|
|
18
|
+
return file;
|
|
12
19
|
}
|
|
13
20
|
/**
|
|
14
21
|
* Use an existing file as a temporary file which gets deleted on disposal.
|
|
@@ -19,16 +26,22 @@ export class TemporaryFile {
|
|
|
19
26
|
file.#path = path;
|
|
20
27
|
return file;
|
|
21
28
|
}
|
|
22
|
-
static async from(content) {
|
|
23
|
-
const file =
|
|
29
|
+
static async from(content, extension) {
|
|
30
|
+
const file = TemporaryFile.create(extension);
|
|
24
31
|
await file.write(content);
|
|
25
32
|
return file;
|
|
26
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Prevents the temporary file from being deleted on disposal.
|
|
36
|
+
*/
|
|
37
|
+
keep() {
|
|
38
|
+
this.#keep = true;
|
|
39
|
+
}
|
|
27
40
|
async read() {
|
|
28
|
-
return readFile(this.#path);
|
|
41
|
+
return await readFile(this.#path);
|
|
29
42
|
}
|
|
30
43
|
async readText() {
|
|
31
|
-
return readFile(this.#path, { encoding: 'utf8' });
|
|
44
|
+
return await readFile(this.#path, { encoding: 'utf8' });
|
|
32
45
|
}
|
|
33
46
|
readStream() {
|
|
34
47
|
const stream = createReadStream(this.#path);
|
|
@@ -37,6 +50,18 @@ export class TemporaryFile {
|
|
|
37
50
|
async write(content) {
|
|
38
51
|
await writeFile(this.#path, content);
|
|
39
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Moves the file to a new location.
|
|
55
|
+
* @param path The new path for the file.
|
|
56
|
+
* @param keep If true, prevents the file from being deleted on disposal.
|
|
57
|
+
*/
|
|
58
|
+
async moveTo(path, keep) {
|
|
59
|
+
await rename(this.#path, path);
|
|
60
|
+
this.#path = path;
|
|
61
|
+
if (keep) {
|
|
62
|
+
this.keep();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
40
65
|
async delete() {
|
|
41
66
|
await unlink(this.#path);
|
|
42
67
|
}
|
|
@@ -45,6 +70,9 @@ export class TemporaryFile {
|
|
|
45
70
|
return result.size;
|
|
46
71
|
}
|
|
47
72
|
async [Symbol.asyncDispose]() {
|
|
73
|
+
if (this.#keep) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
48
76
|
try {
|
|
49
77
|
await this.delete();
|
|
50
78
|
}
|
package/latex/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './render.js';
|
package/latex/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './render.js';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { TemporaryFile } from '../file/server/temporary-file.js';
|
|
2
|
+
export type LatexRenderOptions = {
|
|
3
|
+
/**
|
|
4
|
+
* The LaTeX engine to use for rendering the document.
|
|
5
|
+
* @default 'lualatex'
|
|
6
|
+
*/
|
|
7
|
+
engine?: 'pdflatex' | 'lualatex' | 'xelatex';
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Renders LaTeX source code to a PDF file.
|
|
11
|
+
*
|
|
12
|
+
* Requires latexmk and LuaTeX to be installed on the system.
|
|
13
|
+
*
|
|
14
|
+
* **Minimal recommendation:**
|
|
15
|
+
* - **Arch Linux:** texlive-binextra texlive-luatex texlive-latexrecommended texlive-fontsrecommended
|
|
16
|
+
* @param source The LaTeX source code to render
|
|
17
|
+
* @returns A TemporaryFile representing the generated PDF
|
|
18
|
+
*/
|
|
19
|
+
export declare function renderLatex(source: string, options?: LatexRenderOptions): Promise<TemporaryFile>;
|
package/latex/render.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
2
|
+
if (value !== null && value !== void 0) {
|
|
3
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
4
|
+
var dispose, inner;
|
|
5
|
+
if (async) {
|
|
6
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
7
|
+
dispose = value[Symbol.asyncDispose];
|
|
8
|
+
}
|
|
9
|
+
if (dispose === void 0) {
|
|
10
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
11
|
+
dispose = value[Symbol.dispose];
|
|
12
|
+
if (async) inner = dispose;
|
|
13
|
+
}
|
|
14
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
15
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
16
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
17
|
+
}
|
|
18
|
+
else if (async) {
|
|
19
|
+
env.stack.push({ async: true });
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
};
|
|
23
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
24
|
+
return function (env) {
|
|
25
|
+
function fail(e) {
|
|
26
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
27
|
+
env.hasError = true;
|
|
28
|
+
}
|
|
29
|
+
var r, s = 0;
|
|
30
|
+
function next() {
|
|
31
|
+
while (r = env.stack.pop()) {
|
|
32
|
+
try {
|
|
33
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
34
|
+
if (r.dispose) {
|
|
35
|
+
var result = r.dispose.call(r.value);
|
|
36
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
37
|
+
}
|
|
38
|
+
else s |= 1;
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
fail(e);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
45
|
+
if (env.hasError) throw env.error;
|
|
46
|
+
}
|
|
47
|
+
return next();
|
|
48
|
+
};
|
|
49
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
50
|
+
var e = new Error(message);
|
|
51
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
|
+
});
|
|
53
|
+
import { TemporaryFile } from '../file/server/temporary-file.js';
|
|
54
|
+
import { spawnCommand, spawnWaitCommand } from '../process/spawn.js';
|
|
55
|
+
import { tryIgnoreAsync } from '../utils/try-ignore.js';
|
|
56
|
+
const engineMapping = {
|
|
57
|
+
pdflatex: '-pdflatex',
|
|
58
|
+
lualatex: '-pdflua',
|
|
59
|
+
xelatex: '-pdfxe',
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Renders LaTeX source code to a PDF file.
|
|
63
|
+
*
|
|
64
|
+
* Requires latexmk and LuaTeX to be installed on the system.
|
|
65
|
+
*
|
|
66
|
+
* **Minimal recommendation:**
|
|
67
|
+
* - **Arch Linux:** texlive-binextra texlive-luatex texlive-latexrecommended texlive-fontsrecommended
|
|
68
|
+
* @param source The LaTeX source code to render
|
|
69
|
+
* @returns A TemporaryFile representing the generated PDF
|
|
70
|
+
*/
|
|
71
|
+
export async function renderLatex(source, options) {
|
|
72
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
73
|
+
try {
|
|
74
|
+
const latexFile = __addDisposableResource(env_1, await TemporaryFile.from(source, '.tex'), true);
|
|
75
|
+
const engineFlag = engineMapping[options?.engine ?? 'lualatex'];
|
|
76
|
+
const process = await spawnCommand('latexmk', ['-interaction=nonstopmode', engineFlag, '-cd', latexFile.path]);
|
|
77
|
+
console.log(latexFile.path);
|
|
78
|
+
const { code } = await process.wait();
|
|
79
|
+
if (code != 0) {
|
|
80
|
+
const [out, err] = await Promise.all([process.readOutput(), process.readError()]);
|
|
81
|
+
throw new Error(`LaTeX compilation failed with exit code ${code}. Output:\n${out}\nError Output:\n${err}`);
|
|
82
|
+
}
|
|
83
|
+
await tryIgnoreAsync(async () => await spawnWaitCommand('latexmk', ['-interaction=nonstopmode', '-pdflua', '-cd', '-c', latexFile.path]));
|
|
84
|
+
return TemporaryFile.adopt(`${latexFile.path.slice(0, -4)}.pdf`);
|
|
85
|
+
}
|
|
86
|
+
catch (e_1) {
|
|
87
|
+
env_1.error = e_1;
|
|
88
|
+
env_1.hasError = true;
|
|
89
|
+
}
|
|
90
|
+
finally {
|
|
91
|
+
const result_1 = __disposeResources(env_1);
|
|
92
|
+
if (result_1)
|
|
93
|
+
await result_1;
|
|
94
|
+
}
|
|
95
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tstdl/base",
|
|
3
|
-
"version": "0.93.
|
|
3
|
+
"version": "0.93.68",
|
|
4
4
|
"author": "Patrick Hein",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
"./jsx": "./jsx/index.js",
|
|
74
74
|
"./key-value-store": "./key-value-store/index.js",
|
|
75
75
|
"./key-value-store/postgres": "./key-value-store/postgres/index.js",
|
|
76
|
+
"./latex": "./latex/index.js",
|
|
76
77
|
"./lock": "./lock/index.js",
|
|
77
78
|
"./lock/postgres": "./lock/postgres/index.js",
|
|
78
79
|
"./lock/web": "./lock/web/index.js",
|
package/pdf/utils.js
CHANGED
|
@@ -147,7 +147,7 @@ export async function pdfToImage(file, page, size, format) {
|
|
|
147
147
|
const process = await spawnCommand('pdftocairo', ['-f', String(page), '-l', String(page), '-scale-to', String(size), '-singlefile', `-${format}`, path, '-']);
|
|
148
148
|
process.handleNonZeroExitCode();
|
|
149
149
|
if (isNotString(file)) {
|
|
150
|
-
process.
|
|
150
|
+
process.writeInBackground(file);
|
|
151
151
|
}
|
|
152
152
|
return await process.readOutputBytes();
|
|
153
153
|
}
|
package/process/spawn.d.ts
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
import type { ChildProcessWithoutNullStreams } from 'node:child_process';
|
|
2
|
-
type
|
|
2
|
+
import type { Record } from '../types/types.js';
|
|
3
|
+
export type WaitOptions = {
|
|
3
4
|
throwOnNonZeroExitCode?: boolean;
|
|
4
5
|
};
|
|
5
|
-
type ProcessResult = {
|
|
6
|
+
export type ProcessResult = {
|
|
6
7
|
code: number | null;
|
|
7
8
|
signal: string | null;
|
|
8
9
|
};
|
|
10
|
+
export type SpawnOptions = {
|
|
11
|
+
arguments?: string[];
|
|
12
|
+
workingDirectory?: string;
|
|
13
|
+
environment?: Record<string, string>;
|
|
14
|
+
};
|
|
9
15
|
export type SpawnCommandResult = TransformStream<Uint8Array, Uint8Array> & {
|
|
10
16
|
process: ChildProcessWithoutNullStreams;
|
|
11
17
|
stderr: ReadableStream<Uint8Array>;
|
|
12
18
|
write(chunk: ReadableStream<Uint8Array> | Uint8Array | string, options?: StreamPipeOptions): Promise<void>;
|
|
13
|
-
|
|
19
|
+
writeInBackground(chunk: ReadableStream<Uint8Array> | Uint8Array | string, options?: StreamPipeOptions): void;
|
|
14
20
|
readOutputBytes(): Promise<Uint8Array>;
|
|
15
21
|
readOutput(): Promise<string>;
|
|
16
22
|
readErrorBytes(): Promise<Uint8Array>;
|
|
@@ -18,5 +24,9 @@ export type SpawnCommandResult = TransformStream<Uint8Array, Uint8Array> & {
|
|
|
18
24
|
handleNonZeroExitCode(): void;
|
|
19
25
|
wait(options?: WaitOptions): Promise<ProcessResult>;
|
|
20
26
|
};
|
|
21
|
-
|
|
22
|
-
export
|
|
27
|
+
/** spwans a command and waits for it to complete */
|
|
28
|
+
export declare function spawnWaitCommand(command: string, args?: string[], options?: SpawnOptions & WaitOptions): Promise<ProcessResult>;
|
|
29
|
+
export declare function spawnWaitCommand(command: string, options?: SpawnOptions & WaitOptions): Promise<ProcessResult>;
|
|
30
|
+
/** Spawns a command as a child process. */
|
|
31
|
+
export declare function spawnCommand(command: string, args?: string[], options?: SpawnOptions): Promise<SpawnCommandResult>;
|
|
32
|
+
export declare function spawnCommand(command: string, options?: SpawnOptions): Promise<SpawnCommandResult>;
|
package/process/spawn.js
CHANGED
|
@@ -3,11 +3,17 @@ import { LazyPromise } from '../promise/lazy-promise.js';
|
|
|
3
3
|
import { decodeTextStream, encodeUtf8Stream } from '../utils/encoding.js';
|
|
4
4
|
import { readBinaryStream, readTextStream } from '../utils/stream/stream-reader.js';
|
|
5
5
|
import { toReadableStream } from '../utils/stream/to-readable-stream.js';
|
|
6
|
-
import { assertNotNullOrUndefinedPass, isReadableStream, isString, isUint8Array } from '../utils/type-guards.js';
|
|
7
|
-
export async function
|
|
6
|
+
import { assertNotNullOrUndefinedPass, isArray, isReadableStream, isString, isUint8Array } from '../utils/type-guards.js';
|
|
7
|
+
export async function spawnWaitCommand(command, argsOrOptions, optionsOrNothing) {
|
|
8
|
+
const [args, options] = isArray(argsOrOptions) ? [argsOrOptions, optionsOrNothing] : [undefined, argsOrOptions];
|
|
9
|
+
const process = await spawnCommand(command, args, options);
|
|
10
|
+
return await process.wait({ throwOnNonZeroExitCode: options?.throwOnNonZeroExitCode });
|
|
11
|
+
}
|
|
12
|
+
export async function spawnCommand(command, argsOrOptions, optionsOrNothing) {
|
|
8
13
|
const { spawn } = await dynamicImport('node:child_process');
|
|
9
14
|
const { Readable, Writable } = await dynamicImport('node:stream');
|
|
10
|
-
const
|
|
15
|
+
const [args, options] = isArray(argsOrOptions) ? [argsOrOptions, optionsOrNothing] : [undefined, argsOrOptions];
|
|
16
|
+
const process = spawn(command, args, { stdio: 'pipe', cwd: options?.workingDirectory, env: options?.environment });
|
|
11
17
|
await Promise.race([
|
|
12
18
|
new Promise((resolve) => process.on('spawn', resolve)),
|
|
13
19
|
new Promise((_, reject) => process.on('error', reject)),
|
|
@@ -26,7 +32,8 @@ export async function spawnCommand(command, args) {
|
|
|
26
32
|
await toReadableStream(data).pipeThrough(encodeUtf8Stream()).pipeTo(writable, options);
|
|
27
33
|
}
|
|
28
34
|
}
|
|
29
|
-
|
|
35
|
+
/** Writes data in the background. Must be used with care because of potential unhandled errors */
|
|
36
|
+
function writeInBackground(data, options) {
|
|
30
37
|
write(data, options).catch((error) => {
|
|
31
38
|
readable.cancel(error).catch(() => { });
|
|
32
39
|
writable.abort(error).catch(() => { });
|
|
@@ -80,7 +87,7 @@ export async function spawnCommand(command, args) {
|
|
|
80
87
|
writable,
|
|
81
88
|
stderr,
|
|
82
89
|
write,
|
|
83
|
-
|
|
90
|
+
writeInBackground,
|
|
84
91
|
readOutputBytes,
|
|
85
92
|
readOutput,
|
|
86
93
|
readErrorBytes,
|
package/utils/date-time.d.ts
CHANGED
|
@@ -58,7 +58,7 @@ export declare function dateTimeToTime(dateTime: DateTime): number;
|
|
|
58
58
|
export declare function numericDateTimeToDateTime({ date, time }: NumericDateTime, zone?: string): DateTime;
|
|
59
59
|
/** Converts a timestamp, Date, DateTime, ISO date string, or DateObject to a numeric date */
|
|
60
60
|
export declare function toNumericDate(value: number | Date | DateTime | string | DateObject): number;
|
|
61
|
-
export declare function toNumericDate
|
|
61
|
+
export declare function toNumericDate(value: number | Date | DateTime | string | DateObject | null | undefined): number | null;
|
|
62
62
|
/** Converts a numeric date to an ISO date string (YYYY-MM-DD) */
|
|
63
63
|
export declare function numericDateToIsoDate(numericDate: number): string;
|
|
64
|
-
export declare function numericDateToIsoDate
|
|
64
|
+
export declare function numericDateToIsoDate(numericDate: number | null | undefined): string | null;
|
package/utils/date-time.js
CHANGED
|
@@ -129,7 +129,7 @@ export function numericDateTimeToDateTime({ date, time }, zone) {
|
|
|
129
129
|
}
|
|
130
130
|
export function toNumericDate(value) {
|
|
131
131
|
if (isNullOrUndefined(value)) {
|
|
132
|
-
return
|
|
132
|
+
return null;
|
|
133
133
|
}
|
|
134
134
|
if (isNumber(value)) {
|
|
135
135
|
return timestampToNumericDate(value);
|
|
@@ -154,7 +154,7 @@ export function toNumericDate(value) {
|
|
|
154
154
|
}
|
|
155
155
|
export function numericDateToIsoDate(numericDate) {
|
|
156
156
|
if (isNullOrUndefined(numericDate)) {
|
|
157
|
-
return
|
|
157
|
+
return null;
|
|
158
158
|
}
|
|
159
159
|
const { year, month, day } = numericDateToDateObject(numericDate);
|
|
160
160
|
const monthString = month.toString().padStart(2, '0');
|