@ultimat3/realtime 27.6.0 → 27.6.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/package.json +5 -5
- package/src/pg-array.ts +6 -104
- package/src/pg-values.ts +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ultimat3/realtime",
|
|
3
|
-
"version": "27.6.
|
|
3
|
+
"version": "27.6.1",
|
|
4
4
|
"description": "Three-tier realtime: channels, live queries, local-first sync — one protocol, one mutator shape",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -40,10 +40,10 @@
|
|
|
40
40
|
"test": "bun test"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@ultimat3/core": "27.6.
|
|
44
|
-
"@ultimat3/entity": "27.6.
|
|
45
|
-
"@ultimat3/policy": "27.6.
|
|
46
|
-
"@ultimat3/query": "27.6.
|
|
43
|
+
"@ultimat3/core": "27.6.1",
|
|
44
|
+
"@ultimat3/entity": "27.6.1",
|
|
45
|
+
"@ultimat3/policy": "27.6.1",
|
|
46
|
+
"@ultimat3/query": "27.6.1",
|
|
47
47
|
"nats": "2.29.3"
|
|
48
48
|
}
|
|
49
49
|
}
|
package/src/pg-array.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
// The
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// The element type behind an array type oid. The WAL names only the ARRAY type and carries the
|
|
2
|
+
// value as its TEXT literal (`{a,b}`), while `@ultimat3/entity`'s repository hands an `arrayOf()`
|
|
3
|
+
// column back as a JS array — and a live row and a repository row have to be the same object.
|
|
4
4
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
5
|
+
// The GRAMMAR of that literal is `@ultimat3/entity`'s `parsePgArray`: the repository reads the
|
|
6
|
+
// same text when a driver leaves an array unparsed, and one grammar has one reader. This file
|
|
7
|
+
// keeps the half only the WAL needs — which element decoder an oid selects.
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* `text[]` -> `text`. The wire names only the ARRAY type, so the element type behind it is a
|
|
@@ -41,101 +41,3 @@ const ELEMENT_OF = Object.freeze<Record<number, number>>({
|
|
|
41
41
|
export function arrayElementOid(typeOid: number): number | undefined {
|
|
42
42
|
return Object.hasOwn(ELEMENT_OF, typeOid) ? ELEMENT_OF[typeOid] : undefined;
|
|
43
43
|
}
|
|
44
|
-
|
|
45
|
-
/** Where the scan is, so every branch below advances one cursor rather than slicing the text. */
|
|
46
|
-
interface Scan {
|
|
47
|
-
readonly text: string;
|
|
48
|
-
at: number;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* One array literal -> a nested JS array, or `null` when the text is not one this grammar
|
|
53
|
-
* describes. `null` is not an error: a dimension prefix (`[0:2]={…}`), a `DateStyle` this decoder
|
|
54
|
-
* does not read, or a corrupted literal all mean the same thing to the caller — keep the text it
|
|
55
|
-
* arrived as rather than deliver an array that is missing a member.
|
|
56
|
-
*
|
|
57
|
-
* `decode` is the ELEMENT decoder. It never sees a quoted element's quotes or its backslash
|
|
58
|
-
* escapes: an unquoted `NULL` is the null value and a quoted `"NULL"` is the four-character
|
|
59
|
-
* string, which is the one distinction the quoting exists to carry.
|
|
60
|
-
*/
|
|
61
|
-
export function parsePgArray<T>(text: string, decode: (raw: string) => T): PgArray<T> | null {
|
|
62
|
-
const scan: Scan = { text, at: 0 };
|
|
63
|
-
const parsed = readArray(scan, decode);
|
|
64
|
-
// Trailing content means the literal was not what this grammar accepted, whatever it parsed.
|
|
65
|
-
return parsed === null || scan.at !== text.length ? null : parsed;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/** A member is a decoded value, the null member, or — for a multidimensional array — a row of them. */
|
|
69
|
-
export type PgArray<T> = (T | null | PgArray<T>)[];
|
|
70
|
-
|
|
71
|
-
function readArray<T>(scan: Scan, decode: (raw: string) => T): PgArray<T> | null {
|
|
72
|
-
if (scan.text[scan.at] !== '{') return null;
|
|
73
|
-
scan.at += 1;
|
|
74
|
-
const out: PgArray<T> = [];
|
|
75
|
-
if (scan.text[scan.at] === '}') {
|
|
76
|
-
scan.at += 1;
|
|
77
|
-
return out;
|
|
78
|
-
}
|
|
79
|
-
for (;;) {
|
|
80
|
-
const member = readMember(scan, decode);
|
|
81
|
-
if (member === FAILED) return null;
|
|
82
|
-
out.push(member);
|
|
83
|
-
const next = scan.text[scan.at];
|
|
84
|
-
scan.at += 1;
|
|
85
|
-
if (next === '}') return out;
|
|
86
|
-
if (next !== ',') return null;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/** A sentinel, because `null` is a legal member and `undefined` would be a second spelling of it. */
|
|
91
|
-
const FAILED = Symbol('pg-array-failed');
|
|
92
|
-
|
|
93
|
-
function readMember<T>(
|
|
94
|
-
scan: Scan,
|
|
95
|
-
decode: (raw: string) => T,
|
|
96
|
-
): T | null | PgArray<T> | typeof FAILED {
|
|
97
|
-
const head = scan.text[scan.at];
|
|
98
|
-
if (head === '{') {
|
|
99
|
-
const nested = readArray(scan, decode);
|
|
100
|
-
return nested === null ? FAILED : nested;
|
|
101
|
-
}
|
|
102
|
-
if (head === '"') {
|
|
103
|
-
const quoted = readQuoted(scan);
|
|
104
|
-
return quoted === null ? FAILED : decode(quoted);
|
|
105
|
-
}
|
|
106
|
-
const start = scan.at;
|
|
107
|
-
while (scan.at < scan.text.length) {
|
|
108
|
-
const char = scan.text[scan.at];
|
|
109
|
-
if (char === ',' || char === '}') break;
|
|
110
|
-
// A brace or a quote inside a bare element is a literal this grammar does not describe.
|
|
111
|
-
if (char === '{' || char === '"') return FAILED;
|
|
112
|
-
scan.at += 1;
|
|
113
|
-
}
|
|
114
|
-
if (scan.at === scan.text.length) return FAILED;
|
|
115
|
-
const raw = scan.text.slice(start, scan.at);
|
|
116
|
-
// Unquoted and case-insensitive is the ONLY spelling of the null member; `"NULL"` is a string.
|
|
117
|
-
return raw.toUpperCase() === 'NULL' ? null : decode(raw);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/** The text between one pair of quotes, with `\\` and `\"` unescaped. `null` if it never closes. */
|
|
121
|
-
function readQuoted(scan: Scan): string | null {
|
|
122
|
-
scan.at += 1;
|
|
123
|
-
let out = '';
|
|
124
|
-
while (scan.at < scan.text.length) {
|
|
125
|
-
const char = scan.text[scan.at];
|
|
126
|
-
if (char === '"') {
|
|
127
|
-
scan.at += 1;
|
|
128
|
-
return out;
|
|
129
|
-
}
|
|
130
|
-
if (char === '\\') {
|
|
131
|
-
const escaped = scan.text[scan.at + 1];
|
|
132
|
-
if (escaped === undefined) return null;
|
|
133
|
-
out += escaped;
|
|
134
|
-
scan.at += 2;
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
out += char ?? '';
|
|
138
|
-
scan.at += 1;
|
|
139
|
-
}
|
|
140
|
-
return null;
|
|
141
|
-
}
|
package/src/pg-values.ts
CHANGED
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
// in the component the first patch reached.
|
|
12
12
|
|
|
13
13
|
import { renderThrowable } from '@ultimat3/core';
|
|
14
|
+
import { parsePgArray } from '@ultimat3/entity';
|
|
14
15
|
import { ReplicationProtocolError } from './errors';
|
|
15
16
|
import type { JsonValue } from './json';
|
|
16
|
-
import { arrayElementOid
|
|
17
|
+
import { arrayElementOid } from './pg-array';
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* What a row value can be between the WAL and the wire: JSON, plus the two JS shapes a repository
|