@jarenjs/josl 0.56.0 → 0.66.1

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/FORMAT.md CHANGED
@@ -136,9 +136,31 @@ visits leaves bottom-up, only after the full text has arrived, and never
136
136
  tells you where you are.
137
137
 
138
138
  The write side mirrors this: `createStreamWriter` emits text chunks
139
- event by event (`pair`, `table`, `tableArray`, `rootItem`), and
140
- `stringifyJoslChunks` streams an existing value one `[[]]` record at a
141
- time byte-identical to `stringifyJosl` output.
139
+ event by event (`pair`, `table`, `tableArray`, `rootItem`; `buffer:
140
+ false` retains none of them), `stringifyJoslChunks` streams an existing
141
+ value one `[[]]` record at a time, and `stringifyJoslStream` pulls
142
+ records from an async source one per chunk — all byte-identical to
143
+ `stringifyJosl` output. `iterateJoslStream` is the reading twin: a
144
+ `[[]]` root array as a pull source of records, each detached from the
145
+ root as the next header completes it; a table root is refused, being one
146
+ retained value rather than a record stream.
147
+
148
+ **Limits.** A reader refuses a document by size only when asked. Every
149
+ limit defaults to `Infinity`, counts UTF-8 bytes, and is judged while
150
+ the text is still in cutter, token or container state; a crossing is a
151
+ `JoslLimitError` with a stable code — never a repair.
152
+
153
+ | reader | option | code |
154
+ | --- | --- | --- |
155
+ | JOSL / TOML | `maxTotalBytes` | `JOSL2001` |
156
+ | JOSL / TOML | `maxRecordBytes` — one logical line | `JOSL2002` |
157
+ | JOSL / TOML | `maxTokenBytes` — a string or key as written, quotes included | `JOSL2003` |
158
+ | JOSL / TOML | `maxDepth` — inline nesting and header path depth | `JOSL2004` |
159
+ | JOSL / TOML | `maxRetainedValues` — values the root holds, starting over per detached `[[]]` item | `JOSL2005` |
160
+ | JSONX / JSON (stream reader) | `maxTotalBytes` | `JSONX2001` |
161
+ | JSONX / JSON (stream reader) | `maxTokenBytes` — a string, key, number or regexp as written | `JSONX2002` |
162
+ | JSONX / JSON (stream reader) | `maxDepth` | `JSONX2003` |
163
+ | JSONX / JSON (stream reader) | `maxRetainedValues` — linked values only; a detached subtree never counts | `JSONX2004` |
142
164
 
143
165
  ## JSONX
144
166
 
@@ -206,13 +228,28 @@ information that cannot be recovered downstream:
206
228
  did not carry it, `''` would claim it carried nothing.
207
229
  - `typed: true` promotes an integer beyond 2^53 to bigint rather than
208
230
  rounding, reads unambiguous ISO-8601 as the value classes above, and
209
- leaves anything with a leading zero a string.
231
+ leaves numbers with a leading zero a string. Offset date-times use
232
+ the same instant conversion as JOSL, preserving four-digit years and
233
+ truncating precision beyond milliseconds; invalid offsets stay text.
210
234
  - A BOM is stripped without comment; it is an encoding mark, not data.
211
235
 
212
236
  **Writing** quotes a field only when it contains the delimiter, the quote
213
237
  character, a newline, or edge whitespace a lenient reader might trim. The
214
238
  default terminator is CRLF, per RFC 4180 §2.1 and what spreadsheet
215
- software expects.
239
+ software expects. `stringifyCsv`, `stringifyCsvChunks`, the stream writer
240
+ and the pull form `stringifyCsvStream` share one row formatter and are
241
+ byte-identical for the same records; the pull form requests a record only
242
+ when its consumer asks for the next line.
243
+
244
+ **Limits.** Optional, `Infinity` by default, in UTF-8 bytes, judged before
245
+ the text is kept; a crossing is a `JoslLimitError`, never a repair:
246
+
247
+ | option | code |
248
+ | --- | --- |
249
+ | `maxTotalBytes` | `CSV2001` |
250
+ | `maxRecordBytes` — one record, its terminator included | `CSV2002` |
251
+ | `maxFieldBytes` — one field as written, quotes included | `CSV2003` |
252
+ | `maxColumns` | `CSV2004` |
216
253
 
217
254
  ## Compliance notes
218
255
 
