@jarenjs/core 0.34.2 → 0.43.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/ARCHITECTURE.md +43 -2
- package/README.md +7 -4
- package/dist/types/function.d.ts +25 -0
- package/dist/types/geo/bbox.d.ts +28 -0
- package/dist/types/geo/distance.d.ts +32 -0
- package/dist/types/geo/geojson.d.ts +4 -1
- package/dist/types/geo/simplify.d.ts +28 -0
- package/dist/types/geo/wkt.d.ts +65 -0
- package/dist/types/object.d.ts +18 -0
- package/dist/types/scan.d.ts +1 -0
- package/dist/types/string.d.ts +22 -0
- package/dist/types/text/sse.d.ts +74 -0
- package/docs/GEO.md +77 -4
- package/package.json +1 -1
- package/src/function.js +34 -0
- package/src/geo/bbox.js +41 -0
- package/src/geo/distance.js +48 -0
- package/src/geo/geojson.js +10 -2
- package/src/geo/index.js +1 -1
- package/src/geo/simplify.js +69 -0
- package/src/geo/wkt.js +379 -63
- package/src/object.js +68 -0
- package/src/scan.js +1 -0
- package/src/string.js +36 -0
- package/src/text/sse.js +197 -0
package/src/string.js
CHANGED
|
@@ -276,6 +276,42 @@ export function compareCodePoints(a, b) {
|
|
|
276
276
|
return a.codePointAt(i) < b.codePointAt(i) ? -1 : 1;
|
|
277
277
|
}
|
|
278
278
|
|
|
279
|
+
/**
|
|
280
|
+
* The exclusive upper bound of the strings beginning with `prefix`: the
|
|
281
|
+
* smallest string that sorts above every one of them. For a
|
|
282
|
+
* well-formed `value`, `value` begins with `prefix` **iff**
|
|
283
|
+
* `prefix <= value < codePointPrefixSuccessor(prefix)` under
|
|
284
|
+
* {@link compareCodePoints} — which is what lets a caller spell a
|
|
285
|
+
* prefix test as a half-open range, the form an ordered index can seek
|
|
286
|
+
* rather than test row by row.
|
|
287
|
+
*
|
|
288
|
+
* Null when there is no such string, and the caller then has no range
|
|
289
|
+
* to offer: the empty prefix (every string begins with it) and a
|
|
290
|
+
* prefix of nothing but U+10FFFF (nothing sorts above it).
|
|
291
|
+
*
|
|
292
|
+
* The bound never lands in the surrogate range, because it has to be a
|
|
293
|
+
* string the caller can hand on — to a comparator, a database
|
|
294
|
+
* parameter, a serializer. No well-formed string sorts inside that
|
|
295
|
+
* range, so stepping over it leaves the equivalence above intact.
|
|
296
|
+
*
|
|
297
|
+
* @param {string} prefix - The prefix to bound
|
|
298
|
+
* @returns {string | null} The exclusive upper bound, or null when none exists
|
|
299
|
+
*/
|
|
300
|
+
export function codePointPrefixSuccessor(prefix) {
|
|
301
|
+
const points = toCodePoints(prefix);
|
|
302
|
+
// the last code point that can still be incremented: U+10FFFF cannot,
|
|
303
|
+
// so it drops off and the carry moves left
|
|
304
|
+
let i = points.length - 1;
|
|
305
|
+
while (i >= 0 && points[i] === 0x10FFFF)
|
|
306
|
+
i--;
|
|
307
|
+
if (i < 0)
|
|
308
|
+
return null;
|
|
309
|
+
const next = points[i] + 1;
|
|
310
|
+
points.length = i + 1;
|
|
311
|
+
points[i] = next >= 0xD800 && next <= 0xDFFF ? 0xE000 : next;
|
|
312
|
+
return fromCodePoints(points);
|
|
313
|
+
}
|
|
314
|
+
|
|
279
315
|
/** FNV-1a 32-bit offset basis — the seed a fresh hash starts from. */
|
|
280
316
|
export const FNV1A_OFFSET_BASIS = 0x811c9dc5;
|
|
281
317
|
|
package/src/text/sse.js
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
/**
|
|
3
|
+
* @file An incremental Server-Sent-Events codec (WHATWG HTML §9.2).
|
|
4
|
+
* `createSseEventDecoder` yields complete events `{ event, id, data,
|
|
5
|
+
* retry }` from network chunks fed in any split — mid-line, mid-event,
|
|
6
|
+
* CR, LF or CRLF line endings; `createSseDecoder` is the data-only view
|
|
7
|
+
* of the same machine (the shape every OpenAI-compatible streaming
|
|
8
|
+
* endpoint needs: just the `data:` payloads, in order); and
|
|
9
|
+
* `encodeSseEvent` renders one event back to wire text.
|
|
10
|
+
*
|
|
11
|
+
* Dispatch follows the specification: field lines are `name: value`
|
|
12
|
+
* with one optional leading space in the value, `:` lines are comments,
|
|
13
|
+
* an event is dispatched on the blank line exactly when its data buffer
|
|
14
|
+
* is non-empty (multi-line data joins with `\n`), the last-event-id is
|
|
15
|
+
* a stream attribute that persists across events (an `id` field whose
|
|
16
|
+
* value carries U+0000 is ignored), and `retry` must be ASCII digits.
|
|
17
|
+
* `end()` flushes a final event from a stream that never sent its
|
|
18
|
+
* closing blank line.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* One decoded event. `event` is the `event:` field of the block, `null`
|
|
23
|
+
* when the block had none (the specification's default type "message");
|
|
24
|
+
* `id` is the stream's last-event-id at dispatch, `null` while the
|
|
25
|
+
* stream has not set one; `data` is the joined data payload; `retry` is
|
|
26
|
+
* the reconnection time a `retry:` field set since the previous
|
|
27
|
+
* dispatch, `null` otherwise.
|
|
28
|
+
* @typedef {Object} SseEvent
|
|
29
|
+
* @property {string | null} event
|
|
30
|
+
* @property {string | null} id
|
|
31
|
+
* @property {string} data
|
|
32
|
+
* @property {number | null} retry
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* An incremental decoder yielding complete events.
|
|
37
|
+
* @returns {{ feed: (chunk: string) => SseEvent[], end: () => SseEvent[] }}
|
|
38
|
+
* `feed` returns the events completed by this chunk; `end` flushes a
|
|
39
|
+
* final event from a stream that never sent its closing blank line.
|
|
40
|
+
*/
|
|
41
|
+
export function createSseEventDecoder() {
|
|
42
|
+
let tail = '';
|
|
43
|
+
/** @type {string[]} */
|
|
44
|
+
let data = [];
|
|
45
|
+
/** @type {string | null} */
|
|
46
|
+
let eventType = null;
|
|
47
|
+
/** @type {string | null} */
|
|
48
|
+
let lastId = null;
|
|
49
|
+
/** @type {number | null} */
|
|
50
|
+
let retry = null;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @param {string} line - one complete line, no line terminator
|
|
54
|
+
* @param {SseEvent[]} out
|
|
55
|
+
*/
|
|
56
|
+
function consumeLine(line, out) {
|
|
57
|
+
if (line === '') {
|
|
58
|
+
// the dispatch rule: no data, no event — but the type buffer resets
|
|
59
|
+
if (data.length > 0) {
|
|
60
|
+
out.push({ event: eventType, id: lastId, data: data.join('\n'), retry });
|
|
61
|
+
data = [];
|
|
62
|
+
retry = null;
|
|
63
|
+
}
|
|
64
|
+
eventType = null;
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (line.charCodeAt(0) === 0x3A) return; // a comment
|
|
68
|
+
const colon = line.indexOf(':');
|
|
69
|
+
const name = colon === -1 ? line : line.slice(0, colon);
|
|
70
|
+
const value = colon === -1
|
|
71
|
+
? ''
|
|
72
|
+
: line.slice(line.charCodeAt(colon + 1) === 0x20 ? colon + 2 : colon + 1);
|
|
73
|
+
switch (name) {
|
|
74
|
+
case 'data':
|
|
75
|
+
data.push(value);
|
|
76
|
+
break;
|
|
77
|
+
case 'event':
|
|
78
|
+
eventType = value;
|
|
79
|
+
break;
|
|
80
|
+
case 'id':
|
|
81
|
+
if (value.indexOf('\0') === -1) lastId = value;
|
|
82
|
+
break;
|
|
83
|
+
case 'retry':
|
|
84
|
+
if (value.length > 0 && /^[0-9]+$/.test(value)) retry = Number.parseInt(value, 10);
|
|
85
|
+
break;
|
|
86
|
+
// every other field name is ignored by the specification
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
feed(chunk) {
|
|
92
|
+
/** @type {SseEvent[]} */
|
|
93
|
+
const out = [];
|
|
94
|
+
const text = tail + chunk;
|
|
95
|
+
const length = text.length;
|
|
96
|
+
// a trailing CR is held: the next chunk may complete a CRLF
|
|
97
|
+
const limit = length > 0 && text.charCodeAt(length - 1) === 0x0D ? length - 1 : length;
|
|
98
|
+
let start = 0;
|
|
99
|
+
let i = 0;
|
|
100
|
+
while (i < limit) {
|
|
101
|
+
const c = text.charCodeAt(i);
|
|
102
|
+
if (c === 0x0A) {
|
|
103
|
+
consumeLine(text.slice(start, i), out);
|
|
104
|
+
i += 1;
|
|
105
|
+
start = i;
|
|
106
|
+
}
|
|
107
|
+
else if (c === 0x0D) {
|
|
108
|
+
consumeLine(text.slice(start, i), out);
|
|
109
|
+
i += 1;
|
|
110
|
+
if (i < length && text.charCodeAt(i) === 0x0A) i += 1;
|
|
111
|
+
start = i;
|
|
112
|
+
}
|
|
113
|
+
else i += 1;
|
|
114
|
+
}
|
|
115
|
+
tail = text.slice(start);
|
|
116
|
+
return out;
|
|
117
|
+
},
|
|
118
|
+
end() {
|
|
119
|
+
/** @type {SseEvent[]} */
|
|
120
|
+
const out = [];
|
|
121
|
+
if (tail !== '') {
|
|
122
|
+
consumeLine(tail.charCodeAt(tail.length - 1) === 0x0D ? tail.slice(0, -1) : tail, out);
|
|
123
|
+
tail = '';
|
|
124
|
+
}
|
|
125
|
+
if (data.length > 0) {
|
|
126
|
+
out.push({ event: eventType, id: lastId, data: data.join('\n'), retry });
|
|
127
|
+
data = [];
|
|
128
|
+
retry = null;
|
|
129
|
+
}
|
|
130
|
+
eventType = null;
|
|
131
|
+
return out;
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* The data-only view of the event decoder: `feed` yields the complete
|
|
138
|
+
* `data:` payloads in order and every other field is ignored — the
|
|
139
|
+
* shape a chat-completion stream consumer needs.
|
|
140
|
+
* @returns {{ feed: (chunk: string) => string[], end: () => string[] }}
|
|
141
|
+
*/
|
|
142
|
+
export function createSseDecoder() {
|
|
143
|
+
const inner = createSseEventDecoder();
|
|
144
|
+
/** @param {SseEvent[]} events */
|
|
145
|
+
const dataOf = (events) => events.map((e) => e.data);
|
|
146
|
+
return {
|
|
147
|
+
feed: (chunk) => dataOf(inner.feed(chunk)),
|
|
148
|
+
end: () => dataOf(inner.end()),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Render one event as wire text: the optional `event:`, `id:` and
|
|
154
|
+
* `retry:` lines, the data split on `\n` into one `data:` line each,
|
|
155
|
+
* and the dispatching blank line. Throws `TypeError` for text the frame
|
|
156
|
+
* cannot carry: a line terminator inside `event` or `id`, U+0000 inside
|
|
157
|
+
* `id`, a bare carriage return inside `data` (a decoder would read it
|
|
158
|
+
* as a line break and corrupt the framing), or a `retry` that is not a
|
|
159
|
+
* non-negative integer.
|
|
160
|
+
* @param {{ event?: string | null, id?: string | null, data: string, retry?: number | null }} fields
|
|
161
|
+
* @returns {string}
|
|
162
|
+
*/
|
|
163
|
+
export function encodeSseEvent(fields) {
|
|
164
|
+
if (fields === null || typeof fields !== 'object' || typeof fields.data !== 'string') {
|
|
165
|
+
throw new TypeError('encodeSseEvent: the argument must be { event?, id?, data, retry? } with a string data');
|
|
166
|
+
}
|
|
167
|
+
let out = '';
|
|
168
|
+
const event = fields.event;
|
|
169
|
+
if (event !== undefined && event !== null) {
|
|
170
|
+
if (typeof event !== 'string' || /[\r\n]/.test(event)) {
|
|
171
|
+
throw new TypeError('encodeSseEvent: event must be a single-line string');
|
|
172
|
+
}
|
|
173
|
+
out += 'event: ' + event + '\n';
|
|
174
|
+
}
|
|
175
|
+
const id = fields.id;
|
|
176
|
+
if (id !== undefined && id !== null) {
|
|
177
|
+
if (typeof id !== 'string' || /[\r\n\0]/.test(id)) {
|
|
178
|
+
throw new TypeError('encodeSseEvent: id must be a single-line string without U+0000');
|
|
179
|
+
}
|
|
180
|
+
out += 'id: ' + id + '\n';
|
|
181
|
+
}
|
|
182
|
+
const retry = fields.retry;
|
|
183
|
+
if (retry !== undefined && retry !== null) {
|
|
184
|
+
if (!Number.isInteger(retry) || retry < 0) {
|
|
185
|
+
throw new TypeError('encodeSseEvent: retry must be a non-negative integer');
|
|
186
|
+
}
|
|
187
|
+
out += 'retry: ' + retry + '\n';
|
|
188
|
+
}
|
|
189
|
+
const lines = fields.data.split('\n');
|
|
190
|
+
for (let i = 0; i < lines.length; i++) {
|
|
191
|
+
if (lines[i].indexOf('\r') !== -1) {
|
|
192
|
+
throw new TypeError('encodeSseEvent: data must not contain a bare carriage return — a decoder reads it as a line break');
|
|
193
|
+
}
|
|
194
|
+
out += 'data: ' + lines[i] + '\n';
|
|
195
|
+
}
|
|
196
|
+
return out + '\n';
|
|
197
|
+
}
|