@oh-my-pi/pi-utils 18.1.5 → 18.1.6
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 +11 -0
- package/dist/types/async.d.ts +10 -0
- package/dist/types/incoming-json.d.ts +232 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/json-lexer.d.ts +116 -0
- package/package.json +2 -2
- package/src/async.ts +16 -0
- package/src/incoming-json.ts +854 -0
- package/src/index.ts +1 -0
- package/src/json-lexer.ts +413 -0
- package/src/json-parse.ts +93 -350
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.1.6] - 2026-09-03
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added `IncomingDoc` (`@oh-my-pi/pi-utils/incoming-json`) for incrementally reading path-addressed JSON data as text arrives, including string chunks and lines, array elements, and keyed object values, with structured errors for missing, incomplete, aborted, malformed, or mismatched data.
|
|
10
|
+
- Added `Serial` for running asynchronous operations sequentially in call order.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Fixed relaxed JSON parsing for single-quoted strings followed by line or block comments.
|
|
15
|
+
|
|
5
16
|
## [18.1.5] - 2026-09-03
|
|
6
17
|
|
|
7
18
|
### Added
|
package/dist/types/async.d.ts
CHANGED
|
@@ -20,3 +20,13 @@ export declare class AsyncDrain<T> {
|
|
|
20
20
|
/** Runs the pending batch handler immediately and returns its completion promise. */
|
|
21
21
|
flush(): Promise<void>;
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Runs async operations one at a time in call order. Each `run` starts after
|
|
25
|
+
* the previous operation settles (success or failure) and returns that
|
|
26
|
+
* operation's own promise, so a rejected step never poisons the queue. Used by
|
|
27
|
+
* stateful cursors whose concurrent pulls must not interleave.
|
|
28
|
+
*/
|
|
29
|
+
export declare class Serial {
|
|
30
|
+
#private;
|
|
31
|
+
run<T>(op: () => Promise<T>): Promise<T>;
|
|
32
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed cursors over a JSON document while its text is still arriving.
|
|
3
|
+
*
|
|
4
|
+
* {@link IncomingDoc.channel} returns a push-side {@link IncomingFeed} and a
|
|
5
|
+
* read-side {@link IncomingDoc}. The producer appends text fragments, then
|
|
6
|
+
* explicitly calls {@link IncomingFeed.finish} or {@link IncomingFeed.abort};
|
|
7
|
+
* a feed that is never closed leaves every pending pull waiting forever.
|
|
8
|
+
* There is one shared append-only buffer; cursors are cheap, immutable
|
|
9
|
+
* path handles over it, and every pull is an ordinary promise that
|
|
10
|
+
* re-scans the buffer whenever the feed changes. There are no snapshots,
|
|
11
|
+
* per-field events, or fan-out channels.
|
|
12
|
+
*
|
|
13
|
+
* A scalar completes at its closing quote/delimiter, and a container
|
|
14
|
+
* completes only when its closing delimiter arrives. Finished-but-truncated
|
|
15
|
+
* input rejects with kind `incomplete`; abandoned input rejects with
|
|
16
|
+
* `aborted`. String chunks contain only decoded text whose meaning is
|
|
17
|
+
* stable, so an escape or Unicode escape may span any number of fragments.
|
|
18
|
+
*
|
|
19
|
+
* Pulling an {@link IncomingObject.key} makes that key required: a missing
|
|
20
|
+
* or mistyped value is a structured {@link IncomingJsonError}. Object members
|
|
21
|
+
* never pulled are skipped without validation. {@link IncomingDoc.whole} is
|
|
22
|
+
* the explicit whole-document pull and runs only after successful input
|
|
23
|
+
* completion.
|
|
24
|
+
*
|
|
25
|
+
* Object cursors bind the first occurrence of a duplicate key, whereas
|
|
26
|
+
* complete-value pulls (`value()`, `collect()`, `whole()`) go through the
|
|
27
|
+
* final parser (`parseJsonWithRepair`), whose objects are last-write-wins.
|
|
28
|
+
*
|
|
29
|
+
* Mid-stream cursors tolerate incomplete tokens but read double-quoted
|
|
30
|
+
* strings with the final parser's strict closing rule: an unescaped inner
|
|
31
|
+
* `"` can never swallow a sibling key or value. A pulled scalar completes
|
|
32
|
+
* only once a value terminator follows it, like numbers, so structural
|
|
33
|
+
* garbage after a value surfaces as `incomplete` rather than a silently
|
|
34
|
+
* misparsed pull. Single-quote recovery (`'it's'`) is shared with the final
|
|
35
|
+
* parser and passes both.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```ts
|
|
39
|
+
* const { feed, doc } = IncomingDoc.channel();
|
|
40
|
+
* const args = doc.root().object();
|
|
41
|
+
* const content = args.key("content").string();
|
|
42
|
+
* feed.push('{"path":"a.ts","content":"hel');
|
|
43
|
+
* await content.nextChunk(); // "hel"
|
|
44
|
+
* feed.push('lo"}');
|
|
45
|
+
* feed.finish();
|
|
46
|
+
* await content.nextChunk(); // "lo"
|
|
47
|
+
* await content.nextChunk(); // undefined
|
|
48
|
+
* await args.key("path").value<string>(); // "a.ts"
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
/** Location component in a pulled JSON path: an object member name or an array index. */
|
|
52
|
+
export type PullPathSegment = string | number;
|
|
53
|
+
/** JSON shape observed by a started pull. */
|
|
54
|
+
export type IncomingValueKind = "null" | "boolean" | "number" | "string" | "array" | "object";
|
|
55
|
+
/** Why a pull could not produce the requested shape. */
|
|
56
|
+
export type PullIssueKind =
|
|
57
|
+
/** The requested member was absent when its container completed. */
|
|
58
|
+
"missing"
|
|
59
|
+
/** The producer finished before the pulled value's closing token. */
|
|
60
|
+
| "incomplete"
|
|
61
|
+
/** The producer abandoned the input before the pull completed. */
|
|
62
|
+
| "aborted"
|
|
63
|
+
/** A complete pulled value could not be parsed. */
|
|
64
|
+
| "malformed"
|
|
65
|
+
/** A value was present with a different JSON shape (see `found`). */
|
|
66
|
+
| "mismatch";
|
|
67
|
+
/** Structured failure while awaiting an incoming JSON value. */
|
|
68
|
+
export declare class IncomingJsonError extends Error {
|
|
69
|
+
/** Full key/index path pulled by the consumer. */
|
|
70
|
+
readonly path: readonly PullPathSegment[];
|
|
71
|
+
/** Shape requested by the typed cursor. */
|
|
72
|
+
readonly expected: string;
|
|
73
|
+
/** Why the pull could not produce that shape. */
|
|
74
|
+
readonly kind: PullIssueKind;
|
|
75
|
+
/** Shape observed in the input when `kind` is `mismatch`. */
|
|
76
|
+
readonly found?: string;
|
|
77
|
+
constructor(path: readonly PullPathSegment[], expected: string, kind: PullIssueKind, options?: {
|
|
78
|
+
found?: string;
|
|
79
|
+
cause?: unknown;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
type Shape = {
|
|
83
|
+
kind: "null" | "array" | "object";
|
|
84
|
+
} | {
|
|
85
|
+
kind: "boolean";
|
|
86
|
+
value: boolean;
|
|
87
|
+
} | {
|
|
88
|
+
kind: "number";
|
|
89
|
+
value: number;
|
|
90
|
+
} | {
|
|
91
|
+
kind: "string";
|
|
92
|
+
value: string;
|
|
93
|
+
stableLen: number;
|
|
94
|
+
};
|
|
95
|
+
interface Located {
|
|
96
|
+
tag: "located";
|
|
97
|
+
/** Source offset of the value's first char. */
|
|
98
|
+
start: number;
|
|
99
|
+
/** Source offset just past the value's closing token; `undefined` while it is still open. */
|
|
100
|
+
end: number | undefined;
|
|
101
|
+
shape: Shape;
|
|
102
|
+
}
|
|
103
|
+
/** Readiness predicate a pull waits for once its value has been located. */
|
|
104
|
+
type Ready = (located: Located) => boolean;
|
|
105
|
+
/** Append-only buffer, terminal state, and change notification shared by one feed and its cursors. */
|
|
106
|
+
declare class Shared {
|
|
107
|
+
#private;
|
|
108
|
+
text: string;
|
|
109
|
+
end: "open" | "finished" | "aborted";
|
|
110
|
+
/** Resolves once the buffer or terminal state changes after this call. */
|
|
111
|
+
get changed(): Promise<void>;
|
|
112
|
+
notify(): void;
|
|
113
|
+
/** Await input completion; rejects with `aborted` when the feed was abandoned. */
|
|
114
|
+
finished(): Promise<void>;
|
|
115
|
+
/**
|
|
116
|
+
* Await the value at `path` until `ready` accepts it. Resolves `undefined`
|
|
117
|
+
* when the value's container completed without it.
|
|
118
|
+
*/
|
|
119
|
+
pull(path: readonly PullPathSegment[], expected: string, ready: Ready): Promise<Located | undefined>;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Push side of an {@link IncomingDoc} channel. Call {@link finish} to mark
|
|
123
|
+
* the document complete or {@link abort} to abandon it; both are idempotent
|
|
124
|
+
* and every pending pull settles on the first one.
|
|
125
|
+
*/
|
|
126
|
+
export declare class IncomingFeed {
|
|
127
|
+
#private;
|
|
128
|
+
constructor(shared: Shared);
|
|
129
|
+
/** Append one text fragment and wake every pending cursor. Throws once the feed is closed. */
|
|
130
|
+
push(fragment: string): void;
|
|
131
|
+
/** Mark the input complete. */
|
|
132
|
+
finish(): void;
|
|
133
|
+
/** Abandon the input; pending and future pulls reject with kind `aborted`. */
|
|
134
|
+
abort(): void;
|
|
135
|
+
}
|
|
136
|
+
/** Read side of one growing JSON document. */
|
|
137
|
+
export declare class IncomingDoc {
|
|
138
|
+
#private;
|
|
139
|
+
constructor(shared: Shared);
|
|
140
|
+
/** Create a push feed and its read-side document. */
|
|
141
|
+
static channel(): {
|
|
142
|
+
feed: IncomingFeed;
|
|
143
|
+
doc: IncomingDoc;
|
|
144
|
+
};
|
|
145
|
+
/** Text received so far. */
|
|
146
|
+
get text(): string;
|
|
147
|
+
/** Await explicit input completion; rejects with kind `aborted` if the feed was abandoned. */
|
|
148
|
+
finished(): Promise<void>;
|
|
149
|
+
/**
|
|
150
|
+
* Parse the entire finished document with the final tolerant parser.
|
|
151
|
+
* Waits for {@link IncomingFeed.finish}; aborted input is not decoded and a
|
|
152
|
+
* document that fails the final parse rejects with kind `malformed`.
|
|
153
|
+
*/
|
|
154
|
+
whole<T = unknown>(): Promise<T>;
|
|
155
|
+
/** Cursor for the root JSON value. */
|
|
156
|
+
root(): IncomingJson;
|
|
157
|
+
}
|
|
158
|
+
/** Cursor for one JSON value in the incoming document, addressed by path. */
|
|
159
|
+
export declare class IncomingJson {
|
|
160
|
+
#private;
|
|
161
|
+
readonly path: readonly PullPathSegment[];
|
|
162
|
+
constructor(shared: Shared, path: readonly PullPathSegment[]);
|
|
163
|
+
/** Await the value's first token and report its JSON shape. */
|
|
164
|
+
kind(): Promise<IncomingValueKind>;
|
|
165
|
+
/** Await and parse the complete value. Containers go through the final tolerant parser. */
|
|
166
|
+
value<T = unknown>(): Promise<T>;
|
|
167
|
+
/** Await a complete number. */
|
|
168
|
+
number(): Promise<number>;
|
|
169
|
+
/** Await a complete boolean. */
|
|
170
|
+
boolean(): Promise<boolean>;
|
|
171
|
+
/** Await a complete `null`. */
|
|
172
|
+
null(): Promise<null>;
|
|
173
|
+
/** View this value as an incremental decoded string. */
|
|
174
|
+
string(): IncomingString;
|
|
175
|
+
/** View this value as an array of element cursors. */
|
|
176
|
+
array(): IncomingArray;
|
|
177
|
+
/** View this value as an object with keyed cursors. */
|
|
178
|
+
object(): IncomingObject;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Incremental decoded string consumer. Chunks are emitted in order without
|
|
182
|
+
* overlap and are always prefixes of the final decoded string;
|
|
183
|
+
* {@link text} returns the complete string independently of whether chunks
|
|
184
|
+
* were consumed. Async iteration yields chunks. Concurrent `nextChunk` /
|
|
185
|
+
* `nextLine` calls are served in call order, like a stream reader: a call
|
|
186
|
+
* whose result is abandoned still consumes its chunk.
|
|
187
|
+
*/
|
|
188
|
+
export declare class IncomingString implements AsyncIterable<string> {
|
|
189
|
+
#private;
|
|
190
|
+
constructor(shared: Shared, path: readonly PullPathSegment[]);
|
|
191
|
+
/** Await the next stable decoded chunk, or `undefined` after the closing quote. */
|
|
192
|
+
nextChunk(): Promise<string | undefined>;
|
|
193
|
+
/**
|
|
194
|
+
* Await the next complete decoded line, retaining its trailing newline.
|
|
195
|
+
* A final unterminated line is returned once the closing quote arrives.
|
|
196
|
+
*/
|
|
197
|
+
nextLine(): Promise<string | undefined>;
|
|
198
|
+
/** Iterate complete decoded lines. */
|
|
199
|
+
lines(): AsyncGenerator<string, void, undefined>;
|
|
200
|
+
[Symbol.asyncIterator](): AsyncGenerator<string, void, undefined>;
|
|
201
|
+
/** Await the closing quote and return the complete decoded string. */
|
|
202
|
+
text(): Promise<string>;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Linear cursor over elements of an incoming array. Async iteration yields
|
|
206
|
+
* element cursors. Concurrent `next` calls are served in call order.
|
|
207
|
+
*/
|
|
208
|
+
export declare class IncomingArray implements AsyncIterable<IncomingJson> {
|
|
209
|
+
#private;
|
|
210
|
+
constructor(shared: Shared, path: readonly PullPathSegment[]);
|
|
211
|
+
/** Await the start of the next element; `undefined` only after the closing bracket. */
|
|
212
|
+
next(): Promise<IncomingJson | undefined>;
|
|
213
|
+
[Symbol.asyncIterator](): AsyncGenerator<IncomingJson, void, undefined>;
|
|
214
|
+
/** Await the closing bracket and collect the fully parsed elements. */
|
|
215
|
+
collect<T = unknown>(): Promise<T[]>;
|
|
216
|
+
}
|
|
217
|
+
/** Keyed cursor and final collection for an incoming object. */
|
|
218
|
+
export declare class IncomingObject {
|
|
219
|
+
#private;
|
|
220
|
+
constructor(shared: Shared, path: readonly PullPathSegment[]);
|
|
221
|
+
/**
|
|
222
|
+
* Cursor bound to the first occurrence of `name`. Pulling it makes the
|
|
223
|
+
* key required: a missing member rejects with kind `missing`.
|
|
224
|
+
*/
|
|
225
|
+
key(name: string): IncomingJson;
|
|
226
|
+
/**
|
|
227
|
+
* Await the closing brace and collect the object through the final parser,
|
|
228
|
+
* whose duplicate keys are last-write-wins unlike {@link key}.
|
|
229
|
+
*/
|
|
230
|
+
collect<T = Record<string, unknown>>(): Promise<T>;
|
|
231
|
+
}
|
|
232
|
+
export {};
|
package/dist/types/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export * from "./format.js";
|
|
|
10
10
|
export * from "./frontmatter.js";
|
|
11
11
|
export * from "./fs-error.js";
|
|
12
12
|
export * from "./glob.js";
|
|
13
|
+
export * from "./incoming-json.js";
|
|
13
14
|
export * from "./json.js";
|
|
14
15
|
export * from "./json-parse.js";
|
|
15
16
|
export * as logger from "./logger.js";
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tolerant JSON lexer shared by the final parser (`parseJsonWithRepair`), the
|
|
3
|
+
* streaming partial builder (`parseStreamingJson`), and the incoming cursors
|
|
4
|
+
* (`IncomingDoc`). {@link JsonLexerMode} selects how truncation and unescaped
|
|
5
|
+
* inner double quotes are treated.
|
|
6
|
+
*
|
|
7
|
+
* The grammar is a forgiving superset of JSON covering malformations commonly
|
|
8
|
+
* produced by language models:
|
|
9
|
+
*
|
|
10
|
+
* - single-quoted strings and unquoted object keys (JSON5);
|
|
11
|
+
* - trailing / stray commas, and `//` + block comments;
|
|
12
|
+
* - Python literals `True` / `False` / `None`, plus `0x` / `0b` numbers;
|
|
13
|
+
* - raw control characters and invalid `\x` escapes inside strings (kept literally);
|
|
14
|
+
* - unescaped quotes inside strings — a single quote only closes a string when
|
|
15
|
+
* followed by a value terminator, recovering apostrophes such as `'it's'`;
|
|
16
|
+
* the same recovery applies to double quotes in `streaming` mode only,
|
|
17
|
+
* everywhere else they close strictly;
|
|
18
|
+
* - unquoted string values in value position — an unrecognized bareword such
|
|
19
|
+
* as `{"paths": packages/foo/*}` is recovered as a string up to the next
|
|
20
|
+
* `,` / `}` / `]` / newline.
|
|
21
|
+
*/
|
|
22
|
+
export declare const QUOTE = 34;
|
|
23
|
+
export declare const SQUOTE = 39;
|
|
24
|
+
export declare const BACKSLASH = 92;
|
|
25
|
+
export declare const SLASH = 47;
|
|
26
|
+
export declare const COLON = 58;
|
|
27
|
+
export declare const COMMA = 44;
|
|
28
|
+
export declare const LBRACE = 123;
|
|
29
|
+
export declare const RBRACE = 125;
|
|
30
|
+
export declare const LBRACKET = 91;
|
|
31
|
+
export declare const RBRACKET = 93;
|
|
32
|
+
/** Valid chars after `\` in a strict JSON escape: `" \ / b f n r t u`. */
|
|
33
|
+
export declare const VALID_ESCAPE_CHAR: Uint8Array<ArrayBuffer>;
|
|
34
|
+
export declare function isHexDigit(cp: number): boolean;
|
|
35
|
+
/** JSON insignificant whitespace (RFC 8259 §2). */
|
|
36
|
+
export declare function isWhitespace(cp: number): boolean;
|
|
37
|
+
/** First char of a numeric token: sign, dot, or digit. */
|
|
38
|
+
export declare function isNumberStart(cp: number): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Grammar tolerance selected by the lexer's consumer.
|
|
41
|
+
*
|
|
42
|
+
* - `strict`: final parse — complete input required, double quotes close strictly.
|
|
43
|
+
* - `streaming`: mid-stream snapshot — incomplete tokens tolerated and unescaped
|
|
44
|
+
* inner double quotes recovered for display.
|
|
45
|
+
* - `incoming`: incremental typed pulls — incomplete tokens tolerated, but double
|
|
46
|
+
* quotes close strictly so pulled values match the final parse.
|
|
47
|
+
*/
|
|
48
|
+
export type JsonLexerMode = "strict" | "streaming" | "incoming";
|
|
49
|
+
/** Decoded state of a string token at the current streaming edge. */
|
|
50
|
+
export interface JsonStringProgress {
|
|
51
|
+
/** Decoded content so far (complete when `complete` is true). */
|
|
52
|
+
value: string;
|
|
53
|
+
/**
|
|
54
|
+
* Length of the prefix of `value` whose meaning cannot change when more
|
|
55
|
+
* input arrives. Excludes a trailing split escape, a high surrogate whose
|
|
56
|
+
* low half may still follow, and everything from a quote whose close/inner
|
|
57
|
+
* reading is still undecidable at the buffer edge.
|
|
58
|
+
*/
|
|
59
|
+
stableLen: number;
|
|
60
|
+
/** Whether the closing quote was consumed. */
|
|
61
|
+
complete: boolean;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Cursor over the input with the tolerant token readers. `pos` is the current
|
|
65
|
+
* offset; readers advance it. In `strict` mode a truncated or malformed token
|
|
66
|
+
* throws `SyntaxError`; the lenient modes report progress or return
|
|
67
|
+
* `undefined` so the caller can roll back.
|
|
68
|
+
*/
|
|
69
|
+
export declare class JsonLexer {
|
|
70
|
+
#private;
|
|
71
|
+
readonly src: string;
|
|
72
|
+
readonly mode: JsonLexerMode;
|
|
73
|
+
pos: number;
|
|
74
|
+
constructor(src: string, mode: JsonLexerMode, pos?: number);
|
|
75
|
+
get atEnd(): boolean;
|
|
76
|
+
/** Char code at the cursor; `NaN` at end of input (so every comparison is false). */
|
|
77
|
+
peek(): number;
|
|
78
|
+
/** Skip whitespace plus `//` line and `/* *\/` block comments. */
|
|
79
|
+
ws(): void;
|
|
80
|
+
/**
|
|
81
|
+
* Read a string starting at the opening `quote`, retaining the information
|
|
82
|
+
* an incremental consumer needs. Strict mode throws on an unterminated
|
|
83
|
+
* string; lenient modes consume to the end of input and report progress.
|
|
84
|
+
*/
|
|
85
|
+
string(quote: number): JsonStringProgress;
|
|
86
|
+
/**
|
|
87
|
+
* Read a numeric token with JS `Number()` semantics (decimal with optional
|
|
88
|
+
* sign / leading or trailing dot / exponent, plus `0x` hex and `0b` binary).
|
|
89
|
+
* Non-finite or malformed tokens throw in strict mode and return
|
|
90
|
+
* `undefined` in lenient modes; the cursor is left past the token either way.
|
|
91
|
+
*/
|
|
92
|
+
number(): number | undefined;
|
|
93
|
+
/**
|
|
94
|
+
* Match a keyword literal at the cursor; consumes only on success and
|
|
95
|
+
* returns `undefined` otherwise. Requires a non-identifier boundary so
|
|
96
|
+
* `Truex` / `nullish` are not misread as the keyword followed by junk.
|
|
97
|
+
*/
|
|
98
|
+
keyword(): boolean | null | undefined;
|
|
99
|
+
/** Read an unquoted object key: everything up to `:` / `,` / `}` / whitespace. May be empty. */
|
|
100
|
+
unquotedKey(): string;
|
|
101
|
+
/**
|
|
102
|
+
* Recover an unquoted string value, e.g. `{"paths": packages/foo/*}`:
|
|
103
|
+
* consume until `,` / `}` / `]` / newline and trim trailing whitespace.
|
|
104
|
+
* Recovery still fails — so a final parse never accepts a half-formed or
|
|
105
|
+
* non-finite argument — when the token:
|
|
106
|
+
* - hits end-of-input before a delimiter (truncated value);
|
|
107
|
+
* - contains a `"`, `{`, `[`, or a key-like `:` — this grammar accepts
|
|
108
|
+
* unquoted keys, so a missed comma (`{"a": foo "b": 1}`) would otherwise
|
|
109
|
+
* silently swallow the following field. A colon followed by `/` or `\`
|
|
110
|
+
* stays literal so URL and Windows-path values recover;
|
|
111
|
+
* - is a non-finite atom ({@link NON_RECOVERABLE_BAREWORDS}).
|
|
112
|
+
*
|
|
113
|
+
* Failure throws in strict mode and returns `undefined` in lenient modes.
|
|
114
|
+
*/
|
|
115
|
+
bareword(): string | undefined;
|
|
116
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-utils",
|
|
4
|
-
"version": "18.1.
|
|
4
|
+
"version": "18.1.6",
|
|
5
5
|
"description": "Shared utilities for pi packages",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Stencil Labs, Inc.",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@oh-my-pi/pi-natives": "18.1.
|
|
34
|
+
"@oh-my-pi/pi-natives": "18.1.6"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/bun": "^1.3.14"
|
package/src/async.ts
CHANGED
|
@@ -108,3 +108,19 @@ export class AsyncDrain<T> {
|
|
|
108
108
|
return this.#promise;
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Runs async operations one at a time in call order. Each `run` starts after
|
|
114
|
+
* the previous operation settles (success or failure) and returns that
|
|
115
|
+
* operation's own promise, so a rejected step never poisons the queue. Used by
|
|
116
|
+
* stateful cursors whose concurrent pulls must not interleave.
|
|
117
|
+
*/
|
|
118
|
+
export class Serial {
|
|
119
|
+
#tail: Promise<unknown> = Promise.resolve();
|
|
120
|
+
|
|
121
|
+
run<T>(op: () => Promise<T>): Promise<T> {
|
|
122
|
+
const result = this.#tail.then(op, op);
|
|
123
|
+
this.#tail = result.catch(() => {});
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
}
|