tjawn 0.0.0-stage → 2.0.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.
package/README.md CHANGED
@@ -1,3 +1,231 @@
1
- # Temporary Holding Version
1
+ # tjawn <img src="https://github.com/tjkopena/tjawn/raw/main/docs/shark.png" height="50" title="tjawn" alt='Drawing of a shark stabbing out of the water and growling "Rwaa rwaa, rwaa rwaa!" as it tries to eat you.' style="vertical-align: middle" />
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ This is an implementation of *jawns*, a human friendly lightweight
4
+ object notation. A jawn is a Python dict literal or a slight superset
5
+ of JSON but with more flexible formatting. An example:
6
+
7
+ ```
8
+ protocol: 1.0,
9
+ token: 854c37ee-517c-4ba6-b56b-06475ad24041
10
+ conn: {
11
+ offer= { 'TLS1.2', TLS1.3 "TLS 1.17" }
12
+ "dns:resolver" = 10.0.0.1
13
+ cert: """
14
+ -----BEGIN CERTIFICATE-----
15
+ MIICUTCCAfugAwIBAgIBADANBgkqhkiG9w0BAQQFADBXMQswCQYDVQQGEwJDTjEL
16
+ MAkGA1UECBMCUE4xCzAJBgNVBAcTAkNOMQswCQYDVQQKEwJPTjELMAkGA1UECxMC
17
+ VU4xFDASBgNVBAMTC0hlcm9uZyBZYW5nMB4XDTA1MDcxNTIxMTk0N1oXDTA1MDgx
18
+ ...
19
+ """
20
+ }
21
+ ```
22
+
23
+ Which parses to the equivalent of this Python literal:
24
+ ```python
25
+ {
26
+ "protocol": 1.0,
27
+ "token": "854c37ee-517c-4ba6-b56b-06475ad24041",
28
+ "conn": {
29
+ "offer": {"TLS1.2", "TLS1.3", "TLS 1.17"},
30
+ "dns:resolver": "10.0.0.1",
31
+ "cert": """-----BEGIN CERTIFICATE-----
32
+ MIICUTCCAfugAwIBAgIBADANBgkqhkiG9w0BAQQFADBXMQswCQYDVQQGEwJDTjEL
33
+ MAkGA1UECBMCUE4xCzAJBgNVBAcTAkNOMQswCQYDVQQKEwJPTjELMAkGA1UECxMC
34
+ VU4xFDASBgNVBAMTC0hlcm9uZyBZYW5nMB4XDTA1MDcxNTIxMTk0N1oXDTA1MDgx
35
+ ..."""
36
+ }
37
+ }
38
+ ```
39
+
40
+ Some features:
41
+ * Strings without whitespace generally do not need to be quoted.
42
+ * Single or double quotes used interchangeably (in matched pairs).
43
+ * Multiline whitespace trimming with relative indentation preservation.
44
+ * Key/value pairs interchangeably denoted by either ':' or '='.
45
+ * Commas largely optional but permitted (e.g., on closing elements).
46
+ * Objects, sets, and lists are delimited by brackets, not whitespace.
47
+ * Sets are supported.
48
+
49
+ > _"jawn?"_
50
+
51
+ JSON Alternative Written Notation?\
52
+ Joe's Awesome Wacky ... Notation?\
53
+ [Don't think about it too hard.](https://en.wikipedia.org/wiki/Jawn)
54
+
55
+
56
+ > _"But why?"_
57
+
58
+ I (tjkopena) find it _almost_ convenient to write simple config files
59
+ or parameter sets and so on in JSON. I don't implement such often
60
+ enough to bother to remember how to format TOML or YAML or whateverML,
61
+ and in any event I dislike their whitespace-based formatting, nesting,
62
+ and other aspects. I know JSON, use it all the time, I just want to
63
+ write a little object file in it. But it's _not actually_ convenient
64
+ to do so because JSON is a picky, pedantic pain in the ass---double
65
+ quotes on everything, no trailing commas, blah blah blah GTFO. Hence
66
+ jawns, a similarly simple, straightforward object model and syntax
67
+ that's not too fussed about the details.
68
+
69
+
70
+ ## jawns
71
+
72
+ jawns and their closely associated human-friendly text syntax are
73
+ specified in [](docs/jawn.md). The core description is pretty readable.
74
+ A grammar is in [](src/jawn.g).
75
+
76
+ In sum: A jawn is a map; its values are recursive collections maps,
77
+ lists, or sets, or primitives strings, numbers, booleans, or null; you
78
+ can write them down without lots of double quotes and without having to
79
+ precisely maintain whitespace.
80
+
81
+
82
+ ## Implementations
83
+
84
+ This repository includes several implementations, outlined here.
85
+
86
+ ### Python
87
+
88
+ #### Setup
89
+
90
+ tjawn's Python module has no dependencies. It is [listed in
91
+ PyPi](https://pypi.org/project/tjawn/) and should be includable in
92
+ all the usual ways.
93
+
94
+ #### Usage
95
+
96
+ The `loads` and `dumps` functions respectively provide for reading and
97
+ writing jawns from a string.
98
+
99
+ ```
100
+ import tjawn
101
+
102
+ o = tjawn.loads('a: "Four score and seven..."')
103
+ print(tjawn.dumps(o))
104
+ ```
105
+
106
+ A jawn is a map. As a convenience, a list of jawns, e.g. a file of
107
+ several records, can be read and written with separate functions:
108
+
109
+ ```
110
+ l = tjawn.loads_list('[ {name: alice} {name: bob} ]')
111
+ print(tjawn.dumps_list(l))
112
+ ```
113
+
114
+ The `loadf`, `dumpf`, `loadf_list`, and `dumpf_list` functions do the
115
+ same for UTF-8 files named by a filename parameter:
116
+
117
+ ```
118
+ tjawn.dumpf(o, 'config.jawn')
119
+ o = tjawn.loadf('config.jawn')
120
+ ```
121
+
122
+ #### Development Notes
123
+
124
+ Internally, tjawn's Python module is implemented using Lark to
125
+ generate the lexer and parser, which are then vendored in the project
126
+ rather than incurring a dependency. From the `src/py` directory:
127
+
128
+ ```bash
129
+ poetry install --no-root # install pytest and lark for development
130
+ make parser # regenerate tjawn/lark_parser.py from src/jawn.g
131
+ make test # run the tests
132
+ make build # build the package, including this README
133
+ ```
134
+
135
+ The tests check that the vendored parser matches `src/jawn.g`.
136
+
137
+ ### TypeScript
138
+
139
+ #### Setup
140
+
141
+ tjawn's TypeScript package, in `src/ts`, has no runtime dependencies.
142
+ It is an ES module with type declarations. Parsing and serializing
143
+ work in any ES2022 environment, including browsers; the file functions
144
+ require Node.js 20.16 or later.
145
+
146
+ #### Usage
147
+
148
+ The `parse` and `stringify` functions respectively provide for reading
149
+ and writing jawns from a string, as `JSON.parse` and `JSON.stringify`
150
+ do for JSON.
151
+
152
+ ```
153
+ import * as tjawn from 'tjawn';
154
+
155
+ const o = tjawn.parse('a: "Four score and seven..."');
156
+ console.log(tjawn.stringify(o));
157
+ ```
158
+
159
+ Maps are parsed to plain objects and lists to arrays. Sets are parsed
160
+ to `JawnSet`s, which compare their elements by value, e.g. `{ {a: 1} }`
161
+ holds one map that `has({ a: 1 })`, and keep `true` distinct from `1`.
162
+ Numbers are JavaScript numbers, so `1.0` is read as `1` and written that
163
+ way. `jawnEquals` compares two values as jawns.
164
+
165
+ `stringify` writes objects, `Map`s, arrays, `JawnSet`s, and `Set`s.
166
+ Class instances are written as maps of their own enumerable properties
167
+ not starting with `_`, and properties whose value is `undefined` are
168
+ skipped. Options `indent` and `eoln` set the indentation and
169
+ linebreak; with an empty `eoln` all is written on one line.
170
+
171
+ A jawn is a map. As a convenience, a list of jawns, e.g. a file of
172
+ several records, can be read and written with separate functions:
173
+
174
+ ```
175
+ const l = tjawn.parseList('[ {name: alice} {name: bob} ]');
176
+ console.log(tjawn.stringifyList(l));
177
+ ```
178
+
179
+ The `loadFile`, `dumpFile`, `loadFileList`, and `dumpFileList`
180
+ functions do the same for UTF-8 files named by a path or file URL:
181
+
182
+ ```
183
+ tjawn.dumpFile(o, 'config.jawn');
184
+ const o = tjawn.loadFile('config.jawn');
185
+ ```
186
+
187
+ Errors are thrown as `JawnError`, or its subclass `JawnTypeError` for
188
+ values that can't be written as jawns.
189
+
190
+ #### Development Notes
191
+
192
+ The parser is written by hand, with a contextual lexer whose terminals
193
+ are transcribed from `src/jawn.g` and that mirrors Lark's, so that it
194
+ accepts exactly what the Python module does and reports errors the
195
+ same way. From the `src/ts` directory:
196
+
197
+ ```bash
198
+ npm install # install TypeScript and Vitest for development
199
+ npm test # run the tests
200
+ npm run typecheck # check types, including the tests'
201
+ npm run build # build the package into dist
202
+ npm pack # package it, including this README
203
+ ```
204
+
205
+ The tests check that the lexer's terminals match `src/jawn.g`.
206
+
207
+
208
+ ## License
209
+
210
+ tjawn is released under MIT-0:
211
+
212
+ > Copyright 2023, 2026 tjkopena
213
+ >
214
+ > Permission is hereby granted, free of charge, to any person
215
+ > obtaining a copy of this software and associated documentation files
216
+ > (the "Software"), to deal in the Software without restriction,
217
+ > including without limitation the rights to use, copy, modify, merge,
218
+ > publish, distribute, sublicense, and/or sell copies of the Software,
219
+ > and to permit persons to whom the Software is furnished to do so.
220
+ >
221
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
222
+ > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
223
+ > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
224
+ > NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
225
+ > BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
226
+ > ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
227
+ > CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
228
+ > SOFTWARE.
229
+
230
+ [Shark icon](https://thenounproject.com/icon/shark-1257563/) by Luis
231
+ Prado for The Noun Project, CCBY3.0.
@@ -0,0 +1,7 @@
1
+ /** Thrown for text that isn't a valid jawn, or data that can't be one. */
2
+ export declare class JawnError extends Error {
3
+ constructor(message: string);
4
+ }
5
+ /** Thrown for data of a type that can't be a jawn value. */
6
+ export declare class JawnTypeError extends JawnError {
7
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,10 @@
1
+ /** Thrown for text that isn't a valid jawn, or data that can't be one. */
2
+ export class JawnError extends Error {
3
+ constructor(message) {
4
+ super(message);
5
+ this.name = new.target.name;
6
+ }
7
+ }
8
+ /** Thrown for data of a type that can't be a jawn value. */
9
+ export class JawnTypeError extends JawnError {
10
+ }
@@ -0,0 +1,10 @@
1
+ import { StringifyOptions } from './serializer.js';
2
+ import type { Jawn } from './types.js';
3
+ /** Parse a jawn from a UTF-8 file. */
4
+ export declare function loadFile(path: string | URL): Jawn;
5
+ /** Parse a list of jawns from a UTF-8 file. */
6
+ export declare function loadFileList(path: string | URL): Jawn[];
7
+ /** Serialize a jawn to a UTF-8 file, ending with a linebreak. */
8
+ export declare function dumpFile(o: unknown, path: string | URL, options?: StringifyOptions): void;
9
+ /** Serialize a list of jawns to a UTF-8 file, ending with a linebreak. */
10
+ export declare function dumpFileList(o: unknown, path: string | URL, options?: StringifyOptions): void;
package/dist/files.js ADDED
@@ -0,0 +1,52 @@
1
+ import { JawnError } from './errors.js';
2
+ import { parse, parseList } from './parser.js';
3
+ import { stringify, stringifyList } from './serializer.js';
4
+ // Node's modules are obtained when called rather than imported, so that
5
+ // the rest of the package works where they're absent, e.g. browsers.
6
+ function node() {
7
+ const getBuiltinModule = globalThis.process?.getBuiltinModule;
8
+ if (!getBuiltinModule) {
9
+ throw new Error('Reading and writing jawn files requires Node.js 20.16 or later');
10
+ }
11
+ return {
12
+ fs: getBuiltinModule('node:fs'),
13
+ url: getBuiltinModule('node:url'),
14
+ };
15
+ }
16
+ /** Parse a jawn from a UTF-8 file. */
17
+ export function loadFile(path) {
18
+ return load(parse, path);
19
+ }
20
+ /** Parse a list of jawns from a UTF-8 file. */
21
+ export function loadFileList(path) {
22
+ return load(parseList, path);
23
+ }
24
+ function load(f, path) {
25
+ // Linebreaks are read untranslated, as the parser handles them, and
26
+ // errors name the file.
27
+ const { fs, url } = node();
28
+ const text = fs.readFileSync(path, 'utf8');
29
+ try {
30
+ return f(text);
31
+ }
32
+ catch (e) {
33
+ if (e instanceof JawnError) {
34
+ const name = path instanceof URL ? url.fileURLToPath(path) : path;
35
+ throw new JawnError(`${name}: ${e.message}`);
36
+ }
37
+ throw e;
38
+ }
39
+ }
40
+ /** Serialize a jawn to a UTF-8 file, ending with a linebreak. */
41
+ export function dumpFile(o, path, options = {}) {
42
+ dump(stringify(o, options), path, options);
43
+ }
44
+ /** Serialize a list of jawns to a UTF-8 file, ending with a linebreak. */
45
+ export function dumpFileList(o, path, options = {}) {
46
+ dump(stringifyList(o, options), path, options);
47
+ }
48
+ function dump(s, path, options) {
49
+ // The text is serialized before the file is opened so that an error
50
+ // leaves any existing file untouched.
51
+ node().fs.writeFileSync(path, s + (options.eoln || '\n'), 'utf8');
52
+ }
@@ -0,0 +1,7 @@
1
+ export { JawnError, JawnTypeError } from './errors.js';
2
+ export { JawnSet, jawnEquals } from './jawnset.js';
3
+ export { parse, parseList } from './parser.js';
4
+ export { stringify, stringifyList } from './serializer.js';
5
+ export type { StringifyOptions } from './serializer.js';
6
+ export { loadFile, loadFileList, dumpFile, dumpFileList } from './files.js';
7
+ export type { Jawn, JawnMap, JawnPrimitive, JawnValue } from './types.js';
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { JawnError, JawnTypeError } from './errors.js';
2
+ export { JawnSet, jawnEquals } from './jawnset.js';
3
+ export { parse, parseList } from './parser.js';
4
+ export { stringify, stringifyList } from './serializer.js';
5
+ export { loadFile, loadFileList, dumpFile, dumpFileList } from './files.js';
@@ -0,0 +1,55 @@
1
+ import type { JawnValue } from './types.js';
2
+ /**
3
+ * The canonical form of a jawn value, a string that's equal for two
4
+ * values exactly when they're equal as jawns: maps and sets compare
5
+ * regardless of order, numbers by value (so -0 is 0), and booleans are
6
+ * distinct from numbers. Throws a JawnError for a value that can't be a
7
+ * jawn value.
8
+ */
9
+ export declare function jawnKey(v: unknown): string;
10
+ /** Whether two values are equal as jawn values; false if either isn't one. */
11
+ export declare function jawnEquals(a: unknown, b: unknown): boolean;
12
+ /**
13
+ * A set of jawn values compared by value and type.
14
+ *
15
+ * Unlike the builtin Set, elements are compared by their content rather
16
+ * than identity, so maps and lists may be elements, and booleans are
17
+ * distinct from numbers. Elements are compared by their content when
18
+ * added, so mutating one afterwards leaves the set inconsistent. Methods
19
+ * taking another collection compare by jawn value too, e.g.
20
+ * `new JawnSet([true]).isSubsetOf(new Set([1]))` is false.
21
+ */
22
+ export declare class JawnSet<T = JawnValue> implements Iterable<T> {
23
+ private items;
24
+ constructor(items?: Iterable<T> | null);
25
+ get size(): number;
26
+ get [Symbol.toStringTag](): string;
27
+ /** Adds an element, which must be a jawn value. */
28
+ add(x: T): this;
29
+ /** Like an absent element, a non-jawn value is simply not present. */
30
+ has(x: unknown): boolean;
31
+ delete(x: unknown): boolean;
32
+ clear(): void;
33
+ values(): IterableIterator<T>;
34
+ keys(): IterableIterator<T>;
35
+ entries(): IterableIterator<[T, T]>;
36
+ [Symbol.iterator](): IterableIterator<T>;
37
+ forEach(f: (value: T, value2: T, set: this) => void, thisArg?: unknown): void;
38
+ copy(): JawnSet<T>;
39
+ /** Whether other is a set with the same elements, compared as jawns. */
40
+ equals(other: unknown): boolean;
41
+ union<U>(...others: Iterable<U>[]): JawnSet<T | U>;
42
+ intersection(...others: Iterable<unknown>[]): JawnSet<T>;
43
+ difference(...others: Iterable<unknown>[]): JawnSet<T>;
44
+ symmetricDifference<U>(other: Iterable<U>): JawnSet<T | U>;
45
+ isSubsetOf(other: Iterable<unknown>): boolean;
46
+ isSupersetOf(other: Iterable<unknown>): boolean;
47
+ isDisjointFrom(other: Iterable<unknown>): boolean;
48
+ update(...others: Iterable<T>[]): this;
49
+ intersectionUpdate(...others: Iterable<unknown>[]): this;
50
+ differenceUpdate(...others: Iterable<unknown>[]): this;
51
+ symmetricDifferenceUpdate(other: Iterable<T>): this;
52
+ private within;
53
+ /** The elements as an array, so that JSON.stringify writes sets as lists. */
54
+ toJSON(): T[];
55
+ }
@@ -0,0 +1,250 @@
1
+ import { JawnError, JawnTypeError } from './errors.js';
2
+ import { classify, mapEntries, nested, toNumber, typeName } from './values.js';
3
+ /**
4
+ * The canonical form of a jawn value, a string that's equal for two
5
+ * values exactly when they're equal as jawns: maps and sets compare
6
+ * regardless of order, numbers by value (so -0 is 0), and booleans are
7
+ * distinct from numbers. Throws a JawnError for a value that can't be a
8
+ * jawn value.
9
+ */
10
+ export function jawnKey(v) {
11
+ return key(v, new Set());
12
+ }
13
+ function key(v, stack) {
14
+ switch (classify(v)) {
15
+ case 'null':
16
+ return 'z';
17
+ case 'boolean':
18
+ return v ? 't' : 'f';
19
+ case 'number': {
20
+ const n = toNumber(v);
21
+ return 'n' + (n === 0 ? '0' : String(n));
22
+ }
23
+ case 'string':
24
+ return 's' + JSON.stringify(v);
25
+ case 'list':
26
+ return nested(v, stack, () => '[' + Array.from(v, (x) => key(x, stack)).join(',') + ']');
27
+ case 'set':
28
+ if (v instanceof JawnSet) {
29
+ return '<' + [...v.keys_()].sort().join(',') + '>';
30
+ }
31
+ return nested(v, stack, () => '<' + [...new Set(Array.from(v, (x) => key(x, stack)))].sort().join(',') + '>');
32
+ case 'map':
33
+ return nested(v, stack, () => '{' + mapEntries(v).map(([k, x]) => JSON.stringify(k) + ':' + key(x, stack))
34
+ .sort().join(',') + '}');
35
+ }
36
+ throw new JawnTypeError(`Not a jawn value: ${typeName(v)}`);
37
+ }
38
+ /** Whether two values are equal as jawn values; false if either isn't one. */
39
+ export function jawnEquals(a, b) {
40
+ try {
41
+ return jawnKey(a) === jawnKey(b);
42
+ }
43
+ catch (e) {
44
+ if (e instanceof JawnError) {
45
+ return false;
46
+ }
47
+ throw e;
48
+ }
49
+ }
50
+ // Canonical forms of another collection's elements, for comparing by jawn
51
+ // rather than JavaScript equality. A non-jawn element gets a form of its
52
+ // own that equals no other; canonical forms never begin with \0.
53
+ let unique = 0;
54
+ function keysOf(o) {
55
+ if (o instanceof JawnSet) {
56
+ return new Set(o.keys_());
57
+ }
58
+ const ks = new Set();
59
+ for (const x of o) {
60
+ try {
61
+ ks.add(jawnKey(x));
62
+ }
63
+ catch (e) {
64
+ if (!(e instanceof JawnError)) {
65
+ throw e;
66
+ }
67
+ ks.add(`\0${unique++}`);
68
+ }
69
+ }
70
+ return ks;
71
+ }
72
+ /**
73
+ * A set of jawn values compared by value and type.
74
+ *
75
+ * Unlike the builtin Set, elements are compared by their content rather
76
+ * than identity, so maps and lists may be elements, and booleans are
77
+ * distinct from numbers. Elements are compared by their content when
78
+ * added, so mutating one afterwards leaves the set inconsistent. Methods
79
+ * taking another collection compare by jawn value too, e.g.
80
+ * `new JawnSet([true]).isSubsetOf(new Set([1]))` is false.
81
+ */
82
+ export class JawnSet {
83
+ items = new Map();
84
+ constructor(items) {
85
+ if (items) {
86
+ for (const x of items) {
87
+ this.add(x);
88
+ }
89
+ }
90
+ }
91
+ /** @internal The canonical forms of the elements. */
92
+ keys_() {
93
+ return this.items.keys();
94
+ }
95
+ get size() {
96
+ return this.items.size;
97
+ }
98
+ get [Symbol.toStringTag]() {
99
+ return 'JawnSet';
100
+ }
101
+ /** Adds an element, which must be a jawn value. */
102
+ add(x) {
103
+ const k = jawnKey(x);
104
+ if (!this.items.has(k)) {
105
+ this.items.set(k, x);
106
+ }
107
+ return this;
108
+ }
109
+ /** Like an absent element, a non-jawn value is simply not present. */
110
+ has(x) {
111
+ const k = tryKey(x);
112
+ return k !== undefined && this.items.has(k);
113
+ }
114
+ delete(x) {
115
+ const k = tryKey(x);
116
+ return k !== undefined && this.items.delete(k);
117
+ }
118
+ clear() {
119
+ this.items.clear();
120
+ }
121
+ values() {
122
+ return this.items.values();
123
+ }
124
+ keys() {
125
+ return this.items.values();
126
+ }
127
+ *entries() {
128
+ for (const x of this.items.values()) {
129
+ yield [x, x];
130
+ }
131
+ }
132
+ [Symbol.iterator]() {
133
+ return this.items.values();
134
+ }
135
+ forEach(f, thisArg) {
136
+ for (const x of this.items.values()) {
137
+ f.call(thisArg, x, x, this);
138
+ }
139
+ }
140
+ copy() {
141
+ return new JawnSet(this);
142
+ }
143
+ /** Whether other is a set with the same elements, compared as jawns. */
144
+ equals(other) {
145
+ if (!(other instanceof JawnSet || other instanceof Set)) {
146
+ return false;
147
+ }
148
+ const ks = keysOf(other);
149
+ return ks.size === this.items.size && this.within(ks);
150
+ }
151
+ // Set operations, named as the builtin Set's, which accept any
152
+ // iterables and return new sets.
153
+ union(...others) {
154
+ const r = new JawnSet(this);
155
+ return r.update(...others);
156
+ }
157
+ intersection(...others) {
158
+ return this.copy().intersectionUpdate(...others);
159
+ }
160
+ difference(...others) {
161
+ return this.copy().differenceUpdate(...others);
162
+ }
163
+ symmetricDifference(other) {
164
+ return new JawnSet(this).symmetricDifferenceUpdate(other);
165
+ }
166
+ isSubsetOf(other) {
167
+ return this.within(keysOf(other));
168
+ }
169
+ isSupersetOf(other) {
170
+ for (const k of keysOf(other)) {
171
+ if (!this.items.has(k)) {
172
+ return false;
173
+ }
174
+ }
175
+ return true;
176
+ }
177
+ isDisjointFrom(other) {
178
+ for (const k of keysOf(other)) {
179
+ if (this.items.has(k)) {
180
+ return false;
181
+ }
182
+ }
183
+ return true;
184
+ }
185
+ // In-place counterparts, which return this set.
186
+ update(...others) {
187
+ for (const o of others) {
188
+ for (const x of o) {
189
+ this.add(x);
190
+ }
191
+ }
192
+ return this;
193
+ }
194
+ intersectionUpdate(...others) {
195
+ for (const o of others) {
196
+ const keep = keysOf(o);
197
+ for (const k of [...this.items.keys()]) {
198
+ if (!keep.has(k)) {
199
+ this.items.delete(k);
200
+ }
201
+ }
202
+ }
203
+ return this;
204
+ }
205
+ differenceUpdate(...others) {
206
+ for (const o of others) {
207
+ for (const x of o) {
208
+ this.delete(x);
209
+ }
210
+ }
211
+ return this;
212
+ }
213
+ symmetricDifferenceUpdate(other) {
214
+ for (const x of new JawnSet(other)) {
215
+ if (this.has(x)) {
216
+ this.delete(x);
217
+ }
218
+ else {
219
+ this.add(x);
220
+ }
221
+ }
222
+ return this;
223
+ }
224
+ within(ks) {
225
+ for (const k of this.items.keys()) {
226
+ if (!ks.has(k)) {
227
+ return false;
228
+ }
229
+ }
230
+ return true;
231
+ }
232
+ /** The elements as an array, so that JSON.stringify writes sets as lists. */
233
+ toJSON() {
234
+ return [...this.items.values()];
235
+ }
236
+ [Symbol.for('nodejs.util.inspect.custom')](_depth, options, inspect) {
237
+ return `JawnSet(${inspect([...this.items.values()], options)})`;
238
+ }
239
+ }
240
+ function tryKey(x) {
241
+ try {
242
+ return jawnKey(x);
243
+ }
244
+ catch (e) {
245
+ if (e instanceof JawnError) {
246
+ return undefined;
247
+ }
248
+ throw e;
249
+ }
250
+ }