@tstdl/base 0.93.68 → 0.93.70

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.
@@ -1,11 +1,20 @@
1
- export declare let locale: string;
2
- export declare function configureFormats(options: {
1
+ import { DateTime } from 'luxon';
2
+ import type { ValueOrProvider } from '../utils/value-or-provider.js';
3
+ type LocaleOption = {
3
4
  locale?: string;
4
- }): void;
5
+ };
6
+ export type ConfigureLocaleOptions = {
7
+ locale?: ValueOrProvider<string>;
8
+ };
9
+ export type FormatNumberOptions = Intl.NumberFormatOptions & LocaleOption;
10
+ export type FormatDateTimeOptions = Intl.DateTimeFormatOptions & LocaleOption;
11
+ export type FormatNumberValue = number | bigint | Intl.StringNumericLiteral;
12
+ export type FormatDateTimeValue = number | Date | DateTime;
13
+ export declare let getLocale: () => string;
14
+ export declare function configureFormats({ locale }: ConfigureLocaleOptions): void;
5
15
  export declare const integerFormat: Intl.NumberFormatOptions;
6
16
  export declare const decimalFormat: Intl.NumberFormatOptions;
7
17
  export declare const decimal1Format: Intl.NumberFormatOptions;
8
- export declare const yearFormat: Intl.NumberFormatOptions;
9
18
  export declare const dateTimeNumeric: Intl.DateTimeFormatOptions;
10
19
  export declare const dateTimeShort: Intl.DateTimeFormatOptions;
11
20
  export declare const dateTimeLong: Intl.DateTimeFormatOptions;
@@ -16,19 +25,21 @@ export declare const timeShort: Intl.DateTimeFormatOptions;
16
25
  export declare const currencyFormat: Intl.NumberFormatOptions;
17
26
  export declare const currencyFormatWithoutCents: Intl.NumberFormatOptions;
18
27
  export declare const percentFormat: Intl.NumberFormatOptions;
