@memberjunction/global 6.2.0-edge.1 → 6.2.0-edge.3
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 +26 -0
- package/dist/__tests__/BinaryEncoding.test.d.ts +2 -0
- package/dist/__tests__/BinaryEncoding.test.d.ts.map +1 -0
- package/dist/__tests__/BinaryEncoding.test.js +244 -0
- package/dist/__tests__/BinaryEncoding.test.js.map +1 -0
- package/dist/__tests__/BinaryEncodingRows.test.d.ts +2 -0
- package/dist/__tests__/BinaryEncodingRows.test.d.ts.map +1 -0
- package/dist/__tests__/BinaryEncodingRows.test.js +146 -0
- package/dist/__tests__/BinaryEncodingRows.test.js.map +1 -0
- package/dist/__tests__/BinaryEncodingValidation.test.d.ts +2 -0
- package/dist/__tests__/BinaryEncodingValidation.test.d.ts.map +1 -0
- package/dist/__tests__/BinaryEncodingValidation.test.js +98 -0
- package/dist/__tests__/BinaryEncodingValidation.test.js.map +1 -0
- package/dist/__tests__/util.test.js +27 -0
- package/dist/__tests__/util.test.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/util/BinaryEncoding.d.ts +197 -0
- package/dist/util/BinaryEncoding.d.ts.map +1 -0
- package/dist/util/BinaryEncoding.js +388 -0
- package/dist/util/BinaryEncoding.js.map +1 -0
- package/dist/util.d.ts +15 -8
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +78 -10
- package/dist/util.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binary encoding primitives shared by every MemberJunction tier.
|
|
3
|
+
*
|
|
4
|
+
* MemberJunction stores binary database columns (SQL Server `binary` / `varbinary` / `image`,
|
|
5
|
+
* PostgreSQL `bytea`) in `BaseEntity` and on the wire as **base64 strings**. A string keeps the
|
|
6
|
+
* value JSON-safe, so dirty tracking, Record Changes, the RunView caches, GraphQL and remote
|
|
7
|
+
* invalidation broadcasts all handle it without special cases. Code that needs the actual bytes
|
|
8
|
+
* converts at the edge with the functions in this module.
|
|
9
|
+
*
|
|
10
|
+
* Everything here is dependency-free and runs unchanged in browsers, Node and workers. Each
|
|
11
|
+
* conversion picks the fastest implementation the host offers:
|
|
12
|
+
*
|
|
13
|
+
* | Host | base64 → bytes | bytes → base64 |
|
|
14
|
+
* |---|---|---|
|
|
15
|
+
* | Browsers with the TC39 base64 API (Chrome 140+, Firefox 133+, Safari 18.2+) | `Uint8Array.fromBase64` | `Uint8Array.prototype.toBase64` |
|
|
16
|
+
* | Node.js | `Buffer.from(s, 'base64')` | `Buffer#toString('base64')` |
|
|
17
|
+
* | Anything else | `atob` | `btoa` (chunked) |
|
|
18
|
+
*
|
|
19
|
+
* The fast paths are detected by duck typing, once per process, so older browsers keep working.
|
|
20
|
+
* All three implementations accept and produce exactly the same strings, and the decoders
|
|
21
|
+
* validate input identically, so a value never decodes on one tier and fails on another.
|
|
22
|
+
*
|
|
23
|
+
* @module BinaryEncoding
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Lookup table over every UTF-16 code unit (64 KB, so the scan needs no range check): 1 for the standard base64 alphabet (`A–Z a–z 0–9 + /`),
|
|
27
|
+
* 0 otherwise. Data URIs, URL-safe base64 and MIME line breaks are deliberately rejected: binary
|
|
28
|
+
* columns only ever carry canonical base64. A table scan is used instead of a regular expression
|
|
29
|
+
* because validation runs on every decode, and on an 8 KB vector a regex costs about five times
|
|
30
|
+
* as much as the decode itself.
|
|
31
|
+
*/
|
|
32
|
+
/** Char code of the base64 padding character `=`. */
|
|
33
|
+
const EQUALS_CODE = 0x3d;
|
|
34
|
+
const BASE64_ALPHABET = (() => {
|
|
35
|
+
const table = new Uint8Array(0x10000);
|
|
36
|
+
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
37
|
+
for (let i = 0; i < alphabet.length; i++)
|
|
38
|
+
table[alphabet.charCodeAt(i)] = 1;
|
|
39
|
+
return table;
|
|
40
|
+
})();
|
|
41
|
+
/** `String.fromCharCode` argument count per call when building a binary string for `btoa`. */
|
|
42
|
+
const BTOA_CHUNK = 0x8000;
|
|
43
|
+
/** Reads a global by name without assuming any particular host typings. */
|
|
44
|
+
function readGlobal(name) {
|
|
45
|
+
return Reflect.get(globalThis, name);
|
|
46
|
+
}
|
|
47
|
+
/** Native TC39 base64 support on `Uint8Array`, detected by duck typing. */
|
|
48
|
+
const NativeCodec = {
|
|
49
|
+
Name: 'Uint8Array.fromBase64 / toBase64',
|
|
50
|
+
IsAvailable() {
|
|
51
|
+
return typeof Reflect.get(Uint8Array, 'fromBase64') === 'function'
|
|
52
|
+
&& typeof Reflect.get(Uint8Array.prototype, 'toBase64') === 'function';
|
|
53
|
+
},
|
|
54
|
+
Encode(bytes) {
|
|
55
|
+
return bytes.toBase64();
|
|
56
|
+
},
|
|
57
|
+
Decode(base64) {
|
|
58
|
+
const fromBase64 = Reflect.get(Uint8Array, 'fromBase64');
|
|
59
|
+
return fromBase64.call(Uint8Array, base64, { lastChunkHandling: 'loose' });
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
/** Node's `Buffer`, which encodes and decodes in native code. */
|
|
63
|
+
const NodeBufferCodec = {
|
|
64
|
+
Name: 'Node Buffer',
|
|
65
|
+
IsAvailable() {
|
|
66
|
+
return typeof readGlobal('Buffer')?.from === 'function';
|
|
67
|
+
},
|
|
68
|
+
Encode(bytes) {
|
|
69
|
+
const nodeBuffer = readGlobal('Buffer');
|
|
70
|
+
return nodeBuffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString('base64');
|
|
71
|
+
},
|
|
72
|
+
Decode(base64) {
|
|
73
|
+
const nodeBuffer = readGlobal('Buffer');
|
|
74
|
+
const decoded = nodeBuffer.from(base64, 'base64');
|
|
75
|
+
// Copy out of Node's shared allocation pool: a pooled Buffer views a larger ArrayBuffer at
|
|
76
|
+
// an arbitrary offset, which typed-array views and transfers would otherwise trip over.
|
|
77
|
+
return new Uint8Array(decoded);
|
|
78
|
+
},
|
|
79
|
+
/**
|
|
80
|
+
* Validates by decoding. Node's decoder silently SKIPS characters outside the alphabet
|
|
81
|
+
* (whitespace, punctuation, non-ASCII) and stops at an early `=`, so any such character makes the
|
|
82
|
+
* output shorter than a canonical string of the same length decodes to — the length check rejects
|
|
83
|
+
* it. The one thing it accepts that canonical base64 does not is the URL-safe alphabet (`-`, `_`),
|
|
84
|
+
* rejected separately with a native `indexOf`. About five times cheaper than a JavaScript
|
|
85
|
+
* character scan on vector-sized values; fuzz-tested against {@link IsValidBase64}.
|
|
86
|
+
*/
|
|
87
|
+
TryDecode(base64) {
|
|
88
|
+
if (!HasValidBase64Shape(base64))
|
|
89
|
+
return null;
|
|
90
|
+
if (base64.indexOf('-') !== -1 || base64.indexOf('_') !== -1)
|
|
91
|
+
return null;
|
|
92
|
+
if (base64.length === 0)
|
|
93
|
+
return new Uint8Array(0);
|
|
94
|
+
const decoded = this.Decode(base64);
|
|
95
|
+
return decoded.length === Base64DecodedByteLength(base64) ? decoded : null;
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
/** `atob` / `btoa`, available in every browser, worker and modern Node. */
|
|
99
|
+
const PortableCodec = {
|
|
100
|
+
Name: 'atob / btoa',
|
|
101
|
+
IsAvailable() {
|
|
102
|
+
return typeof readGlobal('atob') === 'function' && typeof readGlobal('btoa') === 'function';
|
|
103
|
+
},
|
|
104
|
+
Encode(bytes) {
|
|
105
|
+
let binary = '';
|
|
106
|
+
for (let start = 0; start < bytes.length; start += BTOA_CHUNK) {
|
|
107
|
+
const chunk = bytes.subarray(start, Math.min(start + BTOA_CHUNK, bytes.length));
|
|
108
|
+
binary += String.fromCharCode(...chunk);
|
|
109
|
+
}
|
|
110
|
+
return btoa(binary);
|
|
111
|
+
},
|
|
112
|
+
Decode(base64) {
|
|
113
|
+
const binary = atob(base64);
|
|
114
|
+
const bytes = new Uint8Array(binary.length);
|
|
115
|
+
for (let i = 0; i < binary.length; i++)
|
|
116
|
+
bytes[i] = binary.charCodeAt(i);
|
|
117
|
+
return bytes;
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* Every base64 implementation, fastest first. Exposed so tests can prove the implementations are
|
|
122
|
+
* interchangeable; application code should call {@link BytesToBase64} / {@link Base64ToBytes}.
|
|
123
|
+
*/
|
|
124
|
+
export const Base64Codecs = [NativeCodec, NodeBufferCodec, PortableCodec];
|
|
125
|
+
let selectedCodec;
|
|
126
|
+
/**
|
|
127
|
+
* Returns the fastest base64 implementation this host supports. The choice is made once and
|
|
128
|
+
* cached; it cannot change during a process's life.
|
|
129
|
+
*
|
|
130
|
+
* @throws Error when the host offers none of the implementations (no JavaScript host MJ supports).
|
|
131
|
+
*/
|
|
132
|
+
export function GetBase64Codec() {
|
|
133
|
+
const codec = findBase64Codec();
|
|
134
|
+
if (!codec)
|
|
135
|
+
throw new Error('No base64 implementation is available on this host');
|
|
136
|
+
return codec;
|
|
137
|
+
}
|
|
138
|
+
/** The selected codec, choosing it on first use; `undefined` when the host offers none. */
|
|
139
|
+
function findBase64Codec() {
|
|
140
|
+
selectedCodec ??= Base64Codecs.find(codec => codec.IsAvailable());
|
|
141
|
+
return selectedCodec;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* True when `value` is canonical base64: the standard alphabet (`A–Z a–z 0–9 + /`), optional
|
|
145
|
+
* `=` padding, no whitespace, and a length that can encode whole bytes. The empty string is valid
|
|
146
|
+
* and decodes to zero bytes.
|
|
147
|
+
*
|
|
148
|
+
* @param value - The string to check.
|
|
149
|
+
*/
|
|
150
|
+
export function IsValidBase64(value) {
|
|
151
|
+
if (!HasValidBase64Shape(value))
|
|
152
|
+
return false;
|
|
153
|
+
const dataLength = value.length - trailingPadding(value);
|
|
154
|
+
for (let i = 0; i < dataLength; i++) {
|
|
155
|
+
if (BASE64_ALPHABET[value.charCodeAt(i)] === 0)
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
/** Number of trailing `=` characters, counting at most two. */
|
|
161
|
+
function trailingPadding(value) {
|
|
162
|
+
const length = value.length;
|
|
163
|
+
if (length === 0 || value.charCodeAt(length - 1) !== EQUALS_CODE)
|
|
164
|
+
return 0;
|
|
165
|
+
return length > 1 && value.charCodeAt(length - 2) === EQUALS_CODE ? 2 : 1;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* The length rules of canonical base64, without looking at the characters: a length that can
|
|
169
|
+
* encode whole bytes, and padding only on a full final quantum ("xx==" or "xxx="), never on a bare
|
|
170
|
+
* remainder. Part of {@link IsValidBase64}; exposed so a codec's fused validate-and-decode
|
|
171
|
+
* ({@link Base64Codec.TryDecode}) applies the same rules.
|
|
172
|
+
*
|
|
173
|
+
* @param value - The string to check.
|
|
174
|
+
*/
|
|
175
|
+
export function HasValidBase64Shape(value) {
|
|
176
|
+
const length = value.length;
|
|
177
|
+
if (length % 4 === 1)
|
|
178
|
+
return false;
|
|
179
|
+
return trailingPadding(value) === 0 || length % 4 === 0;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Returns how many bytes a base64 string decodes to, without decoding it. Useful for size checks
|
|
183
|
+
* on large values. The input is assumed to be valid (see {@link IsValidBase64}); padding is
|
|
184
|
+
* optional.
|
|
185
|
+
*
|
|
186
|
+
* @param base64 - Canonical base64.
|
|
187
|
+
*/
|
|
188
|
+
export function Base64DecodedByteLength(base64) {
|
|
189
|
+
const padding = base64.endsWith('==') ? 2 : base64.endsWith('=') ? 1 : 0;
|
|
190
|
+
const dataChars = base64.length - padding;
|
|
191
|
+
return Math.floor((dataChars * 3) / 4);
|
|
192
|
+
}
|
|
193
|
+
/** Prefix of the text recorded in a change log in place of a binary value. See {@link FormatBinaryChangeValue}. */
|
|
194
|
+
export const BINARY_CHANGE_VALUE_PREFIX = '[binary:';
|
|
195
|
+
/**
|
|
196
|
+
* The text a change log records, and change history shows, in place of a binary field's base64
|
|
197
|
+
* value: `[binary: 6,144 bytes]`. The bytes themselves stay in the record snapshot
|
|
198
|
+
* (`FullRecordJSON`), which restore reads; only the per-field diff (`ChangesJSON`) and its
|
|
199
|
+
* on-screen rendering use this, so a change to an embedding or a file is visible without carrying
|
|
200
|
+
* the content twice more per save. Invalid base64 is reported as such rather than thrown on.
|
|
201
|
+
*
|
|
202
|
+
* @param base64 - The field's value as held above the database.
|
|
203
|
+
*/
|
|
204
|
+
export function FormatBinaryChangeValue(base64) {
|
|
205
|
+
if (!IsValidBase64(base64))
|
|
206
|
+
return `${BINARY_CHANGE_VALUE_PREFIX} invalid base64]`;
|
|
207
|
+
return `${BINARY_CHANGE_VALUE_PREFIX} ${Base64DecodedByteLength(base64).toLocaleString('en-US')} bytes]`;
|
|
208
|
+
}
|
|
209
|
+
/** Whether `value` is text produced by {@link FormatBinaryChangeValue}, so it is shown as-is and never decoded. */
|
|
210
|
+
export function IsBinaryChangeValue(value) {
|
|
211
|
+
return typeof value === 'string' && value.startsWith(BINARY_CHANGE_VALUE_PREFIX) && value.endsWith(']');
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Encodes bytes as standard, padded base64 using the fastest implementation the host offers.
|
|
215
|
+
*
|
|
216
|
+
* @param bytes - The bytes to encode. Any `Uint8Array` works, including a Node `Buffer` or a view
|
|
217
|
+
* into a larger buffer; only the viewed range is encoded.
|
|
218
|
+
* @returns The base64 string; `''` for zero bytes.
|
|
219
|
+
*/
|
|
220
|
+
export function BytesToBase64(bytes) {
|
|
221
|
+
if (bytes.length === 0)
|
|
222
|
+
return '';
|
|
223
|
+
return GetBase64Codec().Encode(bytes);
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Decodes base64 into a new `Uint8Array` that owns its own `ArrayBuffer` (offset 0, never a view
|
|
227
|
+
* into a shared pool), using the fastest implementation the host offers.
|
|
228
|
+
*
|
|
229
|
+
* @param base64 - Canonical base64 (see {@link IsValidBase64}); padding may be omitted.
|
|
230
|
+
* @returns The decoded bytes.
|
|
231
|
+
* @throws Error when `base64` is not valid base64. Use {@link TryBase64ToBytes} to get `null` instead.
|
|
232
|
+
*/
|
|
233
|
+
export function Base64ToBytes(base64) {
|
|
234
|
+
const bytes = decodeValidated(base64);
|
|
235
|
+
if (!bytes) {
|
|
236
|
+
throw new Error(`Invalid base64 value (length ${base64.length})`);
|
|
237
|
+
}
|
|
238
|
+
return bytes;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Validates and decodes in one step: the codec's fused {@link Base64Codec.TryDecode} when it has
|
|
242
|
+
* one, else {@link IsValidBase64} followed by a plain decode. `null` when invalid.
|
|
243
|
+
*/
|
|
244
|
+
function decodeValidated(base64) {
|
|
245
|
+
if (base64.length === 0)
|
|
246
|
+
return new Uint8Array(0);
|
|
247
|
+
const codec = findBase64Codec();
|
|
248
|
+
if (codec?.TryDecode)
|
|
249
|
+
return codec.TryDecode(base64);
|
|
250
|
+
// No fused decoder — or no codec at all, in which case invalid input still answers null and
|
|
251
|
+
// only a valid value reaches GetBase64Codec() and its "no implementation" error.
|
|
252
|
+
if (!IsValidBase64(base64))
|
|
253
|
+
return null;
|
|
254
|
+
return (codec ?? GetBase64Codec()).Decode(base64);
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Like {@link Base64ToBytes}, but returns `null` instead of throwing when the input is not a
|
|
258
|
+
* string or not valid base64. Convenient for reading a nullable binary entity field.
|
|
259
|
+
*
|
|
260
|
+
* @param base64 - The value to decode, typically a binary entity field (`string | null`).
|
|
261
|
+
*/
|
|
262
|
+
export function TryBase64ToBytes(base64) {
|
|
263
|
+
return typeof base64 === 'string' ? decodeValidated(base64) : null;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* True when `value` is a byte array: a `Uint8Array`, which includes a Node `Buffer`. Database
|
|
267
|
+
* drivers return binary columns this way; use it at the provider boundary to recognise values
|
|
268
|
+
* that must be converted to base64 before they reach a `BaseEntity`.
|
|
269
|
+
*
|
|
270
|
+
* @param value - Any value.
|
|
271
|
+
*/
|
|
272
|
+
export function IsByteArray(value) {
|
|
273
|
+
return value instanceof Uint8Array;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Returns `row` with every top-level byte-array value replaced by its base64 string.
|
|
277
|
+
*
|
|
278
|
+
* Copy-on-write: when the row holds no byte arrays (the normal case, since the database providers
|
|
279
|
+
* already convert binary columns) the same object is returned untouched; otherwise a shallow copy
|
|
280
|
+
* is returned and the original — which may be a frozen cache entry — is never modified.
|
|
281
|
+
*
|
|
282
|
+
* Use it as a safety net wherever rows are serialized for a transport: `JSON.stringify` turns a
|
|
283
|
+
* Node `Buffer` into `{"type":"Buffer","data":[...]}` and GraphQL's `String` scalar rejects it.
|
|
284
|
+
*
|
|
285
|
+
* @param row - A data row.
|
|
286
|
+
*/
|
|
287
|
+
export function ReplaceByteArraysWithBase64(row) {
|
|
288
|
+
let copy = null;
|
|
289
|
+
for (const key of Object.keys(row)) {
|
|
290
|
+
const value = row[key];
|
|
291
|
+
if (IsByteArray(value)) {
|
|
292
|
+
if (!copy)
|
|
293
|
+
copy = { ...row };
|
|
294
|
+
copy[key] = BytesToBase64(value);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return (copy ?? row);
|
|
298
|
+
}
|
|
299
|
+
/* ------------------------------------------------------------------------------------------ */
|
|
300
|
+
/* Float32 vectors */
|
|
301
|
+
/* ------------------------------------------------------------------------------------------ */
|
|
302
|
+
/**
|
|
303
|
+
* The byte layout MemberJunction uses for persisted embedding vectors: consecutive
|
|
304
|
+
* IEEE-754 single-precision values, little-endian, 4 bytes each, no header. The dimension count
|
|
305
|
+
* is `byteLength / 4`. This is the layout of a `Float32Array` on every little-endian host, which
|
|
306
|
+
* is every platform MemberJunction runs on; big-endian hosts are still handled correctly.
|
|
307
|
+
*/
|
|
308
|
+
export const FLOAT32_VECTOR_BYTES_PER_VALUE = 4;
|
|
309
|
+
/** True when this host stores multi-byte numbers little-endian. */
|
|
310
|
+
function hostIsLittleEndian() {
|
|
311
|
+
return new Uint8Array(new Uint32Array([1]).buffer)[0] === 1;
|
|
312
|
+
}
|
|
313
|
+
/** Typed-array copy; correct only on little-endian hosts. */
|
|
314
|
+
const TypedArrayFloat32Codec = {
|
|
315
|
+
Name: 'typed array (little-endian host)',
|
|
316
|
+
Encode(values) {
|
|
317
|
+
return new Uint8Array(Float32Array.from(values).buffer);
|
|
318
|
+
},
|
|
319
|
+
Decode(bytes) {
|
|
320
|
+
// Copy first: the source may be a misaligned view (e.g. a pooled Node Buffer), and a
|
|
321
|
+
// Float32Array over it would also alias memory the caller does not own.
|
|
322
|
+
return new Float32Array(bytes.slice().buffer);
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
/** Explicit little-endian reads and writes; correct on every host. */
|
|
326
|
+
const DataViewFloat32Codec = {
|
|
327
|
+
Name: 'DataView (any host)',
|
|
328
|
+
Encode(values) {
|
|
329
|
+
const bytes = new Uint8Array(values.length * FLOAT32_VECTOR_BYTES_PER_VALUE);
|
|
330
|
+
const view = new DataView(bytes.buffer);
|
|
331
|
+
for (let i = 0; i < values.length; i++)
|
|
332
|
+
view.setFloat32(i * FLOAT32_VECTOR_BYTES_PER_VALUE, values[i], true);
|
|
333
|
+
return bytes;
|
|
334
|
+
},
|
|
335
|
+
Decode(bytes) {
|
|
336
|
+
const count = bytes.byteLength / FLOAT32_VECTOR_BYTES_PER_VALUE;
|
|
337
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
338
|
+
const values = new Float32Array(count);
|
|
339
|
+
for (let i = 0; i < count; i++)
|
|
340
|
+
values[i] = view.getFloat32(i * FLOAT32_VECTOR_BYTES_PER_VALUE, true);
|
|
341
|
+
return values;
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
/** Both float32 vector implementations. Exposed for tests; use the functions below instead. */
|
|
345
|
+
export const Float32VectorCodecs = [TypedArrayFloat32Codec, DataViewFloat32Codec];
|
|
346
|
+
const float32Codec = hostIsLittleEndian() ? TypedArrayFloat32Codec : DataViewFloat32Codec;
|
|
347
|
+
/**
|
|
348
|
+
* Encodes a vector as little-endian float32 bytes. Values are rounded to single precision, which
|
|
349
|
+
* is the precision embedding models produce; `NaN` and infinities are stored as-is.
|
|
350
|
+
*
|
|
351
|
+
* @param values - The vector, e.g. a `number[]` or a `Float32Array`.
|
|
352
|
+
* @returns A new byte array of `values.length × 4` bytes.
|
|
353
|
+
*/
|
|
354
|
+
export function Float32VectorToBytes(values) {
|
|
355
|
+
return float32Codec.Encode(values);
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Decodes little-endian float32 bytes into a new `Float32Array` that owns its memory.
|
|
359
|
+
*
|
|
360
|
+
* @param bytes - Bytes in the {@link FLOAT32_VECTOR_BYTES_PER_VALUE persisted vector layout}.
|
|
361
|
+
* @returns The vector, or `null` when `bytes` is empty or not a whole number of float32 values,
|
|
362
|
+
* so a truncated or foreign value is treated as missing rather than misread.
|
|
363
|
+
*/
|
|
364
|
+
export function BytesToFloat32Vector(bytes) {
|
|
365
|
+
if (!bytes || bytes.byteLength === 0 || bytes.byteLength % FLOAT32_VECTOR_BYTES_PER_VALUE !== 0)
|
|
366
|
+
return null;
|
|
367
|
+
return float32Codec.Decode(bytes);
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Encodes a vector as base64 of its little-endian float32 bytes: the value stored in a binary
|
|
371
|
+
* vector entity field such as `VectorBinary`.
|
|
372
|
+
*
|
|
373
|
+
* @param values - The vector.
|
|
374
|
+
*/
|
|
375
|
+
export function Float32VectorToBase64(values) {
|
|
376
|
+
return BytesToBase64(Float32VectorToBytes(values));
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Decodes a binary vector entity field (base64 of little-endian float32 bytes).
|
|
380
|
+
*
|
|
381
|
+
* @param base64 - The field value; `null` / `undefined` are allowed.
|
|
382
|
+
* @returns The vector, or `null` when the value is missing, not valid base64, empty, or not a
|
|
383
|
+
* whole number of float32 values. Callers usually fall back to a JSON copy of the vector.
|
|
384
|
+
*/
|
|
385
|
+
export function Base64ToFloat32Vector(base64) {
|
|
386
|
+
return BytesToFloat32Vector(TryBase64ToBytes(base64));
|
|
387
|
+
}
|
|
388
|
+
//# sourceMappingURL=BinaryEncoding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BinaryEncoding.js","sourceRoot":"","sources":["../../src/util/BinaryEncoding.ts"],"names":[],"mappings":"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"}
|
package/dist/util.d.ts
CHANGED
|
@@ -280,18 +280,25 @@ export declare function GetIrregularPlural(singularName: string): string | null;
|
|
|
280
280
|
/** @deprecated Use {@link GetIrregularPlural}. */
|
|
281
281
|
export declare function getIrregularPlural(singularName: string): string | null;
|
|
282
282
|
/**
|
|
283
|
-
* Converts a singular
|
|
284
|
-
* and
|
|
283
|
+
* Converts a singular name to its plural form. The head of the name is kept
|
|
284
|
+
* verbatim, including the separator that ends it, and only the tail is pluralized.
|
|
285
|
+
* The tail keeps its own case. A linear scan finds the tail: the last space,
|
|
286
|
+
* underscore, or hyphen, or else the last Unicode capital that starts a word.
|
|
287
|
+
* A regex over ASCII letters is not used, because it drops a leading character
|
|
288
|
+
* such as Ä and it erases the underscore that distinguishes user_profile from
|
|
289
|
+
* userProfile.
|
|
285
290
|
*
|
|
286
|
-
* @param singularName - The singular form of the
|
|
287
|
-
* @returns The plural form of the
|
|
291
|
+
* @param singularName - The singular form of the name to pluralize.
|
|
292
|
+
* @returns The plural form of the name.
|
|
288
293
|
*
|
|
289
294
|
* @example
|
|
290
295
|
* ```typescript
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
296
|
+
* GeneratePluralName('child'); // returns 'children'
|
|
297
|
+
* GeneratePluralName('box'); // returns 'boxes'
|
|
298
|
+
* GeneratePluralName('party'); // returns 'parties'
|
|
299
|
+
* GeneratePluralName('dog'); // returns 'dogs'
|
|
300
|
+
* GeneratePluralName('Contact Person'); // returns 'Contact People'
|
|
301
|
+
* GeneratePluralName('user_profile', { capitalizeFirstLetterOnly: true }); // returns 'user_Profiles'
|
|
295
302
|
* ```
|
|
296
303
|
*/
|
|
297
304
|
export declare function GeneratePluralName(singularName: string, options?: {
|
package/dist/util.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"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"}
|
package/dist/util.js
CHANGED
|
@@ -1130,21 +1130,88 @@ function getSingularForm(word) {
|
|
|
1130
1130
|
return word;
|
|
1131
1131
|
}
|
|
1132
1132
|
/**
|
|
1133
|
-
* Converts a singular
|
|
1134
|
-
* and
|
|
1133
|
+
* Converts a singular name to its plural form. The head of the name is kept
|
|
1134
|
+
* verbatim, including the separator that ends it, and only the tail is pluralized.
|
|
1135
|
+
* The tail keeps its own case. A linear scan finds the tail: the last space,
|
|
1136
|
+
* underscore, or hyphen, or else the last Unicode capital that starts a word.
|
|
1137
|
+
* A regex over ASCII letters is not used, because it drops a leading character
|
|
1138
|
+
* such as Ä and it erases the underscore that distinguishes user_profile from
|
|
1139
|
+
* userProfile.
|
|
1135
1140
|
*
|
|
1136
|
-
* @param singularName - The singular form of the
|
|
1137
|
-
* @returns The plural form of the
|
|
1141
|
+
* @param singularName - The singular form of the name to pluralize.
|
|
1142
|
+
* @returns The plural form of the name.
|
|
1138
1143
|
*
|
|
1139
1144
|
* @example
|
|
1140
1145
|
* ```typescript
|
|
1141
|
-
*
|
|
1142
|
-
*
|
|
1143
|
-
*
|
|
1144
|
-
*
|
|
1146
|
+
* GeneratePluralName('child'); // returns 'children'
|
|
1147
|
+
* GeneratePluralName('box'); // returns 'boxes'
|
|
1148
|
+
* GeneratePluralName('party'); // returns 'parties'
|
|
1149
|
+
* GeneratePluralName('dog'); // returns 'dogs'
|
|
1150
|
+
* GeneratePluralName('Contact Person'); // returns 'Contact People'
|
|
1151
|
+
* GeneratePluralName('user_profile', { capitalizeFirstLetterOnly: true }); // returns 'user_Profiles'
|
|
1145
1152
|
* ```
|
|
1146
1153
|
*/
|
|
1147
1154
|
export function GeneratePluralName(singularName, options) {
|
|
1155
|
+
const name = singularName.trim();
|
|
1156
|
+
if (name.length === 0)
|
|
1157
|
+
return name;
|
|
1158
|
+
const { head, tail } = splitPluralTail(name);
|
|
1159
|
+
if (tail.length === 0) {
|
|
1160
|
+
return options?.capitalizeEntireWord ? name.toUpperCase() : name;
|
|
1161
|
+
}
|
|
1162
|
+
// capitalizeEntireWord applies to the whole name, not only the tail.
|
|
1163
|
+
const tailOptions = options?.capitalizeEntireWord
|
|
1164
|
+
? { ...options, capitalizeEntireWord: false }
|
|
1165
|
+
: options;
|
|
1166
|
+
const joined = head + pluralizeOneWord(tail, tailOptions);
|
|
1167
|
+
return options?.capitalizeEntireWord ? joined.toUpperCase() : joined;
|
|
1168
|
+
}
|
|
1169
|
+
function isUnicodeLetter(ch) {
|
|
1170
|
+
return ch.toLowerCase() !== ch.toUpperCase();
|
|
1171
|
+
}
|
|
1172
|
+
function isUnicodeUpper(ch) {
|
|
1173
|
+
return isUnicodeLetter(ch) && ch === ch.toUpperCase();
|
|
1174
|
+
}
|
|
1175
|
+
function isUnicodeLower(ch) {
|
|
1176
|
+
return isUnicodeLetter(ch) && ch === ch.toLowerCase();
|
|
1177
|
+
}
|
|
1178
|
+
/**
|
|
1179
|
+
* Head is everything through the last separator, kept verbatim. With no
|
|
1180
|
+
* separator, the tail starts at the last capital that begins a new word
|
|
1181
|
+
* (a capital after a lowercase letter, or a capital whose next letter is
|
|
1182
|
+
* lowercase, so an acronym stays on the head).
|
|
1183
|
+
*/
|
|
1184
|
+
function splitPluralTail(name) {
|
|
1185
|
+
for (let i = name.length - 1; i >= 0; i--) {
|
|
1186
|
+
const ch = name[i];
|
|
1187
|
+
if (ch === ' ' || ch === '_' || ch === '-' || ch === '\t') {
|
|
1188
|
+
return { head: name.slice(0, i + 1), tail: name.slice(i + 1) };
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
let tailStart = 0;
|
|
1192
|
+
for (let i = 1; i < name.length; i++) {
|
|
1193
|
+
const current = name[i];
|
|
1194
|
+
if (!isUnicodeUpper(current))
|
|
1195
|
+
continue;
|
|
1196
|
+
const next = i + 1 < name.length ? name[i + 1] : '';
|
|
1197
|
+
if (isUnicodeLower(name[i - 1]) || isUnicodeLower(next))
|
|
1198
|
+
tailStart = i;
|
|
1199
|
+
}
|
|
1200
|
+
if (tailStart > 0)
|
|
1201
|
+
return { head: name.slice(0, tailStart), tail: name.slice(tailStart) };
|
|
1202
|
+
return { head: '', tail: name };
|
|
1203
|
+
}
|
|
1204
|
+
function matchTailCase(source, pluralLower) {
|
|
1205
|
+
const letters = [...source].filter(isUnicodeLetter);
|
|
1206
|
+
if (letters.length > 0 && letters.every(isUnicodeUpper))
|
|
1207
|
+
return pluralLower.toUpperCase();
|
|
1208
|
+
const first = source.charAt(0);
|
|
1209
|
+
if (first && isUnicodeUpper(first)) {
|
|
1210
|
+
return pluralLower.charAt(0).toUpperCase() + pluralLower.slice(1);
|
|
1211
|
+
}
|
|
1212
|
+
return pluralLower;
|
|
1213
|
+
}
|
|
1214
|
+
function pluralizeOneWord(singularName, options) {
|
|
1148
1215
|
// Check if it's already plural
|
|
1149
1216
|
const detectedSingular = getSingularForm(singularName);
|
|
1150
1217
|
if (!detectedSingular) {
|
|
@@ -1158,10 +1225,11 @@ export function GeneratePluralName(singularName, options) {
|
|
|
1158
1225
|
// but if we were passed a true singular, then we keep on going
|
|
1159
1226
|
return AdjustCasing(singularName, options);
|
|
1160
1227
|
}
|
|
1161
|
-
//
|
|
1228
|
+
// The irregular map stores lowercase values. Restore the tail's case so
|
|
1229
|
+
// "Person" becomes "People" and "PERSON" becomes "PEOPLE".
|
|
1162
1230
|
const irregularPlural = GetIrregularPlural(singularName);
|
|
1163
1231
|
if (irregularPlural) {
|
|
1164
|
-
return AdjustCasing(irregularPlural, options);
|
|
1232
|
+
return AdjustCasing(matchTailCase(singularName, irregularPlural), options);
|
|
1165
1233
|
}
|
|
1166
1234
|
// Handle common pluralization rules
|
|
1167
1235
|
if (singularName.endsWith('y') && singularName.length > 1) {
|