@oh-my-pi/pi-utils 18.4.8 → 18.4.10
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/CHANGELOG.md +28 -0
- package/dist/types/async.d.ts +6 -1
- package/dist/types/file-lock.d.ts +2 -0
- package/dist/types/logger/rotating-file.d.ts +36 -6
- package/dist/types/logger.d.ts +9 -0
- package/dist/types/marked/core.d.ts +24 -0
- package/dist/types/marked/inline-closers.d.ts +119 -0
- package/dist/types/marked/inline-text.d.ts +70 -0
- package/dist/types/math-delimiters.d.ts +22 -0
- package/dist/types/postmortem.d.ts +4 -0
- package/package.json +2 -2
- package/src/async.ts +11 -1
- package/src/file-lock.ts +8 -1
- package/src/logger/rotating-file.ts +105 -81
- package/src/logger.ts +103 -29
- package/src/marked/core.ts +234 -166
- package/src/marked/inline-closers.ts +434 -0
- package/src/marked/inline-text.ts +321 -0
- package/src/math-delimiters.ts +121 -19
- package/src/postmortem.ts +5 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.4.10] - 2026-10-02
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added `startFrom(src, from)` to inline Markdown tokenizer extensions: a start hint that returns the first match at or after `from` (or `undefined`), so long paragraphs stay linear ([#13961](https://github.com/can1357/oh-my-pi/pull/13961) by [@sjawhar](https://github.com/sjawhar)).
|
|
10
|
+
- Added `this.source` and `this.end` for inline Markdown tokenizer extensions: the whole inline source and where the text being lexed ends in it, with one `this` per source that the link labels and emphasis inside it share, so a tokenizer can remember what it already scanned ([#13961](https://github.com/can1357/oh-my-pi/pull/13961) by [@sjawhar](https://github.com/sjawhar)).
|
|
11
|
+
- Added `mathSpanInContext` and `MathSpans` to `math-delimiters`, which find math spans without rescanning a run of unclosed openers ([#13961](https://github.com/can1357/oh-my-pi/pull/13961) by [@sjawhar](https://github.com/sjawhar)).
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- Fixed long Markdown paragraphs lexing slowly: a 44 KB paragraph with no blank line now parses in about 3 ms instead of 100 ms ([#13961](https://github.com/can1357/oh-my-pi/pull/13961) by [@sjawhar](https://github.com/sjawhar)).
|
|
16
|
+
- Fixed Markdown paragraphs with many unclosed `[`, `*` or `_`, or with a long address-like word and no dotted domain, lexing slowly: a 40 KB paragraph of each now lexes in 4-24 ms instead of 2-12 s ([#13961](https://github.com/can1357/oh-my-pi/pull/13961) by [@sjawhar](https://github.com/sjawhar)).
|
|
17
|
+
- Fixed Markdown paragraphs of deeply nested emphasis, links or images lexing slowly: 32 KB now lexes in about 50 ms instead of 7 s, and a long word inside every level no longer costs its length once per level, except in nested image labels that hold a backslash escape ([#13961](https://github.com/can1357/oh-my-pi/pull/13961) by [@sjawhar](https://github.com/sjawhar)).
|
|
18
|
+
- Fixed Markdown paragraphs with long or many unclosed runs of backticks, or `<http://` autolinks with no space or `>` after them, lexing slowly: 80 KB of each now lexes in about 50-60 ms instead of seconds (40 KB of one unclosed run took 10 s) ([#13961](https://github.com/can1357/oh-my-pi/pull/13961) by [@sjawhar](https://github.com/sjawhar)).
|
|
19
|
+
- Fixed Markdown paragraphs of nested brackets, URLs with long trailing punctuation, or unclosed HTML tags or comments lexing slowly: 80 KB of each now lexes in under 40 ms instead of 4-30 s ([#13961](https://github.com/can1357/oh-my-pi/pull/13961) by [@sjawhar](https://github.com/sjawhar)).
|
|
20
|
+
- Fixed deeply nested Markdown links and emphasis overflowing the stack early: in a fresh process links now nest about three times as deep before a stack overflow, and emphasis twice as deep ([#13961](https://github.com/can1357/oh-my-pi/pull/13961) by [@sjawhar](https://github.com/sjawhar)).
|
|
21
|
+
|
|
22
|
+
## [18.4.9] - 2026-10-01
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- Added `tryAcquireFileLock`, a non-blocking file-lock helper that returns `null` when the lock is already held.
|
|
27
|
+
- Added an `unref` option to `AsyncDrain`, allowing applications to use long batch windows without keeping the process alive.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- Improved logging efficiency and configurability by batching routine file writes, flushing urgent records promptly, adding on-demand `logger.flush()` support, and allowing file log levels to be limited with `OMP_LOG_LEVEL`. Log files are created only when needed, and obsolete log and audit files are cleaned up automatically.
|
|
32
|
+
|
|
5
33
|
## [18.4.4] - 2026-09-29
|
|
6
34
|
|
|
7
35
|
### Added
|
package/dist/types/async.d.ts
CHANGED
|
@@ -28,11 +28,16 @@ export declare function withTimeout<T>(promise: Promise<T>, ms: number, timeout:
|
|
|
28
28
|
* timer (`delayMs`, or a microtask at 0), and every push before it fires joins
|
|
29
29
|
* the same batch and shares the same promise. Used to keep hot paths off
|
|
30
30
|
* synchronous storage (prompt history, model perf).
|
|
31
|
+
*
|
|
32
|
+
* `unref` lets a long batch window not hold the process open; the owner must
|
|
33
|
+
* then {@link flush} on shutdown or the pending batch is lost.
|
|
31
34
|
*/
|
|
32
35
|
export declare class AsyncDrain<T> {
|
|
33
36
|
#private;
|
|
34
37
|
readonly delayMs: number;
|
|
35
|
-
constructor(delayMs?: number
|
|
38
|
+
constructor(delayMs?: number, options?: {
|
|
39
|
+
unref?: boolean;
|
|
40
|
+
});
|
|
36
41
|
/** Queue `value`; `hnd` receives the whole batch when the window closes. */
|
|
37
42
|
push(value: T, hnd: (values: T[]) => Promise<void> | void): Promise<void>;
|
|
38
43
|
/** Runs the pending batch handler immediately and returns its completion promise. */
|
|
@@ -14,6 +14,8 @@ export interface FileLockHandle {
|
|
|
14
14
|
}
|
|
15
15
|
declare function getLockPath(filePath: string): string;
|
|
16
16
|
declare function tryAcquireLock(lockPath: string): NativeFileLock | null;
|
|
17
|
+
/** Acquire an exclusive lease without waiting; `null` while another holder owns it. */
|
|
18
|
+
export declare function tryAcquireFileLock(filePath: string): FileLockHandle | null;
|
|
17
19
|
/** Acquire an exclusive lease; callers must release it when their operation ends. */
|
|
18
20
|
export declare function acquireFileLock(filePath: string, options?: FileLockOptions): Promise<FileLockHandle>;
|
|
19
21
|
/** Run `fn` while holding an OS-backed exclusive lock for `filePath`. */
|
|
@@ -1,14 +1,25 @@
|
|
|
1
|
+
/** Write batching for {@link RotatingFileSink}. */
|
|
2
|
+
export interface RotatingFileBatchOptions {
|
|
3
|
+
/** Longest a buffered record waits before an unref'd timer writes it. */
|
|
4
|
+
readonly intervalMs: number;
|
|
5
|
+
/** Buffered UTF-8 bytes at which the batch is written immediately. */
|
|
6
|
+
readonly maxBytes: number;
|
|
7
|
+
}
|
|
1
8
|
/** Configuration for a process-local rotating file sink. */
|
|
2
9
|
export interface RotatingFileOptions {
|
|
3
10
|
readonly directory: string;
|
|
4
11
|
readonly filenamePrefix: string;
|
|
5
12
|
readonly filenameSuffix: string;
|
|
6
|
-
readonly auditFile: string;
|
|
7
13
|
readonly maxBytes: number;
|
|
8
14
|
readonly maxFiles: number;
|
|
15
|
+
/**
|
|
16
|
+
* Buffer records and write them together instead of one write per record.
|
|
17
|
+
* Without it every record is written as it arrives.
|
|
18
|
+
*/
|
|
19
|
+
readonly batch?: RotatingFileBatchOptions;
|
|
9
20
|
/**
|
|
10
21
|
* Called with the new active file path whenever the sink opens a different
|
|
11
|
-
* file —
|
|
22
|
+
* file — first write, local-day rotation, and size rotation alike.
|
|
12
23
|
*
|
|
13
24
|
* Consumers that hold their own descriptor on the active log (the macOS
|
|
14
25
|
* stderr guard dup2s it onto fd 2) use this to follow the sink instead of
|
|
@@ -16,12 +27,31 @@ export interface RotatingFileOptions {
|
|
|
16
27
|
*/
|
|
17
28
|
readonly onRotate?: (filePath: string) => void;
|
|
18
29
|
}
|
|
19
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* Synchronous append sink with local-day and size rotation plus bounded retention.
|
|
32
|
+
*
|
|
33
|
+
* Construction performs no filesystem I/O: the first file is created when the
|
|
34
|
+
* first record is written out, so a process that never logs leaves nothing on
|
|
35
|
+
* disk. Every file name embeds the owning process's suffix (its PID), so the
|
|
36
|
+
* set of files this sink created is tracked in memory; files left by earlier
|
|
37
|
+
* processes are pruned by name pattern in the logger, not by this sink.
|
|
38
|
+
*
|
|
39
|
+
* With {@link RotatingFileOptions.batch}, records are buffered and written in
|
|
40
|
+
* one `write` per batch. Buffered bytes already count toward the active file's
|
|
41
|
+
* size, and the buffer is written to its file before any rotation, so file
|
|
42
|
+
* selection matches an unbatched sink record for record.
|
|
43
|
+
*/
|
|
20
44
|
export declare class RotatingFileSink {
|
|
21
45
|
#private;
|
|
22
46
|
constructor(options: RotatingFileOptions);
|
|
23
|
-
/**
|
|
24
|
-
|
|
25
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Append one already-formatted log record. A batching sink buffers it unless
|
|
49
|
+
* `flushNow` is set or the buffer reached its size threshold; either way the
|
|
50
|
+
* whole buffer, this record included, is then written in order.
|
|
51
|
+
*/
|
|
52
|
+
write(line: string, flushNow?: boolean): void;
|
|
53
|
+
/** Write every buffered record to the active file now. */
|
|
54
|
+
flush(): void;
|
|
55
|
+
/** Write buffered records, then stop accepting records. */
|
|
26
56
|
close(): void;
|
|
27
57
|
}
|
package/dist/types/logger.d.ts
CHANGED
|
@@ -11,10 +11,19 @@ export interface LogEvent {
|
|
|
11
11
|
export type LogSink = (event: LogEvent) => void;
|
|
12
12
|
/** Register an out-of-band log sink and return a disposer. */
|
|
13
13
|
export declare function registerLogSink(sink: LogSink): () => void;
|
|
14
|
+
/**
|
|
15
|
+
* Write buffered file records to disk now. The file transport batches records
|
|
16
|
+
* (one write per second or per 64 KiB) and writes `warn`/`error` records
|
|
17
|
+
* immediately; process exit flushes on its own. Call this before handing the
|
|
18
|
+
* log file to something that reads it in-process. Never throws.
|
|
19
|
+
*/
|
|
20
|
+
export declare function flush(): void;
|
|
14
21
|
/**
|
|
15
22
|
* Replace the active log transports. Pass `console: true, file: false` for
|
|
16
23
|
* long-running services (the auth broker, etc.) that want their structured
|
|
17
24
|
* logs piped into a process supervisor instead of the rotating file.
|
|
25
|
+
* The previous file transport writes its buffered records before closing, and
|
|
26
|
+
* the file level (`OMP_LOG_LEVEL`) is re-read on each reconfiguration.
|
|
18
27
|
*/
|
|
19
28
|
export declare function setTransports(opts: {
|
|
20
29
|
console?: boolean;
|
|
@@ -184,16 +184,40 @@ export type TokensList = Token[] & {
|
|
|
184
184
|
/** Context supplied to extension tokenizers. */
|
|
185
185
|
export interface TokenizerThis {
|
|
186
186
|
lexer: Lexer;
|
|
187
|
+
/**
|
|
188
|
+
* For an inline tokenizer, the whole inline source, such as a paragraph's text. The `src` it receives is the part
|
|
189
|
+
* of it before `end` that starts at `end - src.length`: inside a link label or the text of emphasis, `end` is where
|
|
190
|
+
* that text ends, right before its closer, and a token there must not reach past it. Inline tokenizers get one
|
|
191
|
+
* context object per inline source, the same for every call while the lexer works through it and the labels and
|
|
192
|
+
* emphasis inside it, so a tokenizer can keep state for it in a `WeakMap`.
|
|
193
|
+
*/
|
|
194
|
+
source?: string;
|
|
195
|
+
/** For an inline tokenizer, where the text the lexer works through ends in `source` (see `source`). */
|
|
196
|
+
end?: number;
|
|
187
197
|
}
|
|
188
198
|
/** A tokenizer extension callback. */
|
|
189
199
|
export type TokenizerExtensionFunction = (this: TokenizerThis, src: string, tokens: Token[] | TokensList) => Tokens.Generic | undefined;
|
|
190
200
|
/** A tokenizer extension start hint. */
|
|
191
201
|
export type TokenizerStartFunction = (this: TokenizerThis, src: string) => number | void;
|
|
202
|
+
/**
|
|
203
|
+
* An offset form of a start hint: the first index at or after `from` in `src` where the extension's tokenizer could
|
|
204
|
+
* match, or `undefined` (not -1) when there is none; anything else below `from` throws. The test at each index must
|
|
205
|
+
* read `src` only from that index on, so the answer for `from` is also the answer for every later offset up to it,
|
|
206
|
+
* and must still pass where `src` goes on past its end: for a link label or the text of emphasis, the lexer asks on
|
|
207
|
+
* the inline source it lies in, then asks again on the label or text at the index it got, if that lies inside.
|
|
208
|
+
*/
|
|
209
|
+
export type TokenizerStartFromFunction = (this: TokenizerThis, src: string, from: number) => number | undefined;
|
|
192
210
|
/** An inline or block tokenizer extension. */
|
|
193
211
|
export interface TokenizerExtension {
|
|
194
212
|
name: string;
|
|
195
213
|
level: "block" | "inline";
|
|
196
214
|
start?: TokenizerStartFunction;
|
|
215
|
+
/**
|
|
216
|
+
* Replaces `start` for inline text when present. Inline lexing calls it on the whole inline source and reuses
|
|
217
|
+
* the answer until lexing passes it, inside link labels and emphasis too, instead of calling `start` on the rest
|
|
218
|
+
* of the source at every text step, so a hint that stops at its answer keeps a long paragraph linear.
|
|
219
|
+
*/
|
|
220
|
+
startFrom?: TokenizerStartFromFunction;
|
|
197
221
|
tokenizer: TokenizerExtensionFunction;
|
|
198
222
|
childTokens?: string[];
|
|
199
223
|
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where brackets, emphasis, code spans, autolinks and inline HTML close in inline Markdown. The indexes are built
|
|
3
|
+
* over one root inline source, each on first use, and shared by every source lexed inside it (a link label, the text
|
|
4
|
+
* of emphasis), so lexing nested sources builds no index of its own and keeps none alive per nesting level.
|
|
5
|
+
*/
|
|
6
|
+
export declare const PUNCTUATION: RegExp;
|
|
7
|
+
/** Whether an odd run of backslashes right before `index` escapes the character there. */
|
|
8
|
+
export declare function escapedAt(src: string, index: number): boolean;
|
|
9
|
+
export declare function canOpenDelimiter(src: string, index: number, width: number, marker: string, previous?: string): boolean;
|
|
10
|
+
export declare function canCloseDelimiter(src: string, index: number, marker: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Where a bracket opened in the root source closes. A search starts right after the opening bracket, and a bracket
|
|
13
|
+
* never begins an escape, so the escape pairing from there is the pairing from the start of the root source. The
|
|
14
|
+
* closer is then the first unescaped closing bracket at or after the start whose depth (openers minus closers
|
|
15
|
+
* before it, from the start of the root source) equals the depth at the start.
|
|
16
|
+
*/
|
|
17
|
+
declare class BracketDepths {
|
|
18
|
+
#private;
|
|
19
|
+
constructor(src: string, open: number, close: number);
|
|
20
|
+
/** The offset of the bracket closing the one just before `start`, or -1. */
|
|
21
|
+
closeAfter(start: number): number;
|
|
22
|
+
/** Whether an unescaped closing bracket lies at or after `from`, right after a bracket, and before `to`. */
|
|
23
|
+
closesWithin(from: number, to: number): boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The delimiters the emphasis rule's closer walk visits for one marker and width in the root source, typed the way
|
|
27
|
+
* the walk types them: a delimiter that can close is a closer, else one that can open is an opener. For width 1 they
|
|
28
|
+
* are every unescaped marker. For width 2 they are marker pairs taken two at a time from the start of each run of
|
|
29
|
+
* markers (after it when an odd run of backslashes escapes the start), which is where a walk that reaches the run
|
|
30
|
+
* from before it enters. The closer for an opener is then the first closer after it whose depth (openers minus
|
|
31
|
+
* closers before it) equals the depth where its walk begins, the first point where the walk's nesting count would
|
|
32
|
+
* drop below zero.
|
|
33
|
+
*/
|
|
34
|
+
declare class EmphasisDelimiters {
|
|
35
|
+
#private;
|
|
36
|
+
constructor(src: string, marker: string, width: number);
|
|
37
|
+
/**
|
|
38
|
+
* Where the emphasis opened by the delimiter at `opener` closes in a source that ends at `end`, or -1 when the
|
|
39
|
+
* closer the walk meets first does not lie wholly before `end`. The walk reads nothing before its start. Past
|
|
40
|
+
* `end` it reads only for the source's last delimiter, which cannot open there and closes as it would at the end
|
|
41
|
+
* of the source: the sources inline lexing nests end right before a marker or "]", and the closing test reads
|
|
42
|
+
* either punctuation there like the end.
|
|
43
|
+
*/
|
|
44
|
+
closeFor(opener: number, end: number): number;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The runs of backticks in the root source, for the code span rule. A span opened by `width` backticks closes at the
|
|
48
|
+
* first `width` backticks after its opening run that no backslash escapes: the start of a later run of at least
|
|
49
|
+
* `width`, or, in a run whose first backtick a backslash escapes, the backticks from its `width`th on, since the
|
|
50
|
+
* search for a closer goes on `width` past an escaped one.
|
|
51
|
+
*/
|
|
52
|
+
declare class CodeRuns {
|
|
53
|
+
#private;
|
|
54
|
+
constructor(src: string);
|
|
55
|
+
/** Where the run of backticks holding the backtick at `at` ends. */
|
|
56
|
+
endOf(at: number): number;
|
|
57
|
+
/** Where the closer of a code span opened by `width` backticks that end at `from` starts, or -1. */
|
|
58
|
+
closerAfter(from: number, width: number): number;
|
|
59
|
+
}
|
|
60
|
+
/** The closer indexes of one root inline source, each built on first use. */
|
|
61
|
+
export declare class CloserIndexes {
|
|
62
|
+
#private;
|
|
63
|
+
constructor(src: string);
|
|
64
|
+
get square(): BracketDepths;
|
|
65
|
+
get round(): BracketDepths;
|
|
66
|
+
emphasis(marker: string, width: number): EmphasisDelimiters;
|
|
67
|
+
/**
|
|
68
|
+
* For each offset, where the HTML rule's scan for a tag's end from there stops: the first ">" outside quotes,
|
|
69
|
+
* where a quote runs to the next quote of its kind, or -1 at the end of the source.
|
|
70
|
+
*/
|
|
71
|
+
get tagEnds(): Int32Array;
|
|
72
|
+
/** Every offset where "-->" starts, ascending. */
|
|
73
|
+
get commentEnds(): number[];
|
|
74
|
+
get codeRuns(): CodeRuns;
|
|
75
|
+
/**
|
|
76
|
+
* The first " " or ">" at or after `from`, or `Infinity`. Asked at offsets that never decrease, as the lexer's
|
|
77
|
+
* position does, so each search starts past the last one's answer.
|
|
78
|
+
*/
|
|
79
|
+
spaceOrAngleFrom(from: number): number;
|
|
80
|
+
/** Every offset of a backslash before ASCII punctuation, ascending: where unescaping drops a backslash. */
|
|
81
|
+
get escapes(): number[];
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The link, emphasis, code span, autolink and inline HTML rules' closer lookups for one inline source, which is the
|
|
85
|
+
* root source of `indexes` or a part of it that ends at `end`. Offsets are relative to `rest`, a suffix of the
|
|
86
|
+
* source. A closer the root source places at or past `end` does not exist in the source.
|
|
87
|
+
*/
|
|
88
|
+
export declare class InlineClosers {
|
|
89
|
+
#private;
|
|
90
|
+
/** Where the source ends in the root source. */
|
|
91
|
+
readonly end: number;
|
|
92
|
+
constructor(indexes: CloserIndexes, end: number);
|
|
93
|
+
/** The closers of a part of `rest` that ends at `end`, lexed as its own source. */
|
|
94
|
+
nested(rest: string, end: number): InlineClosers;
|
|
95
|
+
/** Where the "[" just before `start` closes, or -1. */
|
|
96
|
+
closeSquare(rest: string, start: number): number;
|
|
97
|
+
/** Where the "(" just before `start` closes, or -1. */
|
|
98
|
+
closeRound(rest: string, start: number): number;
|
|
99
|
+
/** Whether `rest.slice(from, to)`, which starts right after a "[", holds an unescaped "]". */
|
|
100
|
+
squareCloserWithin(rest: string, from: number, to: number): boolean;
|
|
101
|
+
/** Whether `rest.slice(from, to)` holds a backslash before ASCII punctuation, which unescaping drops. */
|
|
102
|
+
escapeWithin(rest: string, from: number, to: number): boolean;
|
|
103
|
+
/** Where the emphasis opened by the `width` markers at the start of `rest` closes, or -1. */
|
|
104
|
+
closeEmphasis(rest: string, marker: string, width: number): number;
|
|
105
|
+
/** How many backticks start `rest`, which starts with one. */
|
|
106
|
+
codeOpenerWidth(rest: string): number;
|
|
107
|
+
/** Where the code span opened by the `width` backticks at the start of `rest` closes, or -1. */
|
|
108
|
+
closeCode(rest: string, width: number): number;
|
|
109
|
+
/**
|
|
110
|
+
* Where the URL autolink opened at the start of `rest` ends, its scheme ending at `from`: the first " " or ">"
|
|
111
|
+
* after the scheme if it is a ">" with text before it, or -1. One past the end of `rest` reads as no ">".
|
|
112
|
+
*/
|
|
113
|
+
closeUrlAutolink(rest: string, from: number): number;
|
|
114
|
+
/** Where the HTML tag opened by the "<" at the start of `rest` ends: the first ">" after it outside quotes, or -1. */
|
|
115
|
+
closeTag(rest: string): number;
|
|
116
|
+
/** Where the first "-->" after the "<!--" at the start of `rest` starts, or -1. */
|
|
117
|
+
closeComment(rest: string): number;
|
|
118
|
+
}
|
|
119
|
+
export {};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The plain-text step of inline lexing over one inline source: where text can end ({@link TextStops}, which also
|
|
3
|
+
* tells the bare-URL rule where it can match) and how it is appended ({@link TextRun}).
|
|
4
|
+
*/
|
|
5
|
+
import type { Lexer, Token, TokenizerExtension, TokenizerStartFromFunction } from "./core.js";
|
|
6
|
+
/**
|
|
7
|
+
* The places where plain text can end in one root inline source, as offsets into it, shared by every source lexed
|
|
8
|
+
* inside it (a link label, the text of emphasis). Each stop is the first offset at or after a search offset where a
|
|
9
|
+
* test passes, and each test reads the source only from its own offset on, so a found stop stays the answer for every
|
|
10
|
+
* later search offset up to it. A stop is searched for again only after lexing passes it, starting past it, so all
|
|
11
|
+
* the text steps of a paragraph, at every nesting level, read it a bounded number of times instead of once per step.
|
|
12
|
+
* Search offsets never decrease: the lexer asks at its position for the bare-URL rule and one past it for the text
|
|
13
|
+
* step, its position only grows, and a nested source is lexed from its start to its end before the lexer goes on
|
|
14
|
+
* past it.
|
|
15
|
+
*
|
|
16
|
+
* A nested source is the part of the root source before `end`, and a stop that holds there is a stop of the root
|
|
17
|
+
* source whose match ends by `end`. The first root stop after an offset tells whether there is one: when its match
|
|
18
|
+
* runs past `end`, no stop that ends by `end` follows it, for the reason each search gives.
|
|
19
|
+
*/
|
|
20
|
+
declare class RootTextStops {
|
|
21
|
+
#private;
|
|
22
|
+
constructor(src: string);
|
|
23
|
+
/** The first character that can start another token at or after `from`. */
|
|
24
|
+
tokenCharFrom(from: number): number;
|
|
25
|
+
/** The first bare-URL scheme at or after `from` that ends by `end`. */
|
|
26
|
+
schemeFrom(from: number, end: number): number;
|
|
27
|
+
/** The "@" of the run {@link mailFrom} last found. */
|
|
28
|
+
get mailAt(): number;
|
|
29
|
+
/** The first offset at or after `from` that starts a match of `/[A-Za-z0-9._+-]+@/` whose "@" is before `end`. */
|
|
30
|
+
mailFrom(from: number, end: number): number;
|
|
31
|
+
/** The first offset at or after `from` that starts a match of `/(?: {2,}|\\)\n/` whose "\n" is before `end`. */
|
|
32
|
+
hardBreakFrom(from: number, end: number): number;
|
|
33
|
+
/** Where `extension`'s `startFrom` hint points at or after `from`, asked again only once lexing passes it. */
|
|
34
|
+
startFrom(extension: TokenizerExtension, startFrom: TokenizerStartFromFunction, lexer: Lexer, from: number): number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The places where one inline source's plain text can end, as offsets into that source: its root source's stops
|
|
38
|
+
* that hold before the source's end. The source is a root source, or a part of one lexed as its own source (a link
|
|
39
|
+
* label, the text of emphasis), which ends right before the delimiter that closes it.
|
|
40
|
+
*/
|
|
41
|
+
export declare class TextStops {
|
|
42
|
+
#private;
|
|
43
|
+
/** The stops of `src`, a root source, or else the part of `root`'s source that ends at `end`. */
|
|
44
|
+
constructor(src: string, lexer: Lexer, root?: RootTextStops, end?: number);
|
|
45
|
+
/** The stops of `src`, the part of this source's root source that ends at `end`, lexed as its own source. */
|
|
46
|
+
nested(src: string, end: number): TextStops;
|
|
47
|
+
/**
|
|
48
|
+
* Whether the bare-URL rule can match at `pos`: its first alternative needs a scheme there, and its second a
|
|
49
|
+
* run of e-mail local characters from `pos` to an "@" (the only "@" such a run can reach) with a dotted domain
|
|
50
|
+
* right after that "@", which is the same test for every offset of the run.
|
|
51
|
+
*/
|
|
52
|
+
bareUrlCanStart(pos: number): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Length of the plain text at the start of `rest`, a suffix of the source: the distance to the nearest later
|
|
55
|
+
* offset where another token could start or an inline extension's start hint points.
|
|
56
|
+
*/
|
|
57
|
+
textLength(rest: string): number;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Appends plain text to one inline source's token list, merging into a text token before it as marked does. A text
|
|
61
|
+
* token this run created grows by taking a longer slice of the source instead of by concatenation, so its raw stays
|
|
62
|
+
* a flat string: reading its last character (the emphasis rule's look-behind) does not copy the whole token.
|
|
63
|
+
*/
|
|
64
|
+
export declare class TextRun {
|
|
65
|
+
#private;
|
|
66
|
+
constructor(src: string);
|
|
67
|
+
/** Appends the source text from `start` to `end`. */
|
|
68
|
+
append(tokens: Token[], start: number, end: number): void;
|
|
69
|
+
}
|
|
70
|
+
export {};
|
|
@@ -41,5 +41,27 @@ export declare function mathOpenerAt(source: string, at: number): MathOpener | u
|
|
|
41
41
|
* though a backslash precedes it.
|
|
42
42
|
*/
|
|
43
43
|
export declare function mathSpanAt(source: string, at: number, from?: number): MathSpan | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* The math spans of one source, found as {@link mathSpanAt} finds them with the escape scan stopping at the opener
|
|
46
|
+
* (the caller has consumed the escapes before it). A closer scan also answers for the openers of its kind that it
|
|
47
|
+
* passed, which meet the same closer or none, so asking at every opener of a long run reads the run once.
|
|
48
|
+
*/
|
|
49
|
+
export declare class MathSpans {
|
|
50
|
+
#private;
|
|
51
|
+
constructor(source: string);
|
|
52
|
+
/** The span opened at `at` that ends by `end`, or `undefined`. */
|
|
53
|
+
spanAt(at: number, end?: number): MathSpan | undefined;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The span opened at the start of `src`, with `end` counted from there, for a marked inline tokenizer: `src` is the
|
|
57
|
+
* part of `context.source` before `context.end` that the tokenizer received, and the scans are remembered per
|
|
58
|
+
* context, which marked keeps for one inline source and the link labels and emphasis inside it. Only a span that ends
|
|
59
|
+
* by `context.end` counts. Inside a label or emphasis, `context.end` holds its closer (`]`, `*` or `_`), which is not
|
|
60
|
+
* a digit, so a `$` right before it closes there as it would at the end of the text.
|
|
61
|
+
*/
|
|
62
|
+
export declare function mathSpanInContext(context: {
|
|
63
|
+
source?: string;
|
|
64
|
+
end?: number;
|
|
65
|
+
}, src: string): MathSpan | undefined;
|
|
44
66
|
/** The own-line display block starting at offset 0, or `undefined`. */
|
|
45
67
|
export declare function mathBlockAt(source: string): MathBlock | undefined;
|
|
@@ -46,6 +46,10 @@ export declare const NATIVE_PROCESS_EXIT: unique symbol;
|
|
|
46
46
|
* guard and loops the rejection storm (#11789). After restoring, `reallyExit`
|
|
47
47
|
* (the low-level primitive) is preferred; `process.exit` and finally `SIGKILL`
|
|
48
48
|
* are fallbacks so a poisoned or absent chain can never leave the process alive.
|
|
49
|
+
*
|
|
50
|
+
* Buffered log records are written first: `reallyExit` skips the `exit` event
|
|
51
|
+
* the logger otherwise flushes on, which would drop the last batch on every
|
|
52
|
+
* signal and fatal exit.
|
|
49
53
|
*/
|
|
50
54
|
export declare function exitProcess(code: number): never;
|
|
51
55
|
/** User-facing command printed before fatal cleanup so interrupted work can be resumed. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-utils",
|
|
3
|
-
"version": "18.4.
|
|
3
|
+
"version": "18.4.10",
|
|
4
4
|
"description": "Shared utilities for pi packages",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@oh-my-pi/pi-natives": "18.4.
|
|
60
|
+
"@oh-my-pi/pi-natives": "18.4.10"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@types/bun": "^1.3.14"
|
package/src/async.ts
CHANGED
|
@@ -91,13 +91,22 @@ export function withTimeout<T>(
|
|
|
91
91
|
* timer (`delayMs`, or a microtask at 0), and every push before it fires joins
|
|
92
92
|
* the same batch and shares the same promise. Used to keep hot paths off
|
|
93
93
|
* synchronous storage (prompt history, model perf).
|
|
94
|
+
*
|
|
95
|
+
* `unref` lets a long batch window not hold the process open; the owner must
|
|
96
|
+
* then {@link flush} on shutdown or the pending batch is lost.
|
|
94
97
|
*/
|
|
95
98
|
export class AsyncDrain<T> {
|
|
96
99
|
#queue?: T[];
|
|
97
100
|
#promise = Promise.resolve();
|
|
98
101
|
#flush?: () => void;
|
|
102
|
+
readonly #unref: boolean;
|
|
99
103
|
|
|
100
|
-
constructor(
|
|
104
|
+
constructor(
|
|
105
|
+
readonly delayMs: number = 0,
|
|
106
|
+
options?: { unref?: boolean },
|
|
107
|
+
) {
|
|
108
|
+
this.#unref = options?.unref === true;
|
|
109
|
+
}
|
|
101
110
|
|
|
102
111
|
/** Queue `value`; `hnd` receives the whole batch when the window closes. */
|
|
103
112
|
push(value: T, hnd: (values: T[]) => Promise<void> | void): Promise<void> {
|
|
@@ -119,6 +128,7 @@ export class AsyncDrain<T> {
|
|
|
119
128
|
};
|
|
120
129
|
if (this.delayMs > 0) {
|
|
121
130
|
const timer = setTimeout(exec, this.delayMs);
|
|
131
|
+
if (this.#unref) timer.unref();
|
|
122
132
|
this.#flush = () => {
|
|
123
133
|
clearTimeout(timer);
|
|
124
134
|
exec();
|
package/src/file-lock.ts
CHANGED
|
@@ -34,7 +34,14 @@ function getLockPath(filePath: string): string {
|
|
|
34
34
|
|
|
35
35
|
function tryAcquireLock(lockPath: string): NativeFileLock | null {
|
|
36
36
|
const lock = NativeFileLock.tryAcquire(lockPath);
|
|
37
|
-
|
|
37
|
+
if (lock.acquired) return lock;
|
|
38
|
+
lock.release();
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Acquire an exclusive lease without waiting; `null` while another holder owns it. */
|
|
43
|
+
export function tryAcquireFileLock(filePath: string): FileLockHandle | null {
|
|
44
|
+
return tryAcquireLock(getLockPath(filePath));
|
|
38
45
|
}
|
|
39
46
|
|
|
40
47
|
/** Acquire an exclusive lease; callers must release it when their operation ends. */
|