package/README.md CHANGED
@@ -129,6 +129,49 @@ records went past. The pattern matches an exact path, not a prefix, so a
129
129
  feature's own rings are not separately detached: they belong to their
130
130
  feature and are freed with it.
131
131
 
132
+ The JOSL twin is `iterateJoslStream`: a `[[]]` root array read as a pull
133
+ source of records, each detached from the root the moment the next
134
+ `[[]]` header (or the end) completes it, so the reader holds at most the
135
+ record in progress. A table root is one retained value, not a record
136
+ stream, and is refused by name.
137
+
138
+ ```js
139
+ import { iterateJoslStream } from '@jarenjs/josl/stream';
140
+
141
+ for await (const record of iterateJoslStream(response.body, { signal }))
142
+ await save(record); // the source is closed exactly once on an abort or an early return
143
+ ```
144
+
145
+ ### Hostile input
146
+
147
+ Every reader refuses a document by size only when asked: the limits
148
+ default to `Infinity`, count **UTF-8 bytes** (never code units), and are
149
+ checked while the offending text is still in cutter, token or container
150
+ state — before a concatenation or a link could cross them. A crossing
151
+ throws `JoslLimitError` with a stable code and the limit; it is never a
152
+ repair, because a document that is too large is not damaged.
153
+
154
+ | reader | option | code |
155
+ | --- | --- | --- |
156
+ | CSV | `maxTotalBytes` | `CSV2001` |
157
+ | CSV | `maxRecordBytes` (one record, its terminator included) | `CSV2002` |
158
+ | CSV | `maxFieldBytes` (one field as written, quotes included) | `CSV2003` |
159
+ | CSV | `maxColumns` | `CSV2004` |
160
+ | JOSL / TOML | `maxTotalBytes` | `JOSL2001` |
161
+ | JOSL / TOML | `maxRecordBytes` (one logical line) | `JOSL2002` |
162
+ | JOSL / TOML | `maxTokenBytes` (a string or key as written, quotes included) | `JOSL2003` |
163
+ | JOSL / TOML | `maxDepth` (inline nesting, header path depth) | `JOSL2004` |
164
+ | JOSL / TOML | `maxRetainedValues` (values the root holds; starts over per detached `[[]]` item) | `JOSL2005` |
165
+ | JSONX / JSON stream | `maxTotalBytes` | `JSONX2001` |
166
+ | JSONX / JSON stream | `maxTokenBytes` (a string, key, number or regexp as written) | `JSONX2002` |
167
+ | JSONX / JSON stream | `maxDepth` | `JSONX2003` |
168
+ | JSONX / JSON stream | `maxRetainedValues` (linked values only — a detached subtree is never counted) | `JSONX2004` |
169
+
170
+ The CSV and JOSL limits guard the whole-document parsers too, which run
171
+ the same machines; the JSONX limits are the stream reader's, which is
172
+ where a document arrives a chunk at a time. `CSV_LIMIT_CODES`,
173
+ `JOSL_LIMIT_CODES` and `JSONX_LIMIT_CODES` are the tables as data.
174
+
132
175
  Measured on a synthetic OpenStreetMap-shaped extract (`node --expose-gc
133
176
  benchmark/jsonx-stream.js`), reading 20 000 features of 40 vertices each:
134
177
 
@@ -212,23 +255,33 @@ The write-side mirror of the reader — build a document event by event and
212
255
  ship each chunk as it is produced:
213
256
 
214
257
  ```js
215
- import { createStreamWriter, stringifyJoslChunks } from '@jarenjs/josl/write';
258
+ import { createStreamWriter, stringifyJoslChunks, stringifyJoslStream } from '@jarenjs/josl/write';
216
259
 
217
- const w = createStreamWriter({ onChunk: (c) => response.write(c) });
260
+ const w = createStreamWriter({ onChunk: (c) => response.write(c), buffer: false });
218
261
  w.pair('title', 'run 42')
219
262
  .table('server')
220
263
  .pair('host', 'localhost');
221
264
  // root-array documents: w.rootItem(record) per completed record
222
- const text = w.end();
265
+ w.end(); // '' — buffer: false retains nothing; the sink has it
223
266
 
224
267
  // or stream an existing value, one chunk per [[]] record:
225
268
  for (const chunk of stringifyJoslChunks(records))
226
269
  response.write(chunk);
270
+
271
+ // or pull records from an async source — a database cursor — one per chunk:
272
+ response.body = stringifyJoslStream(store.collection('rows').query(doc), { signal });
227
273
  ```
