@jarenjs/josl 0.34.0

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.
@@ -0,0 +1,97 @@
1
+ export declare class JoslMachine {
2
+ mode: string;
3
+ onEvent: ((event: object) => void) | null;
4
+ onLine: any;
5
+ lineValueStart: number;
6
+ lineValueEnd: number;
7
+ buf: string;
8
+ scanPos: number;
9
+ scanState: number;
10
+ scanDepth: number;
11
+ scanNl: number;
12
+ startLine: number;
13
+ lineOrigin: number;
14
+ started: boolean;
15
+ ended: boolean;
16
+ rootValue: {} | undefined;
17
+ rootIsArray: boolean;
18
+ current: any;
19
+ currentPath: any[];
20
+ meta: WeakMap<WeakKey, any>;
21
+ line: string;
22
+ /**
23
+ * @param {object} [options] - Reader options
24
+ * @param {'josl'|'toml'} [options.mode] - 'toml' rejects JOSL extensions
25
+ * @param {(event: object) => void} [options.onEvent] - Document-order
26
+ * event sink: {type:'table'|'table-array'|'root-item'|'pair', path, ...}
27
+ */
28
+ constructor(options?: {
29
+ mode?: 'josl' | 'toml';
30
+ onEvent?: (event: object) => void;
31
+ });
32
+ /**
33
+ * Feed the next chunk of source text; chunks may split any token.
34
+ * @param {string} chunk - Next piece of the document
35
+ * @returns {this} The machine, for chaining
36
+ */
37
+ feed(chunk: string): this;
38
+ /**
39
+ * Finish the document, flushing any pending logical line.
40
+ * @returns {*} The completed root value
41
+ */
42
+ end(): any;
43
+ /**
44
+ * Parse a complete document in one pass. Every value parser already stops
45
+ * at the newlines TOML forbids a construct from crossing, so with the
46
+ * whole text in hand the parser finds each logical line's end itself and
47
+ * the cutter's separate pass over the source is not needed. `feed`/`end`
48
+ * keep the cutter because a chunk can stop mid-token, where only a
49
+ * side-effect-free pre-pass can decide whether a line is complete.
50
+ * @param {string} text - The entire document
51
+ * @returns {*} The completed root value
52
+ */
53
+ parseAll(text: string): any;
54
+ /**
55
+ * The (possibly still growing) root value: `{}`-rooted for documents,
56
+ * `[]`-rooted after a `[[]]` header. Undefined content yields `{}`.
57
+ * @returns {*} Current root value
58
+ */
59
+ root(): any;
60
+ scan(): void;
61
+ cutLine(buf: any, start: any, nlPos: any, innerNl: any): void;
62
+ err(pos: any, message: any, hint: any): void;
63
+ emit(event: any): void;
64
+ parseLine(line: any, pos?: number): any;
65
+ skipWs(line: any, pos: any): any;
66
+ skipWsNlComment(line: any, pos: any): any;
67
+ expectLineEnd(line: any, pos: any): any;
68
+ checkComment(line: any, pos: any): any;
69
+ parseHeader(line: any, pos: any): any;
70
+ headerBase(): any[];
71
+ navigate(keys: any, pos: any): any[];
72
+ openTable(keys: any, pos: any): void;
73
+ openArrayTable(keys: any, pos: any): void;
74
+ openRootItem(pos: any): void;
75
+ parsePair(line: any, pos: any): any;
76
+ assignPair(keys: any, value: any, pos: any): void;
77
+ parseKeys(line: any, pos: any): any[];
78
+ parseValue(line: any, pos: any): any;
79
+ checkValueEnd(line: any, pos: any): any;
80
+ parseWord(line: any, pos: any): any[] | undefined;
81
+ parseSignedWord(line: any, pos: any): any[] | undefined;
82
+ decodeEscape(line: any, pos: any): any[] | undefined;
83
+ decodeUnicodeEscape(line: any, pos: any, width: any): any[];
84
+ checkStringChar(line: any, pos: any, multiline: any): void;
85
+ parseBasicString(line: any, pos: any): any[] | undefined;
86
+ parseLiteralString(line: any, pos: any): any[] | undefined;
87
+ parseMlBasicString(line: any, pos: any): any[] | undefined;
88
+ parseMlLiteralString(line: any, pos: any): any[] | undefined;
89
+ parseArray(line: any, pos: any): any;
90
+ parseInlineTable(line: any, pos: any): any[];
91
+ assignInline(obj: any, keys: any, value: any, pos: any): void;
92
+ parseRegExp(line: any, pos: any): any[] | undefined;
93
+ parseDateTimeOrNumber(line: any, pos: any): any[];
94
+ bigIntCheck(pos: any, suffix: any): boolean;
95
+ intValue(pos: any, source: any, big: any, m0: any): number | bigint;
96
+ parseNumber(line: any, pos: any): any[];
97
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Parse a complete JOSL document.
3
+ * @param {string} text - JOSL source text
4
+ * @param {object} [options] - Reader options
5
+ * @param {'josl'|'toml'} [options.mode] - 'toml' rejects JOSL extensions
6
+ * (null, bigint, regexp literals, root arrays) for strict TOML 1.0 input
7
+ * @param {(event: object) => void} [options.onEvent] - Document-order
8
+ * event sink; see `createStreamReader`
9
+ * @returns {object|Array} The root table, or root array for [[]] documents
10
+ * @throws {import('./errors.js').JoslSyntaxError} On invalid input
11
+ */
12
+ export declare function parseJosl(text: string, options?: {
13
+ mode?: 'josl' | 'toml';
14
+ onEvent?: (event: object) => void;
15
+ }): object | any[];
16
+ /**
17
+ * Parse a complete document in strict TOML 1.0 mode.
18
+ * @param {string} text - TOML source text
19
+ * @param {object} [options] - Reader options minus `mode`
20
+ * @returns {object} The root table
21
+ */
22
+ export declare function parseToml(text: string, options?: object): object;
23
+ export { JoslSyntaxError } from './errors.js';
24
+ export { LocalDate, LocalTime, LocalDateTime } from './values.js';
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Create an incremental JOSL/TOML reader.
3
+ *
4
+ * Events, in document order:
5
+ * {type:'table', path, line} - a [header] opened
6
+ * {type:'table-array', path, line} - a [[header]] appended
7
+ * {type:'root-item', path, index, line} - a [[]] element started
8
+ * {type:'pair', path, key, value, line} - a key-value completed
9
+ *
10
+ * @param {object} [options] - Reader options
11
+ * @param {'josl'|'toml'} [options.mode] - 'toml' rejects JOSL extensions
12
+ * @param {(event: object) => void} [options.onEvent] - Event sink
13
+ * @returns {{feed(chunk: string): void, end(): *, root(): *}} The reader:
14
+ * `feed` accepts chunks that may split any token, `end` flushes and
15
+ * returns the completed root, `root` peeks at the partial result.
16
+ */
17
+ export declare function createStreamReader(options?: {
18
+ mode?: 'josl' | 'toml';
19
+ onEvent?: (event: object) => void;
20
+ }): {
21
+ feed(chunk: string): void;
22
+ end(): any;
23
+ root(): any;
24
+ };
25
+ /**
26
+ * Parse an async iterable of string chunks (e.g. an LLM output stream).
27
+ * @param {AsyncIterable<string>|Iterable<string>} chunks - Source chunks
28
+ * @param {object} [options] - Reader options; see `createStreamReader`
29
+ * @returns {Promise<*>} The completed root value
30
+ */
31
+ export declare function parseJoslStream(chunks: AsyncIterable<string> | Iterable<string>, options?: object): Promise<any>;
32
+ export { JoslSyntaxError } from './errors.js';
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Whether a value serializes as a table (a plain object).
3
+ * @param {*} v - The value
4
+ * @returns {boolean} True for plain objects
5
+ */
6
+ export declare function isPlainTable(v: any): boolean;
7
+ /**
8
+ * Format a single key (bare when possible, quoted otherwise).
9
+ * @param {string} key - The key
10
+ * @returns {string} JOSL/TOML key text
11
+ */
12
+ export declare function formatKey(key: string): string;
13
+ /**
14
+ * Format a dotted key path.
15
+ * @param {string[]} path - Key path segments
16
+ * @returns {string} Dotted key path text
17
+ */
18
+ export declare function formatKeyPath(path: string[]): string;
19
+ /**
20
+ * Format a single value (scalars, inline arrays, inline tables).
21
+ * @param {*} value - The value
22
+ * @param {object} [options] - Writer options; see `stringifyJosl`
23
+ * @param {(string|number)[]} [path] - Error-reporting path
24
+ * @returns {string} JOSL/TOML value text
25
+ * @throws {JoslStringifyError} When the value cannot be represented
26
+ */
27
+ export declare function formatValue(value: any, options?: object, path?: (string | number)[]): string;
28
+ /**
29
+ * Format a table body: its pairs followed by nested `[header]` /
30
+ * `[[header]]` sections, with headers made relative to `headerPath`.
31
+ * @param {object} obj - A plain object table
32
+ * @param {object} [options] - Writer options; see `stringifyJosl`
33
+ * @param {string[]} [headerPath] - Prefix for nested section headers
34
+ * @returns {string} Section text (newline terminated, may be empty)
35
+ * @throws {JoslStringifyError} When a value cannot be represented
36
+ */
37
+ export declare function formatSection(obj: object, options?: object, headerPath?: string[]): string;
38
+ /**
39
+ * Serialize a value to JOSL (or strict TOML) text.
40
+ * @param {object|Array} value - A plain object root, or (JOSL mode only)
41
+ * an array of plain objects for a [[]] root-array document
42
+ * @param {object} [options] - Writer options
43
+ * @param {'josl'|'toml'} [options.mode] - 'toml' emits strict TOML 1.0
44
+ * @param {'error'|'omit'} [options.onNull] - TOML mode: what to do with
45
+ * null table values (array elements always error)
46
+ * @param {'error'|'string'} [options.onRegExp] - TOML mode: represent
47
+ * regexps as strings, or error
48
+ * @returns {string} The serialized document, newline terminated
49
+ * @throws {JoslStringifyError} When the value cannot be represented
50
+ */
51
+ export declare function stringifyJosl(value: object | any[], options?: {
52
+ mode?: 'josl' | 'toml';
53
+ onNull?: 'error' | 'omit';
54
+ onRegExp?: 'error' | 'string';
55
+ }): string;
56
+ /**
57
+ * Serialize a value to strict TOML 1.0 text.
58
+ * @param {object} value - A plain object root
59
+ * @param {object} [options] - Writer options minus `mode`
60
+ * @returns {string} The serialized document
61
+ */
62
+ export declare function stringifyToml(value: object, options?: object): string;
63
+ export { JoslStringifyError } from './errors.js';
@@ -0,0 +1,56 @@
1
+ /** `YYYY-MM-DD` with an optional time half. @type {RegExp} */
2
+ export declare const RE_DATETIME: RegExp;
3
+ /** A bare `HH:MM:SS` with an optional fraction. @type {RegExp} */
4
+ export declare const RE_TIMEONLY: RegExp;
5
+ /**
6
+ * Run a sticky regex at `pos` and return its match (or null).
7
+ * @param {RegExp} re - A sticky (`y`) pattern
8
+ * @param {string} text - The text to match against
9
+ * @param {number} pos - Position the match must start at
10
+ * @returns {RegExpExecArray | null} The match, anchored at `pos`
11
+ */
12
+ export declare function stickyExec(re: RegExp, text: string, pos: number): RegExpExecArray | null;
13
+ /**
14
+ * Shared `feed(chunk)` body for the buffering stream machines (JOSL and
15
+ * CSV): guard against feeding after `end()`, strip a leading BOM on the
16
+ * first non-empty chunk, then buffer and scan. The JSONX stream reader
17
+ * has its own `feed` on purpose - it pumps a token loop and models BOM
18
+ * handling differently.
19
+ * @template {{ ended: boolean, started: boolean, buf: string, scan: () => void }} T
20
+ * @param {T} machine - The stream machine (`this` of its `feed`)
21
+ * @param {string} chunk - Next piece of the document
22
+ * @returns {T} The machine, for chaining
23
+ */
24
+ export declare function feedMachine<T extends {
25
+ ended: boolean;
26
+ started: boolean;
27
+ buf: string;
28
+ scan: () => void;
29
+ }>(machine: T, chunk: string): T;
30
+ /**
31
+ * Shared `parseAll(text)` prelude for the buffering stream machines:
32
+ * reject mixing with `feed()`/`end()`, mark the machine started and
33
+ * ended, and strip a leading BOM.
34
+ * @param {{ ended: boolean, started: boolean }} machine - The stream machine
35
+ * @param {string} text - The entire document
36
+ * @returns {string} The text with any leading BOM removed
37
+ */
38
+ export declare function beginParseAll(machine: {
39
+ ended: boolean;
40
+ started: boolean;
41
+ }, text: string): string;
42
+ /**
43
+ * Read an own property, ignoring the prototype chain.
44
+ * @param {object} obj - Source object
45
+ * @param {string} key - Member name
46
+ * @returns {*} The own value or undefined
47
+ */
48
+ export declare function getOwn(obj: object, key: string): any;
49
+ /**
50
+ * Compute the 1-based column of `pos` inside `str` (columns restart after
51
+ * every newline; multi-line logical lines report positions within them).
52
+ * @param {string} str - Input text
53
+ * @param {number} pos - Offset into the text
54
+ * @returns {number} 1-based column number
55
+ */
56
+ export declare function columnOf(str: string, pos: number): number;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * A TOML/JOSL local date (no time, no offset), e.g. `1979-05-27`.
3
+ */
4
+ export declare class LocalDate {
5
+ year: number;
6
+ month: number;
7
+ day: number;
8
+ /**
9
+ * @param {number} year - Full year
10
+ * @param {number} month - 1-based month
11
+ * @param {number} day - 1-based day of month
12
+ */
13
+ constructor(year: number, month: number, day: number);
14
+ toString(): string;
15
+ toJSON(): string;
16
+ }
17
+ /**
18
+ * A TOML/JOSL local time (no date, no offset), e.g. `07:32:00.999`.
19
+ * The sub-second part is kept as the literal fraction string (including
20
+ * the leading dot, or `''`) so precision round-trips exactly.
21
+ */
22
+ export declare class LocalTime {
23
+ hour: number;
24
+ minute: number;
25
+ second: number;
26
+ fraction: string;
27
+ /**
28
+ * @param {number} hour - 0-23
29
+ * @param {number} minute - 0-59
30
+ * @param {number} second - 0-60 (60 allows leap seconds)
31
+ * @param {string} [fraction] - Literal fraction incl. leading dot, or ''
32
+ */
33
+ constructor(hour: number, minute: number, second: number, fraction?: string);
34
+ toString(): string;
35
+ toJSON(): string;
36
+ }
37
+ /**
38
+ * A TOML/JOSL local date-time (no offset), e.g. `1979-05-27T07:32:00`.
39
+ */
40
+ export declare class LocalDateTime {
41
+ date: LocalDate;
42
+ time: LocalTime;
43
+ /**
44
+ * @param {LocalDate} date - The date part
45
+ * @param {LocalTime} time - The time part
46
+ */
47
+ constructor(date: LocalDate, time: LocalTime);
48
+ toString(): string;
49
+ toJSON(): string;
50
+ }
51
+ export { isDateOnlyInRange as isValidDateParts } from '@jarenjs/core/dates';
52
+ /**
53
+ * Whether hour/minute/second form a valid time-of-day (second 60 is
54
+ * accepted for leap seconds).
55
+ * @param {number} hour - Hours
56
+ * @param {number} minute - Minutes
57
+ * @param {number} second - Seconds
58
+ * @returns {boolean} True when in range
59
+ */
60
+ export declare function isValidTimeParts(hour: number, minute: number, second: number): boolean;
@@ -0,0 +1,92 @@
1
+ declare class JoslStreamWriter {
2
+ options: {
3
+ mode: string;
4
+ onNull: any;
5
+ onRegExp: any;
6
+ };
7
+ onChunk: any;
8
+ chunks: any[];
9
+ ended: boolean;
10
+ rootIsArray: boolean;
11
+ rootHasPairs: boolean;
12
+ sawSection: boolean;
13
+ sectionKeys: Set<any>;
14
+ headers: Set<any>;
15
+ headerScope: number;
16
+ constructor(options?: {});
17
+ emit(chunk: any): void;
18
+ guard(): void;
19
+ blank(): void;
20
+ toPath(path: any): any[];
21
+ openSection(keys: any, wrap: any): void;
22
+ /**
23
+ * Emit a key-value pair into the current section.
24
+ * @param {string|string[]} key - A key, or key segments for dotted keys
25
+ * @param {*} value - The value
26
+ * @returns {this} The writer, for chaining
27
+ */
28
+ pair(key: string | string[], value: any): this;
29
+ /**
30
+ * Open a `[table]` section.
31
+ * @param {string|string[]} path - Header path
32
+ * @returns {this} The writer, for chaining
33
+ */
34
+ table(path: string | string[]): this;
35
+ /**
36
+ * Append a `[[table-array]]` element.
37
+ * @param {string|string[]} path - Header path
38
+ * @returns {this} The writer, for chaining
39
+ */
40
+ tableArray(path: string | string[]): this;
41
+ /**
42
+ * Start a `[[]]` root-array element (JOSL mode only). With a record
43
+ * argument the whole table body is emitted at once - the natural
44
+ * unit for streaming one record per completed result.
45
+ * @param {object} [record] - Optional complete record to emit
46
+ * @returns {this} The writer, for chaining
47
+ */
48
+ rootItem(record?: object): this;
49
+ /**
50
+ * Emit a comment line (multi-line text becomes multiple comments).
51
+ * @param {string} text - Comment text
52
+ * @returns {this} The writer, for chaining
53
+ */
54
+ comment(text: string): this;
55
+ /**
56
+ * The document text emitted so far.
57
+ * @returns {string} Concatenated chunks
58
+ */
59
+ text(): string;
60
+ /**
61
+ * Finish the document.
62
+ * @returns {string} The complete document text
63
+ */
64
+ end(): string;
65
+ }
66
+ /**
67
+ * Create a streaming JOSL/TOML writer - the write-side mirror of
68
+ * `createStreamReader`. Chunks are delivered through `onChunk` as they
69
+ * are produced and also accumulate for `text()` / `end()`.
70
+ * @param {object} [options] - Writer options
71
+ * @param {'josl'|'toml'} [options.mode] - 'toml' emits strict TOML 1.0
72
+ * @param {'error'|'omit'} [options.onNull] - See `stringifyJosl`
73
+ * @param {'error'|'string'} [options.onRegExp] - See `stringifyJosl`
74
+ * @param {(chunk: string) => void} [options.onChunk] - Chunk sink
75
+ * @returns {JoslStreamWriter} The writer
76
+ */
77
+ export declare function createStreamWriter(options?: {
78
+ mode?: 'josl' | 'toml';
79
+ onNull?: 'error' | 'omit';
80
+ onRegExp?: 'error' | 'string';
81
+ onChunk?: (chunk: string) => void;
82
+ }): JoslStreamWriter;
83
+ /**
84
+ * Serialize a value as an iterable of text chunks: one chunk per record
85
+ * for a root array, a single chunk for a table root. Useful for piping
86
+ * record streams onward without building the full string.
87
+ * @param {object|Array} value - Same roots as `stringifyJosl`
88
+ * @param {object} [options] - Writer options; see `stringifyJosl`
89
+ * @yields {string} Document chunks, in order
90
+ */
91
+ export declare function stringifyJoslChunks(value: object | any[], options?: object): Generator<string, void, unknown>;
92
+ export { JoslStringifyError } from './errors.js';
package/package.json ADDED
@@ -0,0 +1,104 @@
1
+ {
2
+ "name": "@jarenjs/josl",
3
+ "private": false,
4
+ "version": "0.34.0",
5
+ "type": "module",
6
+ "main": "./src/index.js",
7
+ "types": "./dist/types/index.d.ts",
8
+ "sideEffects": false,
9
+ "description": "JOSL - JavaScript Obvious Streaming Language. A TOML 1.0 backward-compatible data language with null, bigint, regexp, datetimes and root arrays as first class citizens, plus JSONX, the same extensions over JSON, and a self-healing CSV reader/writer - with incremental streaming readers for JOSL, TOML, JSONX, strict JSON and CSV.",
10
+ "author": "joham",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/jklarenbeek/jarenjs.git",
14
+ "directory": "packages/josl"
15
+ },
16
+ "license": "MIT",
17
+ "engines": {
18
+ "node": ">=24"
19
+ },
20
+ "publishConfig": {
21
+ "access": "public",
22
+ "registry": "https://registry.npmjs.org/"
23
+ },
24
+ "keywords": [
25
+ "jaren",
26
+ "josl",
27
+ "toml",
28
+ "jsonx",
29
+ "json",
30
+ "csv",
31
+ "streaming",
32
+ "incremental",
33
+ "real-time",
34
+ "structured-output",
35
+ "parser",
36
+ "llm"
37
+ ],
38
+ "scripts": {
39
+ "build": "npm run build:types",
40
+ "build:types": "tsc -p tsconfig.json",
41
+ "prepack": "npm run build:types"
42
+ },
43
+ "files": [
44
+ "dist/types/",
45
+ "src/",
46
+ "schemas/",
47
+ "FORMAT.md"
48
+ ],
49
+ "exports": {
50
+ ".": {
51
+ "types": "./dist/types/index.d.ts",
52
+ "default": "./src/index.js"
53
+ },
54
+ "./parse": {
55
+ "types": "./dist/types/parse.d.ts",
56
+ "default": "./src/parse.js"
57
+ },
58
+ "./cst": {
59
+ "types": "./dist/types/cst.d.ts",
60
+ "default": "./src/cst.js"
61
+ },
62
+ "./gbnf": {
63
+ "types": "./dist/types/gbnf.d.ts",
64
+ "default": "./src/gbnf.js"
65
+ },
66
+ "./stream": {
67
+ "types": "./dist/types/stream.d.ts",
68
+ "default": "./src/stream.js"
69
+ },
70
+ "./stringify": {
71
+ "types": "./dist/types/stringify.d.ts",
72
+ "default": "./src/stringify.js"
73
+ },
74
+ "./write": {
75
+ "types": "./dist/types/write.d.ts",
76
+ "default": "./src/write.js"
77
+ },
78
+ "./jsonx": {
79
+ "types": "./dist/types/jsonx.d.ts",
80
+ "default": "./src/jsonx.js"
81
+ },
82
+ "./jsonx-stream": {
83
+ "types": "./dist/types/jsonx-stream.d.ts",
84
+ "default": "./src/jsonx-stream.js"
85
+ },
86
+ "./csv": {
87
+ "types": "./dist/types/csv.d.ts",
88
+ "default": "./src/csv.js"
89
+ },
90
+ "./csv-stream": {
91
+ "types": "./dist/types/csv-stream.d.ts",
92
+ "default": "./src/csv-stream.js"
93
+ },
94
+ "./values": {
95
+ "types": "./dist/types/values.d.ts",
96
+ "default": "./src/values.js"
97
+ },
98
+ "./schemas/*": "./schemas/*",
99
+ "./package.json": "./package.json"
100
+ },
101
+ "dependencies": {
102
+ "@jarenjs/core": "^0.34.0"
103
+ }
104
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://jarenjs.dev/schemas/jaren-josl-data/0.1",
4
+ "title": "JOSL document data model (JSON-safe subset)",
5
+ "description": "The JSON-safe data model of a JOSL document, for LLM structured output: a model emits JSON matching this schema and stringifyJosl renders canonical JOSL text (parseJosl round-trips it exactly). The subset is deliberate - JOSL's native date/time scalars parse to platform Date values, which JSON cannot carry; represent them as strings and parse downstream. The root is a table (object), exactly as in TOML.",
6
+ "type": "object",
7
+ "additionalProperties": { "$ref": "#/$defs/value" },
8
+ "$defs": {
9
+ "value": {
10
+ "description": "Any JSON-safe JOSL value: scalars, arrays, or nested tables.",
11
+ "anyOf": [
12
+ { "type": "string" },
13
+ { "type": "number" },
14
+ { "type": "boolean" },
15
+ { "type": "null" },
16
+ { "type": "array", "items": { "$ref": "#/$defs/value" } },
17
+ { "type": "object", "additionalProperties": { "$ref": "#/$defs/value" } }
18
+ ]
19
+ }
20
+ }
21
+ }