19
- export declare function formatNumber(value: number, format?: Intl.NumberFormatOptions): string;
20
- export declare function formatInteger(value: number): string;
21
- export declare function formatDecimal(value: number, minimumFractionDigits?: number, maximumFractionDigits?: number): string;
22
- export declare function formatYear(value: number): string;
23
- export declare function formatTimeShort(value: number): string;
24
- export declare function formatDateShort(value: Date | number): string;
25
- export declare function formatDate(dateOrTimestamp: number | Date): string;
26
- export declare function formatNumericDate(numericDate: number): string;
27
- export declare function formatCurrency(value: number, currency: string): string;
28
- export declare function formatCurrencyWithoutCents(value: number, currency: string): string;
29
- export declare function formatEuro(value: number): string;
30
- export declare function formatEuroWithoutCents(value: number): string;
31
- export declare function formatPercent(value: number): string;
28
+ export declare function formatNumber(value: FormatNumberValue, format?: FormatNumberOptions): string;
29
+ export declare function formatDateTime(value: number | Date | DateTime, format?: FormatDateTimeOptions): string;
30
+ export declare function formatInteger(value: FormatNumberValue, format?: FormatNumberOptions): string;
31
+ export declare function formatDecimal(value: FormatNumberValue, format?: FormatNumberOptions): string;
32
+ export declare function formatCurrency(value: FormatNumberValue, currency: string, format?: FormatNumberOptions): string;
33
+ export declare function formatCurrencyWithoutCents(value: FormatNumberValue, currency: string, format?: FormatNumberOptions): string;
34
+ export declare function formatEuro(value: FormatNumberValue, format?: FormatNumberOptions): string;
35
+ export declare function formatEuroWithoutCents(value: FormatNumberValue, format?: FormatNumberOptions): string;
36
+ export declare function formatPercent(value: FormatNumberValue, format?: FormatNumberOptions): string;
37
+ export declare function formatTimeShort(value: FormatDateTimeValue, format?: FormatDateTimeOptions): string;
38
+ export declare function formatDateShort(value: FormatDateTimeValue, format?: FormatDateTimeOptions): string;
39
+ export declare function formatDate(dateOrTimestamp: FormatDateTimeValue, format?: FormatDateTimeOptions): string;
40
+ export declare function formatNumericDate(numericDate: number, options?: FormatDateTimeOptions): string;
41
+ export declare function formatNumericDateShort(numericDate: number, options?: FormatDateTimeOptions): string;
42
+ export declare function formatNumericDateLong(numericDate: number, options?: FormatDateTimeOptions): string;
32
43
  export type FormatPersonNameOptions<F = unknown> = {
33
44
  lastNameFirst?: boolean;
34
45
  fallback?: F;
@@ -45,7 +56,4 @@ export declare function formatPersonName(person: {
45
56
  } | null | undefined, options?: FormatPersonNameOptions & {
46
57
  fallback?: undefined;
47
58
  }): string;
48
- /**
49
- * @deprecated use {@link formatPersonName} instead
50
- */
51
- export declare const formatUserName: typeof formatPersonName;
59
+ export {};
@@ -1,9 +1,13 @@
1
- import { numericDateToTimestamp } from '../utils/date-time.js';
2
- import { memoize, memoizeSingle } from '../utils/function/memoize.js';
3
- import { isNullOrUndefined, isUndefined } from '../utils/type-guards.js';
4
- export let locale = 'de-DE';
5
- export function configureFormats(options) {
6
- locale = options.locale ?? locale;
1
+ import { DateTime } from 'luxon';
2
+ import { numericDateToDate } from '../utils/date-time.js';
3
+ import { isDefined, isNullOrUndefined, isString } from '../utils/type-guards.js';
4
+ const numberFormatterMap = new Map();
5
+ const dateTimeFormatterMap = new Map();
6
+ export let getLocale = () => 'de-DE';
7
+ export function configureFormats({ locale }) {
8
+ getLocale = (isString(locale)
9
+ ? () => locale
10
+ : locale) ?? getLocale;
7
11
  }
8
12
  export const integerFormat = {
9
13
  useGrouping: true,
@@ -19,10 +23,6 @@ export const decimal1Format = {
19
23
  minimumFractionDigits: 0,
20
24
  maximumFractionDigits: 1,
21
25
  };
22
- export const yearFormat = {
23
- useGrouping: false,
24
- maximumFractionDigits: 0,
25
- };
26
26
  export const dateTimeNumeric = {
27
27
  year: 'numeric',
28
28
  month: '2-digit',
@@ -77,57 +77,54 @@ export const percentFormat = {
77
77
  minimumFractionDigits: 2,
78
78
  maximumFractionDigits: 2,
79
79
  };
80
- const getDecimalFormatter = memoize(_getDecimalFormatter);
81
- const integerFormatter = memoizeSingle((loc) => Intl.NumberFormat(loc, integerFormat));
82
- const decimalFormatter = memoizeSingle((loc) => Intl.NumberFormat(loc, decimalFormat));
83
- const yearFormatter = memoizeSingle((loc) => Intl.NumberFormat(loc, yearFormat));
84
- const percentFormatter = memoizeSingle((loc) => Intl.NumberFormat(loc, percentFormat));
85
- const dateFormatter = memoizeSingle((loc) => Intl.DateTimeFormat(loc, dateShort));
86
- const currencyFormatter = memoize((currency, loc) => Intl.NumberFormat(loc, { ...currencyFormat, currency }));
87
- const currencyFormatterWithoutCents = memoize((currency, loc) => Intl.NumberFormat(loc, { ...currencyFormatWithoutCents, currency }));
88
- const timeShortFormatter = memoizeSingle((loc) => Intl.DateTimeFormat(loc, timeShort));
89
- const dateShortFormatter = memoizeSingle((loc) => Intl.DateTimeFormat(loc, dateShort));
90
80
  export function formatNumber(value, format) {
91
- return Intl.NumberFormat(locale, format).format(value);
81
+ const locale = format?.locale ?? getLocale();
82
+ return getNumberFormatter(locale, format).format(value);
92
83
  }
93
- export function formatInteger(value) {
94
- return integerFormatter(locale).format(value);
84
+ export function formatDateTime(value, format) {
85
+ const date = DateTime.isDateTime(value) ? value.toJSDate() : value;
86
+ const locale = format?.locale ?? getLocale();
87
+ return getDateTimeFormatter(locale, format).format(date);
95
88
  }
96
- export function formatDecimal(value, minimumFractionDigits, maximumFractionDigits) {
97
- if (isUndefined(minimumFractionDigits) && isUndefined(maximumFractionDigits)) {
98
- return decimalFormatter(locale).format(value);
99
- }
100
- return getDecimalFormatter(locale, minimumFractionDigits ?? 2, maximumFractionDigits ?? 2).format(value);
89
+ export function formatInteger(value, format) {
90
+ return formatNumber(value, { ...integerFormat, ...format });
91
+ }
92
+ export function formatDecimal(value, format) {
93
+ return formatNumber(value, { ...decimalFormat, ...format });
101
94
  }
102
- export function formatYear(value) {
103
- return yearFormatter(locale).format(value);
95
+ export function formatCurrency(value, currency, format) {
96
+ return formatNumber(value, { ...currencyFormat, currency, ...format });
104
97
  }
105
- export function formatTimeShort(value) {
106
- return timeShortFormatter(locale).format(new Date(1970, 1, 1, 0, 0, 0, value));
98
+ export function formatCurrencyWithoutCents(value, currency, format) {
99
+ return formatNumber(value, { ...currencyFormatWithoutCents, currency, ...format });
107
100
  }
108
- export function formatDateShort(value) {
109
- return dateShortFormatter(locale).format(value);
101
+ export function formatEuro(value, format) {
102
+ return formatCurrency(value, 'EUR', format);
110
103
  }
111
- export function formatDate(dateOrTimestamp) {
112
- return dateFormatter(locale).format(dateOrTimestamp);
104
+ export function formatEuroWithoutCents(value, format) {
105
+ return formatCurrencyWithoutCents(value, 'EUR', format);
113
106
  }
114
- export function formatNumericDate(numericDate) {
115
- return formatDate(numericDateToTimestamp(numericDate));
107
+ export function formatPercent(value, format) {
108
+ return formatNumber(value, { ...percentFormat, ...format });
116
109
  }
117
- export function formatCurrency(value, currency) {
118
- return currencyFormatter(currency, locale).format(value);
110
+ export function formatTimeShort(value, format) {
111
+ return formatDateTime(value, { ...timeShort, ...format });
119
112
  }
120
- export function formatCurrencyWithoutCents(value, currency) {
121
- return currencyFormatterWithoutCents(currency, locale).format(value);
113
+ export function formatDateShort(value, format) {
114
+ return formatDateTime(value, { ...dateShort, ...format });
122
115
  }
123
- export function formatEuro(value) {
124
- return currencyFormatter('EUR', locale).format(value);
116
+ export function formatDate(dateOrTimestamp, format) {
117
+ return formatDateTime(dateOrTimestamp, { ...dateShort, ...format });
125
118
  }
126
- export function formatEuroWithoutCents(value) {
127
- return currencyFormatterWithoutCents('EUR', locale).format(value);
119
+ export function formatNumericDate(numericDate, options) {
120
+ const timestamp = numericDateToDate(numericDate);
121
+ return formatDateTime(timestamp, { ...dateShort, ...options });
128
122
  }
129
- export function formatPercent(value) {
130
- return percentFormatter(locale).format(value);
123
+ export function formatNumericDateShort(numericDate, options) {
124
+ return formatNumericDate(numericDate, { ...dateShort, ...options });
125
+ }
126
+ export function formatNumericDateLong(numericDate, options) {
127
+ return formatNumericDate(numericDate, { ...dateLong, ...options });
131
128
  }
132
129
  export function formatPersonName(person, { lastNameFirst = false, fallback } = {}) {
133
130
  if (isNullOrUndefined(person?.firstName) || isNullOrUndefined(person.lastName)) {
@@ -138,14 +135,23 @@ export function formatPersonName(person, { lastNameFirst = false, fallback } = {
138
135
  }
139
136
  return `${person.firstName} ${person.lastName}`;
140
137
  }
141
- /**
142
- * @deprecated use {@link formatPersonName} instead
143
- */
144
- export const formatUserName = formatPersonName;
145
- function _getDecimalFormatter(locale, minimumFractionDigits = 2, maximumFractionDigits = 2) {
146
- return Intl.NumberFormat(locale, {
147
- useGrouping: true,
148
- minimumFractionDigits,
149
- maximumFractionDigits,
150
- });
138
+ function getNumberFormatter(locale, options) {
139
+ const key = JSON.stringify({ locale, options });
140
+ const existing = numberFormatterMap.get(key);
141
+ if (isDefined(existing)) {
142
+ return existing;
143
+ }
144
+ const formatter = new Intl.NumberFormat(locale, options);
145
+ numberFormatterMap.set(key, formatter);
146
+ return formatter;
147
+ }
148
+ function getDateTimeFormatter(locale, options) {
149
+ const key = JSON.stringify({ locale, options });
150
+ const existing = dateTimeFormatterMap.get(key);
151
+ if (isDefined(existing)) {
152
+ return existing;
153
+ }
154
+ const formatter = new Intl.DateTimeFormat(locale, options);
155
+ dateTimeFormatterMap.set(key, formatter);
156
+ return formatter;
151
157
  }
package/latex/render.d.ts CHANGED
@@ -9,11 +9,15 @@ export type LatexRenderOptions = {
9
9
  /**
10
10
  * Renders LaTeX source code to a PDF file.
11
11
  *
12
+ * ## WARNING
13
+ * **This function should not be used with untrusted LaTeX source, as it can lead to arbitrary code execution on the system.**
14
+ *
12
15
  * Requires latexmk and LuaTeX to be installed on the system.
13
16
  *
14
17
  * **Minimal recommendation:**
15
18
  * - **Arch Linux:** texlive-binextra texlive-luatex texlive-latexrecommended texlive-fontsrecommended
16
19
  * @param source The LaTeX source code to render
20
+ * @param options Rendering options
17
21
  * @returns A TemporaryFile representing the generated PDF
18
22
  */
19
23
  export declare function renderLatex(source: string, options?: LatexRenderOptions): Promise<TemporaryFile>;
package/latex/render.js CHANGED
@@ -51,7 +51,8 @@ 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, spawnWaitCommand } from '../process/spawn.js';
54
+ import { spawnWaitCommand, spawnWaitReadCommand } from '../process/spawn.js';
55
+ import { withTimeout } from '../utils/timing.js';
55
56
  import { tryIgnoreAsync } from '../utils/try-ignore.js';
56
57
  const engineMapping = {
57
58
  pdflatex: '-pdflatex',
@@ -61,11 +62,15 @@ const engineMapping = {
61
62
  /**
62
63
  * Renders LaTeX source code to a PDF file.
63
64
  *
65
+ * ## WARNING
66
+ * **This function should not be used with untrusted LaTeX source, as it can lead to arbitrary code execution on the system.**
67
+ *
64
68
  * Requires latexmk and LuaTeX to be installed on the system.
65
69
  *
66
70
  * **Minimal recommendation:**
67
71
  * - **Arch Linux:** texlive-binextra texlive-luatex texlive-latexrecommended texlive-fontsrecommended
68
72
  * @param source The LaTeX source code to render
73
+ * @param options Rendering options
69
74
  * @returns A TemporaryFile representing the generated PDF
70
75
  */
71
76
  export async function renderLatex(source, options) {
@@ -73,14 +78,16 @@ export async function renderLatex(source, options) {
73
78
  try {
74
79
  const latexFile = __addDisposableResource(env_1, await TemporaryFile.from(source, '.tex'), true);
75
80
  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}`);
81
+ const { code, output, error } = await spawnWaitReadCommand('string', 'latexmk', ['-interaction=nonstopmode', engineFlag, '-cd', latexFile.path], { throwOnNonZeroExitCode: false });
82
+ await tryIgnoreAsync(async () => await withTimeout(1000, spawnWaitCommand('latexmk', ['-c', '-cd', engineFlag, latexFile.path])));
83
+ if (code !== 0) {
84
+ throw new Error(`
85
+ LaTeX compilation failed with exit code ${code}.\n
86
+ File: ${latexFile.path}\n
87
+ Output:\n${output}\n
88
+ Error Output:\n${error}
89
+ `.trim());
82
90
  }
83
- await tryIgnoreAsync(async () => await spawnWaitCommand('latexmk', ['-interaction=nonstopmode', '-pdflua', '-cd', '-c', latexFile.path]));
84
91
  return TemporaryFile.adopt(`${latexFile.path.slice(0, -4)}.pdf`);
85
92
  }
86
93
  catch (e_1) {
@@ -22,7 +22,7 @@ declare const getCurrentTransactionalContext: {
22
22
  export { getCurrentTransactionalContext, isInTransactionalContext, runInTransactionalContext };
23
23
  export declare abstract class Transactional<ContextData = unknown> {
24
24
  #private;
25
- readonly session: Database | PgTransaction;
25
+ readonly session: PgTransaction | Database;
26
26
  readonly isInTransaction: boolean;
27
27
  constructor();
28
28
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tstdl/base",
3
- "version": "0.93.68",
3
+ "version": "0.93.70",
4
4
  "author": "Patrick Hein",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -142,7 +142,7 @@
142
142
  "peerDependencies": {
143
143
  "@genkit-ai/google-genai": "^1.25",
144
144
  "@google-cloud/storage": "^7.18",
145
- "@google/genai": "^1.32",
145
+ "@google/genai": "^1.33",
146
146
  "@toon-format/toon": "^2.1.0",
147
147
  "@tstdl/angular": "^0.93",
148
148
  "@zxcvbn-ts/core": "^3.0",
@@ -175,9 +175,9 @@
175
175
  "@types/koa__router": "12.0",
176
176
  "@types/luxon": "3.7",
177
177
  "@types/mjml": "4.7",
178
- "@types/node": "24",
178
+ "@types/node": "25",
179
179
  "@types/nodemailer": "7.0",
180
- "@types/pg": "8.15",
180
+ "@types/pg": "8.16",
181
181
  "concurrently": "9.2",
182
182
  "drizzle-kit": "0.31",
183
183
  "eslint": "9.39",
@@ -1,12 +1,18 @@
1
1
  import type { ChildProcessWithoutNullStreams } from 'node:child_process';
2
2
  import type { Record } from '../types/types.js';
3
+ type WaitReadResultFormat = 'string' | 'binary';
4
+ type WaitReadResultFormatType<T extends WaitReadResultFormat> = T extends 'string' ? string : Uint8Array<ArrayBuffer>;
3
5
  export type WaitOptions = {
4
6
  throwOnNonZeroExitCode?: boolean;
5
7
  };
6
- export type ProcessResult = {
8
+ export type WaitResult = {
7
9
  code: number | null;
8
10
  signal: string | null;
9
11
  };
12
+ export type WaitReadResult<Format extends WaitReadResultFormat> = WaitResult & {
13
+ output: WaitReadResultFormatType<Format>;
14
+ error: WaitReadResultFormatType<Format>;
15
+ };
10
16
  export type SpawnOptions = {
11
17
  arguments?: string[];
12
18
  workingDirectory?: string;
@@ -22,11 +28,16 @@ export type SpawnCommandResult = TransformStream<Uint8Array, Uint8Array> & {
22
28
  readErrorBytes(): Promise<Uint8Array>;
23
29
  readError(): Promise<string>;
24
30
  handleNonZeroExitCode(): void;
25
- wait(options?: WaitOptions): Promise<ProcessResult>;
31
+ wait(options?: WaitOptions): Promise<WaitResult>;
32
+ waitRead<F extends WaitReadResultFormat>(format: F, options?: WaitOptions): Promise<WaitReadResult<F>>;
26
33
  };
27
34
  /** 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>;
35
+ export declare function spawnWaitCommand(command: string, args?: string[], options?: SpawnOptions & WaitOptions): Promise<WaitResult>;
36
+ export declare function spawnWaitCommand(command: string, options?: SpawnOptions & WaitOptions): Promise<WaitResult>;
37
+ /** spwans a command, waits for it to complete and reads its output */
38
+ export declare function spawnWaitReadCommand<F extends WaitReadResultFormat>(format: F, command: string, args?: string[], options?: SpawnOptions & WaitOptions): Promise<WaitReadResult<F>>;
39
+ export declare function spawnWaitReadCommand<F extends WaitReadResultFormat>(format: F, command: string, options?: SpawnOptions & WaitOptions): Promise<WaitReadResult<F>>;
30
40
  /** Spawns a command as a child process. */
31
41
  export declare function spawnCommand(command: string, args?: string[], options?: SpawnOptions): Promise<SpawnCommandResult>;
32
42
  export declare function spawnCommand(command: string, options?: SpawnOptions): Promise<SpawnCommandResult>;
43
+ export {};
package/process/spawn.js CHANGED
@@ -9,6 +9,11 @@ export async function spawnWaitCommand(command, argsOrOptions, optionsOrNothing)
9
9
  const process = await spawnCommand(command, args, options);
10
10
  return await process.wait({ throwOnNonZeroExitCode: options?.throwOnNonZeroExitCode });
11
11
  }
12
+ export async function spawnWaitReadCommand(format, command, argsOrOptions, optionsOrNothing) {
13
+ const [args, options] = isArray(argsOrOptions) ? [argsOrOptions, optionsOrNothing] : [undefined, argsOrOptions];
14
+ const process = await spawnCommand(command, args, options);
15
+ return await process.waitRead(format, { throwOnNonZeroExitCode: options?.throwOnNonZeroExitCode });
16
+ }
12
17
  export async function spawnCommand(command, argsOrOptions, optionsOrNothing) {
13
18
  const { spawn } = await dynamicImport('node:child_process');
14
19
  const { Readable, Writable } = await dynamicImport('node:stream');
@@ -81,6 +86,18 @@ export async function spawnCommand(command, argsOrOptions, optionsOrNothing) {
81
86
  }
82
87
  return result;
83
88
  }
89
+ async function waitRead(format, { throwOnNonZeroExitCode = true } = {}) {
90
+ const [result, output, error] = await Promise.all([
91
+ wait({ throwOnNonZeroExitCode }),
92
+ (format === 'string') ? readOutput() : readOutputBytes(),
93
+ (format === 'string') ? readError() : readErrorBytes(),
94
+ ]);
95
+ return {
96
+ ...result,
97
+ output: output,
98
+ error: error,
99
+ };
100
+ }
84
101
  return {
85
102
  process,
86
103
  readable,
@@ -94,5 +111,6 @@ export async function spawnCommand(command, argsOrOptions, optionsOrNothing) {
94
111
  readError,
95
112
  handleNonZeroExitCode: () => void handleNonZeroExitCode(),
96
113
  wait,
114
+ waitRead,
97
115
  };
98
116
  }
@@ -20,7 +20,6 @@ type MessagePortDataMessage<T> = MessagePortMessageBase<'data'> & {
20
20
  type MessagePortMessage<T> = MessagePortOpenChannelMessage | MessagePortCloseChannelMessage | MessagePortDataMessage<T>;
21
21
  export declare class MessagePortRpcChannel<Data = any, Req = any, Res = any> extends RpcChannel<Data, Req, Res> {
22
22
  #private;
23
- private readonly _postMessage;
24
23
  private transport;
25
24
  readonly messagePortMessage$: Observable<MessagePortMessage<RpcChannelMessage<Data, Req, Res>>>;
26
25
  channelMessages$: Observable<RpcChannelMessage<Data, Req, Res>>;
@@ -30,9 +29,9 @@ export declare class MessagePortRpcChannel<Data = any, Req = any, Res = any> ext
30
29
  lastSequence: number;
31
30
  constructor(id: string, transport: MessagePortRpcTransport | undefined, endpoint: MessagePortRpcEndpoint);
32
31
  setTransport(transport: MessagePortRpcTransport): void;
33
- postPortMessage(message: MessagePortMessage<RpcChannelMessage<Data, Req, Res>>, transfer?: any[]): void;
32
+ postPortMessage(message: MessagePortMessage<RpcChannelMessage<Data, Req, Res>>, transferOrOptions?: Transferable[] | NodeWorkerThreads.Transferable[] | NodeWorkerThreads.StructuredSerializeOptions | StructuredSerializeOptions): void;
34
33
  close(): void;
35
- postMessage(message: RpcChannelMessage<Data, Req, Res>, transfer?: any[] | undefined): void | Promise<void>;
34
+ postMessage(message: RpcChannelMessage<Data, Req, Res>, transfer?: Transferable[] | StructuredSerializeOptions): void | Promise<void>;
36
35
  }
37
36
  export declare class MessagePortRpcEndpoint extends RpcEndpoint {
38
37
  #private;
@@ -6,7 +6,6 @@ import { RpcChannel, RpcEndpoint } from '../rpc.endpoint.js';
6
6
  export class MessagePortRpcChannel extends RpcChannel {
7
7
  #transportSubject = new ReplaySubject(1);
8
8
  #closeSubject = new Subject();
9
- _postMessage;
10
9
  transport = { postMessage: deferThrow(() => new Error('Rpc transport not yet initialized.')) };
11
10
  messagePortMessage$;
12
11
  channelMessages$;
@@ -22,9 +21,6 @@ export class MessagePortRpcChannel extends RpcChannel {
22
21
  }
23
22
  this.messagePortMessage$ = this.#transportSubject.pipe(startWith(transport), filter(isDefined), switchMap((newTransport) => fromEvent(newTransport, 'message')), takeUntil(this.#closeSubject), map((message) => ((message instanceof MessageEvent) ? message.data : message)), shareReplay({ bufferSize: 0, refCount: true }));
24
23
  this.channelMessages$ = this.messagePortMessage$.pipe(filter((message) => message.type == 'data'), map((message) => message.data));
25
- this._postMessage = isBrowser
26
- ? (data, transfer) => this.transport.postMessage(data, { transfer })
27
- : (data, transfer) => this.transport.postMessage(data, transfer);
28
24
  if (transport instanceof MessagePort) {
29
25
  transport.start();
30
26
  }
@@ -39,8 +35,8 @@ export class MessagePortRpcChannel extends RpcChannel {
39
35
  this.#transportSubject.next(transport);
40
36
  this.#transportSubject.complete();
41
37
  }
42
- postPortMessage(message, transfer) {
43
- this._postMessage(message, transfer);
38
+ postPortMessage(message, transferOrOptions) {
39
+ this.transport.postMessage(message, transferOrOptions);
44
40
  }
45
41
  close() {
46
42
  this.postPortMessage({ seq: this.sequence++, type: 'close-channel' });
@@ -0,0 +1 @@
1
+ export {};
package/test-schema.js ADDED
@@ -0,0 +1,124 @@
1
+ import z from 'zod';
2
+ import { Application } from './application/application.js';
3
+ import { provideModule, provideSignalHandler } from './application/providers.js';
4
+ import { PrettyPrintLogFormatter, provideConsoleLogTransport } from './logger/index.js';
5
+ import { array, boolean, literal, number, object, optional, string, union } from './schema/index.js';
6
+ import { benchmark } from './utils/benchmark.js';
7
+ function logResult(name, tstdl, zod) {
8
+ const faster = tstdl.millisecondsPerOperation < zod.millisecondsPerOperation ? 'TSTDL' : 'Zod';
9
+ const factor = tstdl.millisecondsPerOperation < zod.millisecondsPerOperation ? zod.millisecondsPerOperation / tstdl.millisecondsPerOperation : tstdl.millisecondsPerOperation / zod.millisecondsPerOperation;
10
+ console.log(`\n--- ${name} ---`);
11
+ console.log(`TSTDL: ${tstdl.operationsPerMillisecond.toFixed(2)} ops/ms`);
12
+ console.log(`Zod: ${zod.operationsPerMillisecond.toFixed(2)} ops/ms`);
13
+ console.log(`Result: ${faster} is ${factor.toFixed(2)}x faster`);
14
+ }
15
+ // --- SETUP SCHEMAS ---
16
+ // 1. Primitive Schema
17
+ const tstdlString = string();
18
+ const zodString = z.string();
19
+ // 2. Simple Object
20
+ const tstdlUser = object({
21
+ name: string(),
22
+ age: number(),
23
+ isActive: boolean(),
24
+ });
25
+ const zodUser = z.object({
26
+ name: z.string(),
27
+ age: z.number(),
28
+ isActive: z.boolean(),
29
+ });
30
+ // 3. Complex Nested Schema
31
+ const tstdlComplex = object({
32
+ id: number(),
33
+ tags: array(string()),
34
+ metadata: optional(object({
35
+ createdAt: number(),
36
+ source: union(literal('web'), literal('mobile')),
37
+ })),
38
+ });
39
+ const zodComplex = z.object({
40
+ id: z.number(),
41
+ tags: z.array(z.string()),
42
+ metadata: z.object({
43
+ createdAt: z.number(),
44
+ source: z.union([z.literal('web'), z.literal('mobile')]),
45
+ }).optional(),
46
+ });
47
+ // 4. Large Array
48
+ const tstdlArray = array(number());
49
+ const zodArray = z.array(z.number());
50
+ // --- DATA ---
51
+ const validString = 'Hello World';
52
+ const validUser = {
53
+ name: 'Alice',
54
+ age: 30,
55
+ isActive: true,
56
+ };
57
+ const validComplex = {
58
+ id: 101,
59
+ tags: ['admin', 'staff', 'verified'],
60
+ metadata: {
61
+ createdAt: 1678900000,
62
+ source: 'web',
63
+ },
64
+ };
65
+ const largeArrayData = Array.from({ length: 1000 }, (_, i) => i);
66
+ const invalidUser = {
67
+ name: 'Bob',
68
+ age: '30', // Invalid: string instead of number
69
+ isActive: true,
70
+ };
71
+ // --- RUN BENCHMARKS ---
72
+ console.log('Starting Benchmarks...');
73
+ // 1. Primitive Parsing
74
+ const ITERATIONS_PRIMITIVE = 1_000_000;
75
+ const zodPrim = benchmark(ITERATIONS_PRIMITIVE, () => zodString.parse(validString));
76
+ const tstdlPrim = benchmark(ITERATIONS_PRIMITIVE, () => tstdlString.parse(validString));
77
+ logResult(`Primitive String Parse (${ITERATIONS_PRIMITIVE} ops)`, tstdlPrim, zodPrim);
78
+ // 2. Simple Object Parsing
79
+ const ITERATIONS_OBJ = 500_000;
80
+ const zodObj = benchmark(ITERATIONS_OBJ, () => zodUser.parse(validUser));
81
+ const tstdlObj = benchmark(ITERATIONS_OBJ, () => tstdlUser.parse(validUser));
82
+ logResult(`Simple Object Parse (${ITERATIONS_OBJ} ops)`, tstdlObj, zodObj);
83
+ // 3. Complex Nested Object Parsing
84
+ const ITERATIONS_COMPLEX = 200_000;
85
+ const zodCmplx = benchmark(ITERATIONS_COMPLEX, () => zodComplex.parse(validComplex));
86
+ const tstdlCmplx = benchmark(ITERATIONS_COMPLEX, () => tstdlComplex.parse(validComplex));
87
+ logResult(`Complex Object Parse (${ITERATIONS_COMPLEX} ops)`, tstdlCmplx, zodCmplx);
88
+ // 4. Large Array Parsing
89
+ const ITERATIONS_ARRAY = 5_000;
90
+ const zodArr = benchmark(ITERATIONS_ARRAY, () => zodArray.parse(largeArrayData));
91
+ const tstdlArr = benchmark(ITERATIONS_ARRAY, () => tstdlArray.parse(largeArrayData));
92
+ logResult(`Large Array (1k items) Parse (${ITERATIONS_ARRAY} ops)`, tstdlArr, zodArr);
93
+ // 5. Error Handling (Exceptions)
94
+ // Note: TSTDL creates stack traces by default, Zod does not.
95
+ const ITERATIONS_ERROR = 100_000;
96
+ const tstdlErr = benchmark(ITERATIONS_ERROR, () => {
97
+ try {
98
+ tstdlUser.parse(invalidUser);
99
+ }
100
+ catch (e) { }
101
+ });
102
+ const zodErr = benchmark(ITERATIONS_ERROR, () => {
103
+ try {
104
+ zodUser.parse(invalidUser);
105
+ }
106
+ catch (e) { }
107
+ });
108
+ logResult(`Error Handling / Throwing (${ITERATIONS_ERROR} ops)`, tstdlErr, zodErr);
109
+ // 6. Error Handling (TSTDL Fast Errors vs Zod)
110
+ // TSTDL has a 'fastErrors' option which might improve performance by skipping stack traces
111
+ const tstdlErrFast = benchmark(ITERATIONS_ERROR, () => {
112
+ try {
113
+ tstdlUser.parse(invalidUser, { fastErrors: true });
114
+ }
115
+ catch (e) { }
116
+ });
117
+ logResult(`Error Handling (TSTDL fastErrors=true) (${ITERATIONS_ERROR} ops)`, tstdlErrFast, zodErr);
118
+ function main() {
119
+ }
120
+ Application.run('Test', [
121
+ provideModule(main),
122
+ provideConsoleLogTransport(PrettyPrintLogFormatter),
123
+ provideSignalHandler(),
124
+ ]);
package/test6.js CHANGED
@@ -1,124 +1,33 @@
1
- import z from 'zod';
2
- import { Application } from './application/application.js';
3
- import { provideModule, provideSignalHandler } from './application/providers.js';
4
- import { PrettyPrintLogFormatter, provideConsoleLogTransport } from './logger/index.js';
5
- import { array, boolean, literal, number, object, optional, string, union } from './schema/index.js';
6
1
  import { benchmark } from './utils/benchmark.js';
7
- function logResult(name, tstdl, zod) {
8
- const faster = tstdl.millisecondsPerOperation < zod.millisecondsPerOperation ? 'TSTDL' : 'Zod';
9
- const factor = tstdl.millisecondsPerOperation < zod.millisecondsPerOperation ? zod.millisecondsPerOperation / tstdl.millisecondsPerOperation : tstdl.millisecondsPerOperation / zod.millisecondsPerOperation;
10
- console.log(`\n--- ${name} ---`);
11
- console.log(`TSTDL: ${tstdl.operationsPerMillisecond.toFixed(2)} ops/ms`);
12
- console.log(`Zod: ${zod.operationsPerMillisecond.toFixed(2)} ops/ms`);
13
- console.log(`Result: ${faster} is ${factor.toFixed(2)}x faster`);
14
- }
15
- // --- SETUP SCHEMAS ---
16
- // 1. Primitive Schema
17
- const tstdlString = string();
18
- const zodString = z.string();
19
- // 2. Simple Object
20
- const tstdlUser = object({
21
- name: string(),
22
- age: number(),
23
- isActive: boolean(),
24
- });
25
- const zodUser = z.object({
26
- name: z.string(),
27
- age: z.number(),
28
- isActive: z.boolean(),
29
- });
30
- // 3. Complex Nested Schema
31
- const tstdlComplex = object({
32
- id: number(),
33
- tags: array(string()),
34
- metadata: optional(object({
35
- createdAt: number(),
36
- source: union(literal('web'), literal('mobile')),
37
- })),
38
- });
39
- const zodComplex = z.object({
40
- id: z.number(),
41
- tags: z.array(z.string()),
42
- metadata: z.object({
43
- createdAt: z.number(),
44
- source: z.union([z.literal('web'), z.literal('mobile')]),
45
- }).optional(),
46
- });
47
- // 4. Large Array
48
- const tstdlArray = array(number());
49
- const zodArray = z.array(z.number());
50
- // --- DATA ---
51
- const validString = 'Hello World';
52
- const validUser = {
53
- name: 'Alice',
54
- age: 30,
55
- isActive: true,
56
- };
57
- const validComplex = {
58
- id: 101,
59
- tags: ['admin', 'staff', 'verified'],
60
- metadata: {
61
- createdAt: 1678900000,
62
- source: 'web',
63
- },
64
- };
65
- const largeArrayData = Array.from({ length: 1000 }, (_, i) => i);
66
- const invalidUser = {
67
- name: 'Bob',
68
- age: '30', // Invalid: string instead of number
69
- isActive: true,
70
- };
71
- // --- RUN BENCHMARKS ---
72
- console.log('Starting Benchmarks...');
73
- // 1. Primitive Parsing
74
- const ITERATIONS_PRIMITIVE = 1_000_000;
75
- const zodPrim = benchmark(ITERATIONS_PRIMITIVE, () => zodString.parse(validString));
76
- const tstdlPrim = benchmark(ITERATIONS_PRIMITIVE, () => tstdlString.parse(validString));
77
- logResult(`Primitive String Parse (${ITERATIONS_PRIMITIVE} ops)`, tstdlPrim, zodPrim);
78
- // 2. Simple Object Parsing
79
- const ITERATIONS_OBJ = 500_000;
80
- const zodObj = benchmark(ITERATIONS_OBJ, () => zodUser.parse(validUser));
81
- const tstdlObj = benchmark(ITERATIONS_OBJ, () => tstdlUser.parse(validUser));
82
- logResult(`Simple Object Parse (${ITERATIONS_OBJ} ops)`, tstdlObj, zodObj);
83
- // 3. Complex Nested Object Parsing
84
- const ITERATIONS_COMPLEX = 200_000;
85
- const zodCmplx = benchmark(ITERATIONS_COMPLEX, () => zodComplex.parse(validComplex));
86
- const tstdlCmplx = benchmark(ITERATIONS_COMPLEX, () => tstdlComplex.parse(validComplex));
87
- logResult(`Complex Object Parse (${ITERATIONS_COMPLEX} ops)`, tstdlCmplx, zodCmplx);
88
- // 4. Large Array Parsing
89
- const ITERATIONS_ARRAY = 5_000;
90
- const zodArr = benchmark(ITERATIONS_ARRAY, () => zodArray.parse(largeArrayData));
91
- const tstdlArr = benchmark(ITERATIONS_ARRAY, () => tstdlArray.parse(largeArrayData));
92
- logResult(`Large Array (1k items) Parse (${ITERATIONS_ARRAY} ops)`, tstdlArr, zodArr);
93
- // 5. Error Handling (Exceptions)
94
- // Note: TSTDL creates stack traces by default, Zod does not.
95
- const ITERATIONS_ERROR = 100_000;
96
- const tstdlErr = benchmark(ITERATIONS_ERROR, () => {
97
- try {
98
- tstdlUser.parse(invalidUser);
99
- }
100
- catch (e) { }
101
- });
102
- const zodErr = benchmark(ITERATIONS_ERROR, () => {
103
- try {
104
- zodUser.parse(invalidUser);
2
+ import { isDefined } from './utils/type-guards.js';
3
+ const numberFormatterMap = new Map();
4
+ function getNumberFormatter1(locale, options) {
5
+ const key = JSON.stringify({ locale, ...options });
6
+ const existing = numberFormatterMap.get(key);
7
+ if (isDefined(existing)) {
8
+ return existing;
105
9
  }
106
- catch (e) { }
107
- });
108
- logResult(`Error Handling / Throwing (${ITERATIONS_ERROR} ops)`, tstdlErr, zodErr);
109
- // 6. Error Handling (TSTDL Fast Errors vs Zod)
110
- // TSTDL has a 'fastErrors' option which might improve performance by skipping stack traces
111
- const tstdlErrFast = benchmark(ITERATIONS_ERROR, () => {
112
- try {
113
- tstdlUser.parse(invalidUser, { fastErrors: true });
10
+ const formatter = new Intl.NumberFormat(locale, options);
11
+ numberFormatterMap.set(key, formatter);
12
+ return formatter;
13
+ }
14
+ function getNumberFormatter2(locale, options) {
15
+ const key = JSON.stringify({ locale, options });
16
+ const existing = numberFormatterMap.get(key);
17
+ if (isDefined(existing)) {
18
+ return existing;
114
19
  }
115
- catch (e) { }
116
- });
117
- logResult(`Error Handling (TSTDL fastErrors=true) (${ITERATIONS_ERROR} ops)`, tstdlErrFast, zodErr);
118
- function main() {
20
+ const formatter = new Intl.NumberFormat(locale, options);
21
+ numberFormatterMap.set(key, formatter);
22
+ return formatter;
23
+ }
24
+ function format1(value, locale, format) {
25
+ return getNumberFormatter1(locale, format).format(value);
26
+ }
27
+ function format2(value, locale, format) {
28
+ return getNumberFormatter2(locale, format).format(value);
119
29
  }
120
- Application.run('Test', [
121
- provideModule(main),
122
- provideConsoleLogTransport(PrettyPrintLogFormatter),
123
- provideSignalHandler(),
124
- ]);
30
+ const result1 = benchmark(200000, (run) => format1(run, 'en-US', { maximumFractionDigits: 2 }));
31
+ const result2 = benchmark(200000, (run) => format2(run, 'en-US', { maximumFractionDigits: 2 }));
32
+ console.log(`format: ${result1.operationsPerMillisecond.toFixed(2)} ops/ms`);
33
+ console.log(`formatCached: ${result2.operationsPerMillisecond.toFixed(2)} ops/ms`);
@@ -1,9 +1,9 @@
1
1
  import type { Logger } from '../logger/logger.js';
2
- export declare function tryIgnore<R>(fn: () => R): R;
2
+ export declare function tryIgnore<R>(fn: () => R): R | undefined;
3
3
  export declare function tryIgnore<R, F>(fn: () => R, fallback: F): R | F;
4
- export declare function tryIgnoreAsync<R>(fn: () => Promise<R>): Promise<R>;
4
+ export declare function tryIgnoreAsync<R>(fn: () => Promise<R>): Promise<R | undefined>;
5
5
  export declare function tryIgnoreAsync<R, F>(fn: () => Promise<R>, fallback: F): Promise<F>;
6
- export declare function tryIgnoreLog<R>(logger: Logger, fn: () => R): R;
6
+ export declare function tryIgnoreLog<R>(logger: Logger, fn: () => R): R | undefined;
7
7
  export declare function tryIgnoreLog<R, F>(logger: Logger, fn: () => R, fallback: F): R | F;
8
- export declare function tryIgnoreLogAsync<R>(logger: Logger, fn: () => Promise<R>): Promise<R>;
8
+ export declare function tryIgnoreLogAsync<R>(logger: Logger, fn: () => Promise<R>): Promise<R | undefined>;
9
9
  export declare function tryIgnoreLogAsync<R, F>(logger: Logger, fn: () => Promise<R>, fallback: F): Promise<F>;