@syncular/core 0.58.0 → 0.60.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,62 @@
1
+ /**
2
+ * The JSON validity rule (SPEC.md Conventions): the one definition of a valid
3
+ * JSON document for envelope `json` fields, push-payload `json` columns and
4
+ * `blob_ref` values. The Rust core (`ssp2::primitives::parse_json_document`)
5
+ * accepts exactly the same documents.
6
+ */
7
+ import { DecodeError } from './errors';
8
+
9
+ const MAX_DEPTH = 64;
10
+
11
+ /**
12
+ * Parse `raw` as a JSON document under the validity rule, which is defined on
13
+ * the raw text: RFC 8259, at most 64 nested containers anywhere in the text,
14
+ * every number token a finite binary64, no U+0000 and no unpaired surrogate in
15
+ * any string token (key or value), written literally or as a `\u` escape.
16
+ * `JSON.parse` drops values shadowed by a duplicate key, so one lexical pass
17
+ * over the text checks what the parser cannot see. A violation throws
18
+ * `DecodeError` (`sync.invalid_request`); `what` names the field in the
19
+ * message.
20
+ */
21
+ export function parseJsonDocument(raw: string, what: string): unknown {
22
+ let value: unknown;
23
+ try {
24
+ value = JSON.parse(raw);
25
+ } catch {
26
+ throw new DecodeError(
27
+ 'sync.invalid_request',
28
+ `${what} does not parse as a JSON document`,
29
+ );
30
+ }
31
+ let depth = 0;
32
+ for (const [token] of raw.matchAll(
33
+ /"(?:[^"\\]|\\.)*"|[[{\]}]|-?\d[\d.eE+-]*/g,
34
+ )) {
35
+ const head = token[0];
36
+ if (head === '[' || head === '{') {
37
+ if (++depth > MAX_DEPTH) {
38
+ throw new DecodeError(
39
+ 'sync.invalid_request',
40
+ `${what} nests deeper than ${MAX_DEPTH} containers`,
41
+ );
42
+ }
43
+ } else if (head === ']' || head === '}') {
44
+ depth--;
45
+ } else if (head === '"') {
46
+ // In `u` mode `\p{Cs}` matches only a surrogate outside a pair.
47
+ const text: string = token.includes('\\') ? JSON.parse(token) : token;
48
+ if (text.includes('\u0000') || /\p{Cs}/u.test(text)) {
49
+ throw new DecodeError(
50
+ 'sync.invalid_request',
51
+ `${what} holds a string with U+0000 or an unpaired surrogate`,
52
+ );
53
+ }
54
+ } else if (!Number.isFinite(Number(token))) {
55
+ throw new DecodeError(
56
+ 'sync.invalid_request',
57
+ `${what} holds a number outside finite binary64`,
58
+ );
59
+ }
60
+ }
61
+ return value;
62
+ }