228
274
 
229
275
  The writer validates what the reader would reject (duplicate keys and
230
276
  headers, root table/array mixing, TOML downleveling) and shares its
231
277
  serialization with `stringifyJosl`, so both produce identical text.
278
+ `stringifyJoslStream` is the pull form: the next record is requested only
279
+ when the consumer asks for the next chunk, so a cursor behind it never
280
+ runs ahead of the socket in front of it; its chunks are byte-identical to
281
+ `stringifyJoslChunks` for the same records, and an abort (`signal`), a
282
+ consumer that stops early or a throw closes the record source exactly
283
+ once. `buffer: false` (with an `onChunk` sink) keeps the event writer from
284
+ retaining a second copy of the document it streams.
232
285
 
233
286
  ## Editing a document (CST)
234
287
 
@@ -275,6 +328,30 @@ stringifyJsonx(v, { mode: 'json' }); // delegates to JSON.stringify
275
328
 
276
329
  ## Exports
277
330
 
331
+ Every subpath a consumer can import, derived from the manifest by
332
+ `npm run docs:derive` (`npm run docs:check` fails when the two drift):
333
+
334
+ <!--fact:exports.josl-->
335
+ | Import | Kind | Declarations |
336
+ |---|---|---|
337
+ | `@jarenjs/josl` | JavaScript | declared |
338
+ | `@jarenjs/josl/parse` | JavaScript | declared |
339
+ | `@jarenjs/josl/cst` | JavaScript | declared |
340
+ | `@jarenjs/josl/gbnf` | JavaScript | declared |
341
+ | `@jarenjs/josl/stream` | JavaScript | declared |
342
+ | `@jarenjs/josl/stringify` | JavaScript | declared |
343
+ | `@jarenjs/josl/write` | JavaScript | declared |
344
+ | `@jarenjs/josl/jsonx` | JavaScript | declared |
345
+ | `@jarenjs/josl/jsonx-stream` | JavaScript | declared |
346
+ | `@jarenjs/josl/csv` | JavaScript | declared |
347
+ | `@jarenjs/josl/csv-stream` | JavaScript | declared |
348
+ | `@jarenjs/josl/values` | JavaScript | declared |
349
+ | `@jarenjs/josl/schemas/jaren-josl-data.schema.json` | schema | — |
350
+ | `@jarenjs/josl/package.json` | metadata | — |
351
+ <!--/fact-->
352
+
353
+ What each subpath carries:
354
+
278
355
  | Subpath | What |
279
356
  | --- | --- |
280
357
  | `@jarenjs/josl/parse` | `parseJosl`, `parseToml` |
@@ -304,10 +381,22 @@ parseCsv('a,b\n1,2'); // [['a','b'], ['1','2']]
304
381
  parseCsv('a,b\n1,2', { headers: true }); // [{ a: '1', b: '2' }]
305
382
 
306
383
  // rows as they complete, without ever holding the table
307
- for await (const row of iterateCsvStream(response.body, { headers: true }))
384
+ for await (const row of iterateCsvStream(response.body, { headers: true, signal }))
308
385
  await save(row);
386
+
387
+ // and the other way: pull records from a cursor into CSV lines, header once
388
+ response.body = stringifyCsvStream(store.collection('rows').query(doc), { signal });
309
389
  ```
310
390
 
391
+ `stringifyCsvStream` is byte-identical to `stringifyCsvChunks` and
392
+ `stringifyCsv` for the same records and pulls one record per line, so the
393
+ source is never asked for a row the consumer has not asked for; `signal`
394
+ aborts between pulls, and an abort, an early return or a throw closes the
395
+ source exactly once. The reader's `signal` does the same for
396
+ `iterateCsvStream`. The hostile-input limits (`maxTotalBytes`,
397
+ `maxRecordBytes`, `maxFieldBytes`, `maxColumns`, the `CSV2xxx` codes) are
398
+ listed under Streaming.
399
+
311
400
  **Strict by default.** Anything RFC 4180 forbids throws a
312
401
  `CsvSyntaxError` carrying a `CSV1xxx` code, a line and a column — the
313
402
  same machine-repairable error shape the JOSL parser uses.
@@ -39,6 +39,12 @@ export declare class CsvMachine {
39
39
  skipEmptyLines: boolean;
40
40
  onEvent: any;
41
41
  onRepair: any;
42
+ maxTotalBytes: number;
43
+ maxRecordBytes: number;
44
+ maxFieldBytes: number;
45
+ maxColumns: number;
46
+ limited: boolean;
47
+ totalBytes: number;
42
48
  wantHeader: boolean;
43
49
  headerFields: any;
44
50
  objectRows: boolean;
@@ -69,6 +75,8 @@ export declare class CsvMachine {
69
75
  * @returns {this} The machine, for chaining
70
76
  */
71
77
  feed(chunk: string): this;
78
+ count(text: any): void;
79
+ endsRecordWithin(text: any): boolean;
72
80
  /**
73
81
  * Finish the document, flushing any pending record.
74
82
  * @returns {Array} The completed rows
@@ -94,6 +102,7 @@ export declare class CsvMachine {
94
102
  scan(): void;
95
103
  compact(start: any): void;
96
104
  readSpan(text: any, pos: any, end: any): any;
105
+ admitCell(cells: any, text: any, start: any, stop: any): void;
97
106
  parseRecord(text: any, pos: any, end: any, cells: any): any;
98
107
  parsePlain(text: any, pos: any, end: any, cells: any): any;
99
108
  parseQuoted(text: any, pos: any, end: any, cells: any): any;
@@ -48,6 +48,23 @@ export declare function parseCsvStream(chunks: AsyncIterable<string> | Iterable<
48
48
  * await save(row);
49
49
  */
50
50
  export declare function iterateCsvStream(chunks: AsyncIterable<string> | Iterable<string>, options?: object): AsyncGenerator<any, void, unknown>;
51
+ /**
52
+ * Serialize an async iterable of records as an async iterable of CSV
53
+ * text chunks — the pull form of `stringifyCsvChunks`, byte-identical
54
+ * to it for the same records: the header exactly once before the first
55
+ * object row, one line per chunk. Pull is the backpressure: the next
56
+ * record is requested only when the consumer asks for the next chunk,
57
+ * so a database cursor behind it never runs ahead of the socket in
58
+ * front of it. `options.signal` aborts between pulls (the rejection is
59
+ * the signal's reason); an abort, a consumer that stops early or a throw
60
+ * closes the record source exactly once.
61
+ * @param {AsyncIterable<Array|object>|Iterable<Array|object>} rows - Records
62
+ * @param {object} [options] - Writer options; see `stringifyCsv`, plus `signal`
63
+ * @yields {string} One line at a time
64
+ * @example
65
+ * response.body = stringifyCsvStream(store.collection('rows').query(doc), { signal });
66
+ */
67
+ export declare function stringifyCsvStream(rows: AsyncIterable<any[] | object> | Iterable<any[] | object>, options?: object): AsyncGenerator<string, void, unknown>;
51
68
  /**
52
69
  * An incremental CSV writer: rows in, chunks out.
53
70
  */
@@ -56,12 +73,17 @@ export declare class CsvStreamWriter {
56
73
  options: object;
57
74
  chunks: any[];
58
75
  onChunk: any;
59
- fields: any;
60
- wroteHeader: boolean;
76
+ formatter: {
77
+ lines: (row: any[] | object) => string[];
78
+ tail: () => string;
79
+ fields: () => string[] | null;
80
+ };
61
81
  /**
62
82
  * @param {object} [options] - Writer options; see `stringifyCsv`
63
83
  */
64
84
  constructor(options?: object);
85
+ /** @returns {string[]|null} The columns as decided, once known. */
86
+ get fields(): string[] | null;
65
87
  /**
66
88
  * Write one record.
67
89
  * @param {Array|object} row - An array, or an object keyed by column
@@ -80,7 +102,8 @@ export declare class CsvStreamWriter {
80
102
  */
81
103
  toString(): string;
82
104
  /**
83
- * Finish writing.
105
+ * Finish writing: the header an explicit field list is still owed
106
+ * goes out when no record was written.
84
107
  * @returns {string} The complete document, or `''` with an `onChunk` sink
85
108
  */
86
109
  end(): string;
@@ -100,3 +123,4 @@ export declare function createCsvStreamWriter(options?: {
100
123
  onChunk?: (chunk: string) => void;
101
124
  }): CsvStreamWriter;
102
125
  export { stringifyCsvChunks, formatCsvValue };
126
+ export { JoslLimitError, CSV_LIMIT_CODES } from './limits.js';
@@ -96,8 +96,10 @@ export declare function sniffCsvDialect(text: string, options?: object): {
96
96
  };
97
97
  /**
98
98
  * Render one value as CSV cell text. `null`/`undefined` become empty,
99
- * bigints lose the `n` suffix JOSL uses (CSV has no type marks), and the
100
- * JOSL date classes and `Date` render as ISO-8601.
99
+ * bigints lose the `n` suffix JOSL uses (CSV has no type marks), the
100
+ * JOSL date classes and `Date` render as ISO-8601, and a plain object or
101
+ * an array — a nested document in a flat format — is its JSON text, so
102
+ * a reader gets the value back instead of `[object Object]`.
101
103
  * @param {*} value - The value
102
104
  * @returns {string} Cell text, unquoted
103
105
  */
@@ -130,9 +132,28 @@ export declare function stringifyCsv(rows: Array<any[] | object>, options?: {
130
132
  fields?: string[];
131
133
  header?: boolean;
132
134
  }): string;
135
+ /**
136
+ * The one row formatter behind every CSV writer: the same delimiter,
137
+ * quote and terminator rules and the same header-once rule, whether
138
+ * rows arrive as an array, an iterable, an async iterable or one at a
139
+ * time through the stream writer — so all of them produce byte-identical
140
+ * text for the same records by construction.
141
+ * @param {object} [options] - Writer options; see `stringifyCsv`
142
+ * @returns {{ lines: (row: Array|object) => string[], tail: () => string, fields: () => string[] | null }}
143
+ * `lines` formats one record as its line — preceded by the header
144
+ * line exactly once, before the first object row — `tail` answers the
145
+ * header an explicit field list is still owed when no record was
146
+ * ever written, `fields` the columns as decided
147
+ */
148
+ export declare function createCsvRowFormatter(options?: object): {
149
+ lines: (row: any[] | object) => string[];
150
+ tail: () => string;
151
+ fields: () => string[] | null;
152
+ };
133
153
  /**
134
154
  * Serialize records as an iterable of chunks, one record at a time, so a
135
- * large table never exists as a single string.
155
+ * large table never exists as a single string. The header precedes the
156
+ * first object row; a chunk is one line.
136
157
  * @param {Iterable<Array|object>} rows - Records
137
158
  * @param {object} [options] - Writer options; see `stringifyCsv`
138
159
  * @yields {string} One record (or the header) at a time
@@ -1,12 +1,13 @@
1
1
  export { parseJosl, parseToml } from './parse.js';
2
2
  export { parseJoslCst, parseTomlCst, JoslCstDocument } from './cst.js';
3
- export { createStreamReader, parseJoslStream } from './stream.js';
3
+ export { createStreamReader, parseJoslStream, iterateJoslStream } from './stream.js';
4
4
  export { stringifyJosl, stringifyToml, formatKey, formatKeyPath, formatValue, formatSection, } from './stringify.js';
5
- export { createStreamWriter, stringifyJoslChunks } from './write.js';
5
+ export { createStreamWriter, stringifyJoslChunks, stringifyJoslStream } from './write.js';
6
6
  export { toGbnf, tomlToGbnf } from './gbnf.js';
7
7
  export { parseJsonx, stringifyJsonx } from './jsonx.js';
8
8
  export { createJsonxStreamReader, parseJsonxStream } from './jsonx-stream.js';
9
9
  export { parseCsv, parseCsvDocument, stringifyCsv, stringifyCsvChunks, sniffCsvDialect, formatCsvValue, coerceCsvValue, CSV_CODES, } from './csv.js';
10
- export { createCsvStreamReader, parseCsvStream, iterateCsvStream, createCsvStreamWriter, CsvStreamWriter, } from './csv-stream.js';
10
+ export { createCsvStreamReader, parseCsvStream, iterateCsvStream, stringifyCsvStream, createCsvStreamWriter, CsvStreamWriter, } from './csv-stream.js';
11
11
  export { JoslSyntaxError, JoslStringifyError, JsonxSyntaxError, CsvSyntaxError } from './errors.js';
12
+ export { JoslLimitError, CSV_LIMIT_CODES, JOSL_LIMIT_CODES, JSONX_LIMIT_CODES } from './limits.js';
12
13
  export { LocalDate, LocalTime, LocalDateTime, isValidDateParts, isValidTimeParts, } from './values.js';
@@ -3,6 +3,12 @@ export declare class JsonxMachine {
3
3
  onEvent: ((event: JsonxStreamEvent) => void) | null;
4
4
  partialText: boolean;
5
5
  detach: (string | number)[] | null;
6
+ maxTotalBytes: number;
7
+ maxTokenBytes: number;
8
+ maxDepth: number;
9
+ maxRetainedValues: number;
10
+ totalBytes: number;
11
+ retained: number;
6
12
  partialFrom: number;
7
13
  partialHold: string;
8
14
  buf: string;
@@ -42,6 +48,8 @@ export declare class JsonxMachine {
42
48
  * @returns {this} The machine, for chaining
43
49
  */
44
50
  feed(chunk: string): this;
51
+ token(buf: any, start: any, end: any): void;
52
+ retain(): void;
45
53
  /**
46
54
  * Finish the document, flushing any pending token.
47
55
  * @returns {*} The completed root value
@@ -161,3 +169,4 @@ export declare function createJsonxStreamReader(options?: {
161
169
  */
162
170
  export declare function parseJsonxStream(chunks: AsyncIterable<string> | Iterable<string>, options?: object): Promise<any>;
163
171
  export { JsonxSyntaxError } from './errors.js';
172
+ export { JoslLimitError, JSONX_LIMIT_CODES } from './limits.js';
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The CSV limits, by option name.
3
+ * @type {Readonly<Record<'CSV2001' | 'CSV2002' | 'CSV2003' | 'CSV2004', string>>}
4
+ */
5
+ export declare const CSV_LIMIT_CODES: Readonly<Record<'CSV2001' | 'CSV2002' | 'CSV2003' | 'CSV2004', string>>;
6
+ /**
7
+ * The JOSL/TOML limits, by option name. A "record" is one logical line —
8
+ * the unit the reader buffers before it parses anything.
9
+ * @type {Readonly<Record<'JOSL2001' | 'JOSL2002' | 'JOSL2003' | 'JOSL2004' | 'JOSL2005', string>>}
10
+ */
11
+ export declare const JOSL_LIMIT_CODES: Readonly<Record<'JOSL2001' | 'JOSL2002' | 'JOSL2003' | 'JOSL2004' | 'JOSL2005', string>>;
12
+ /**
13
+ * The JSONX/JSON limits, by option name.
14
+ * @type {Readonly<Record<'JSONX2001' | 'JSONX2002' | 'JSONX2003' | 'JSONX2004', string>>}
15
+ */
16
+ export declare const JSONX_LIMIT_CODES: Readonly<Record<'JSONX2001' | 'JSONX2002' | 'JSONX2003' | 'JSONX2004', string>>;
17
+ /**
18
+ * Error thrown when a document crosses a limit a caller set. Never a
19
+ * repair, never healed: the text is refused where it stands.
20
+ */
21
+ export declare class JoslLimitError extends Error {
22
+ code: string;
23
+ limit: number;
24
+ line: number | undefined;
25
+ /**
26
+ * @param {string} code - The stable `CSV2xxx` / `JOSL2xxx` / `JSONX2xxx` code
27
+ * @param {string} message - What was crossed
28
+ * @param {number} limit - The limit the option set
29
+ * @param {number} [line] - 1-based physical line where the crossing was met, when known
30
+ */
31
+ constructor(code: string, message: string, limit: number, line?: number);
32
+ }
33
+ /**
34
+ * Read one limit option: absent is `Infinity`; anything but a positive
35
+ * integer or `Infinity` is a `TypeError` — a limit of zero refuses every
36
+ * document, and a fraction or a string is a caller's mistake.
37
+ * @param {object} options - The reader/writer options
38
+ * @param {string} name - The option name
39
+ * @returns {number} The limit, `Infinity` when unset
40
+ */
41
+ export declare function limitOption(options: object, name: string): number;
@@ -1,6 +1,16 @@
1
1
  export declare class JoslMachine {
2
2
  mode: string;
3
3
  onEvent: ((event: object) => void) | null;
4
+ detachRoot: any;
5
+ rootCount: number;
6
+ maxTotalBytes: number;
7
+ maxRecordBytes: number;
8
+ maxTokenBytes: number;
9
+ maxDepth: number;
10
+ maxRetainedValues: number;
11
+ totalBytes: number;
12
+ retained: number;
13
+ depth: number;
4
14
  onLine: any;
5
15
  lineValueStart: number;
6
16
  lineValueEnd: number;
@@ -54,9 +64,15 @@ export declare class JoslMachine {
54
64
  /**
55
65
  * The (possibly still growing) root value: `{}`-rooted for documents,
56
66
  * `[]`-rooted after a `[[]]` header. Undefined content yields `{}`.
67
+ * Under `detachRoot` a finished document hands its last item over
68
+ * here, so the root ends empty.
57
69
  * @returns {*} Current root value
58
70
  */
59
71
  root(): any;
72
+ detachLast(): void;
73
+ retain(): void;
74
+ deeper(depth: any): void;
75
+ token(line: any, start: any, end: any): void;
60
76
  scan(): void;
61
77
  cutLine(buf: any, start: any, nlPos: any, innerNl: any): void;
62
78
  err(pos: any, message: any, hint: any): void;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Close an iterator once, swallowing what the close throws: a source
3
+ * that refuses its cancel is already gone.
4
+ * @param {Iterator<any> | AsyncIterator<any>} iterator
5
+ * @returns {Promise<void>}
6
+ */
7
+ export declare function closeIterator(iterator: Iterator<any> | AsyncIterator<any>): Promise<void>;
8
+ /**
9
+ * The rejection of an aborted pull: the signal's reason when it has one,
10
+ * else an `AbortError`-named error, as the platform spells it.
11
+ * @param {AbortSignal} signal
12
+ * @returns {unknown}
13
+ */
14
+ export declare function abortedError(signal: AbortSignal): unknown;
@@ -29,4 +29,24 @@ export declare function createStreamReader(options?: {
29
29
  * @returns {Promise<*>} The completed root value
30
30
  */
31
31
  export declare function parseJoslStream(chunks: AsyncIterable<string> | Iterable<string>, options?: object): Promise<any>;
32
+ /**
33
+ * Yield the `[[]]` root items of a JOSL document as they complete,
34
+ * without ever holding the whole document: each item is DETACHED from
35
+ * the root the moment the next `[[]]` header (or the end) completes it,
36
+ * so `root()`-style retention never grows past one record — the JOSL
37
+ * twin of the JSONX reader's `detach`. A document with a table root is
38
+ * refused by name (`JoslSyntaxError`): a table root is one retained
39
+ * value, not a stream of records. `options.signal` aborts between
40
+ * chunks; an abort, a consumer that stops early or a throw closes the
41
+ * chunk source exactly once.
42
+ * @param {AsyncIterable<string>|Iterable<string>} chunks - Source chunks
43
+ * @param {object} [options] - Reader options; see `createStreamReader`,
44
+ * plus `signal` and the `JOSL2xxx` limits
45
+ * @yields {object} One completed root item at a time
46
+ * @example
47
+ * for await (const record of iterateJoslStream(response.body))
48
+ * await save(record);
49
+ */
50
+ export declare function iterateJoslStream(chunks: AsyncIterable<string> | Iterable<string>, options?: object): AsyncGenerator<object, void, unknown>;
32
51
  export { JoslSyntaxError } from './errors.js';
52
+ export { JoslLimitError, JOSL_LIMIT_CODES } from './limits.js';
@@ -2,6 +2,18 @@
2
2
  export declare const RE_DATETIME: RegExp;
3
3
  /** A bare `HH:MM:SS` with an optional fraction. @type {RegExp} */
4
4
  export declare const RE_TIMEONLY: RegExp;
5
+ /**
6
+ * Convert a local date and time with an offset to their native instant.
7
+ * ISO parsing preserves years 0000-0099 and truncates sub-millisecond
8
+ * precision consistently for JOSL and typed CSV. An invalid offset (or
9
+ * an instant Date cannot represent) returns an invalid Date for the
10
+ * caller's own refusal or text-preservation policy.
11
+ * @param {import('./values.js').LocalDate} date - Validated calendar date
12
+ * @param {import('./values.js').LocalTime} time - Validated local time
13
+ * @param {string} offset - Z/z or a signed HH:MM offset
14
+ * @returns {Date}
15
+ */
16
+ export declare function offsetDateTime(date: import('./values.js').LocalDate, time: import('./values.js').LocalTime, offset: string): Date;
5
17
  /**
6
18
  * Run a sticky regex at `pos` and return its match (or null).
7
19
  * @param {RegExp} re - A sticky (`y`) pattern
@@ -5,7 +5,9 @@ declare class JoslStreamWriter {
5
5
  onRegExp: any;
6
6
  };
7
7
  onChunk: any;
8
+ buffered: boolean;
8
9
  chunks: any[];
10
+ emitted: boolean;
9
11
  ended: boolean;
10
12
  rootIsArray: boolean;
11
13
  rootHasPairs: boolean;
@@ -53,7 +55,8 @@ declare class JoslStreamWriter {
53
55
  */
54
56
  comment(text: string): this;
55
57
  /**
56
- * The document text emitted so far.
58
+ * The document text emitted so far; `''` under `buffer: false`, whose
59
+ * chunks went to the sink and nowhere else.
57
60
  * @returns {string} Concatenated chunks
58
61
  */
59
62
  text(): string;
@@ -66,12 +69,15 @@ declare class JoslStreamWriter {
66
69
  /**
67
70
  * Create a streaming JOSL/TOML writer - the write-side mirror of
68
71
  * `createStreamReader`. Chunks are delivered through `onChunk` as they
69
- * are produced and also accumulate for `text()` / `end()`.
72
+ * are produced and also accumulate for `text()` / `end()` — unless
73
+ * `buffer: false`, which keeps no copy: the sink is the only holder.
70
74
  * @param {object} [options] - Writer options
71
75
  * @param {'josl'|'toml'} [options.mode] - 'toml' emits strict TOML 1.0
72
76
  * @param {'error'|'omit'} [options.onNull] - See `stringifyJosl`
73
77
  * @param {'error'|'string'} [options.onRegExp] - See `stringifyJosl`
74
78
  * @param {(chunk: string) => void} [options.onChunk] - Chunk sink
79
+ * @param {boolean} [options.buffer] - `false` retains no emitted text
80
+ * (needs `onChunk`); `text()` and `end()` then answer `''`
75
81
  * @returns {JoslStreamWriter} The writer
76
82
  */
77
83
  export declare function createStreamWriter(options?: {
@@ -79,6 +85,7 @@ export declare function createStreamWriter(options?: {
79
85
  onNull?: 'error' | 'omit';
80
86
  onRegExp?: 'error' | 'string';
81
87
  onChunk?: (chunk: string) => void;
88
+ buffer?: boolean;
82
89
  }): JoslStreamWriter;
83
90
  /**
84
91
  * Serialize a value as an iterable of text chunks: one chunk per record
@@ -89,4 +96,19 @@ export declare function createStreamWriter(options?: {
89
96
  * @yields {string} Document chunks, in order
90
97
  */
91
98
  export declare function stringifyJoslChunks(value: object | any[], options?: object): Generator<string, void, unknown>;
99
+ /**
100
+ * Serialize an async iterable of table records as an async iterable of
101
+ * `[[]]` chunks — the pull form of `stringifyJoslChunks` over a root
102
+ * array, byte-identical to it for the same records. Pull is the
103
+ * backpressure: the next record is requested only when the consumer
104
+ * asks for the next chunk. `options.signal` aborts between pulls (the
105
+ * rejection is the signal's reason); an abort, a consumer that stops
106
+ * early or a throw closes the record source exactly once.
107
+ * @param {AsyncIterable<object>|Iterable<object>} records - Table records
108
+ * @param {object} [options] - Writer options; see `stringifyJosl`, plus `signal`
109
+ * @yields {string} One `[[]]` record per chunk
110
+ * @example
111
+ * response.body = stringifyJoslStream(store.collection('rows').query(doc), { signal });
112
+ */
113
+ export declare function stringifyJoslStream(records: AsyncIterable<object> | Iterable<object>, options?: object): AsyncGenerator<string, void, unknown>;
92
114
  export { JoslStringifyError } from './errors.js';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@jarenjs/josl",
3
3
  "private": false,
4
- "version": "0.56.0",
4
+ "version": "0.66.1",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
7
7
  "types": "./dist/types/index.d.ts",
@@ -99,6 +99,6 @@
99
99
  "./package.json": "./package.json"
100
100
  },
101
101
  "dependencies": {
102
- "@jarenjs/core": "^0.56.0"
102
+ "@jarenjs/core": "^0.66.1"
103
103
  }
104
104
  }