sliftutils 1.4.87 → 1.5.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/index.d.ts +14 -6
- package/misc/https/certs.d.ts +6 -5
- package/misc/https/certs.ts +36 -27
- package/misc/strings.d.ts +1 -0
- package/misc/strings.ts +8 -0
- package/package.json +1 -1
- package/storage/BulkDatabase2/BulkDatabaseFormat.d.ts +2 -0
- package/storage/BulkDatabase2/BulkDatabaseFormat.ts +5 -0
- package/storage/BulkDatabase2/LoadedIndex.ts +28 -4
- package/storage/BulkDatabase2/streamLog.ts +1 -0
- package/storage/proxydatabase/ivfDbCheck.ts +1 -1
- package/storage/proxydatabase/ivfEmbeddingDatabase.d.ts +1 -1
- package/storage/proxydatabase/ivfEmbeddingDatabase.ts +7 -31
package/index.d.ts
CHANGED
|
@@ -72,7 +72,7 @@ declare module "sliftutils/misc/https/certs" {
|
|
|
72
72
|
import * as forge from "node-forge";
|
|
73
73
|
import { MaybePromise } from "socket-function/src/types";
|
|
74
74
|
export declare const CA_NOT_FOUND_ERROR = "18aa7318-f88f-4d2d-b41f-3daf4a433827";
|
|
75
|
-
export declare const identityStorageKey = "
|
|
75
|
+
export declare const identityStorageKey = "machineCA_11";
|
|
76
76
|
export type IdentityStorageType = {
|
|
77
77
|
domain: string;
|
|
78
78
|
certB64: string;
|
|
@@ -99,7 +99,7 @@ declare module "sliftutils/misc/https/certs" {
|
|
|
99
99
|
export declare const sign: (keyPair: {
|
|
100
100
|
key: string | Buffer;
|
|
101
101
|
}, data: unknown) => string;
|
|
102
|
-
export declare function verify(cert: string, signature: string, data: unknown):
|
|
102
|
+
export declare function verify(cert: string, signature: string, data: unknown): void;
|
|
103
103
|
export declare function validateCACert(domain: string, cert: string | Buffer): void;
|
|
104
104
|
export declare function validateCertificate(domain: string, cert: Buffer | string, issuerCert: Buffer | string): void;
|
|
105
105
|
export declare function generateKeyPair(): forge.pki.rsa.KeyPair;
|
|
@@ -108,21 +108,22 @@ declare module "sliftutils/misc/https/certs" {
|
|
|
108
108
|
export declare function createCertFromCA(config: {
|
|
109
109
|
CAKeyPair: X509KeyPair;
|
|
110
110
|
}): X509KeyPair;
|
|
111
|
-
export declare function getMachineId(
|
|
111
|
+
export declare function getMachineId(domainNameOrNodeId: string, domain: string): string;
|
|
112
112
|
export type NodeIdParts = {
|
|
113
113
|
threadId: string;
|
|
114
114
|
machineId: string;
|
|
115
115
|
domain: string;
|
|
116
116
|
port: number;
|
|
117
117
|
};
|
|
118
|
-
export declare function decodeNodeId(nodeId: string): NodeIdParts | undefined;
|
|
119
|
-
export declare function decodeNodeIdAssert(nodeId: string): NodeIdParts;
|
|
118
|
+
export declare function decodeNodeId(nodeId: string, domain: string, allowMissingThreadId?: "allowMissingThreadId"): NodeIdParts | undefined;
|
|
119
|
+
export declare function decodeNodeIdAssert(nodeId: string, domain: string, allowMissingThreadId?: "allowMissingThreadId"): NodeIdParts;
|
|
120
120
|
export declare function encodeNodeId(parts: NodeIdParts): string;
|
|
121
121
|
export declare function setIdentityCARaw(domain: string, json: string): Promise<void>;
|
|
122
122
|
export declare function loadIdentityCA(domain: string): Promise<void>;
|
|
123
123
|
export declare function getIdentityCA(domain: string): X509KeyPair;
|
|
124
124
|
export declare function getIdentityCAPromise(domain: string): MaybePromise<X509KeyPair>;
|
|
125
125
|
export declare function getOwnMachineId(domain: string): string;
|
|
126
|
+
export declare function getOwnThreadId(domain: string): string;
|
|
126
127
|
/** Part of the machineId comes from the publicKey, so we can use it to verify */
|
|
127
128
|
export declare function verifyMachineIdForPublicKey(config: {
|
|
128
129
|
machineId: string;
|
|
@@ -291,6 +292,11 @@ declare module "sliftutils/misc/random" {
|
|
|
291
292
|
|
|
292
293
|
}
|
|
293
294
|
|
|
295
|
+
declare module "sliftutils/misc/strings" {
|
|
296
|
+
export declare function ellipsize(text: string, maxLength: number): string;
|
|
297
|
+
|
|
298
|
+
}
|
|
299
|
+
|
|
294
300
|
declare module "sliftutils/misc/types" {
|
|
295
301
|
export declare function isDefined<T>(value: T | undefined | null): value is T;
|
|
296
302
|
export declare function freezeObject(value: unknown): unknown;
|
|
@@ -1153,6 +1159,7 @@ declare module "sliftutils/storage/BulkDatabase2/BulkDatabaseFormat" {
|
|
|
1153
1159
|
export declare function buildFileBuffer(rows: Record<string, unknown>[], times: number[], targetBytes?: number): BuiltFile[];
|
|
1154
1160
|
export declare function buildFileBufferRaw(rows: RawRow[], targetBytes?: number): BuiltFile[];
|
|
1155
1161
|
export type BaseBulkDatabaseReader = {
|
|
1162
|
+
name?: string;
|
|
1156
1163
|
rowCount: number;
|
|
1157
1164
|
totalBytes: number;
|
|
1158
1165
|
minTime: number;
|
|
@@ -1194,6 +1201,7 @@ declare module "sliftutils/storage/BulkDatabase2/BulkDatabaseFormat" {
|
|
|
1194
1201
|
export declare function loadBulkDatabase(config: {
|
|
1195
1202
|
totalBytes: number;
|
|
1196
1203
|
getRange: (start: number, end: number) => Promise<Buffer>;
|
|
1204
|
+
name?: string;
|
|
1197
1205
|
}): Promise<BaseBulkDatabaseReader>;
|
|
1198
1206
|
|
|
1199
1207
|
}
|
|
@@ -2393,7 +2401,7 @@ declare module "sliftutils/storage/proxydatabase/ivfEmbeddingDatabase" {
|
|
|
2393
2401
|
};
|
|
2394
2402
|
export declare function rebuildStructure(database: Database<IvfEmbeddingRoot>): void;
|
|
2395
2403
|
export declare function searchEmbeddings(database: Database<IvfEmbeddingRoot>, query: StoredEmbedding, options: {
|
|
2396
|
-
|
|
2404
|
+
probeBudget: number;
|
|
2397
2405
|
resultCount: number;
|
|
2398
2406
|
}): SearchHit[] | undefined;
|
|
2399
2407
|
export declare function lookupEmbeddings(database: Database<IvfEmbeddingRoot>, refs: string[]): Map<string, StoredEmbedding> | undefined;
|
package/misc/https/certs.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import * as forge from "node-forge";
|
|
6
6
|
import { MaybePromise } from "socket-function/src/types";
|
|
7
7
|
export declare const CA_NOT_FOUND_ERROR = "18aa7318-f88f-4d2d-b41f-3daf4a433827";
|
|
8
|
-
export declare const identityStorageKey = "
|
|
8
|
+
export declare const identityStorageKey = "machineCA_11";
|
|
9
9
|
export type IdentityStorageType = {
|
|
10
10
|
domain: string;
|
|
11
11
|
certB64: string;
|
|
@@ -32,7 +32,7 @@ export declare function getPublicIdentifier(PEMorDER: string | Buffer): Buffer;
|
|
|
32
32
|
export declare const sign: (keyPair: {
|
|
33
33
|
key: string | Buffer;
|
|
34
34
|
}, data: unknown) => string;
|
|
35
|
-
export declare function verify(cert: string, signature: string, data: unknown):
|
|
35
|
+
export declare function verify(cert: string, signature: string, data: unknown): void;
|
|
36
36
|
export declare function validateCACert(domain: string, cert: string | Buffer): void;
|
|
37
37
|
export declare function validateCertificate(domain: string, cert: Buffer | string, issuerCert: Buffer | string): void;
|
|
38
38
|
export declare function generateKeyPair(): forge.pki.rsa.KeyPair;
|
|
@@ -41,21 +41,22 @@ export declare function generateTestCA(domain: string): X509KeyPair;
|
|
|
41
41
|
export declare function createCertFromCA(config: {
|
|
42
42
|
CAKeyPair: X509KeyPair;
|
|
43
43
|
}): X509KeyPair;
|
|
44
|
-
export declare function getMachineId(
|
|
44
|
+
export declare function getMachineId(domainNameOrNodeId: string, domain: string): string;
|
|
45
45
|
export type NodeIdParts = {
|
|
46
46
|
threadId: string;
|
|
47
47
|
machineId: string;
|
|
48
48
|
domain: string;
|
|
49
49
|
port: number;
|
|
50
50
|
};
|
|
51
|
-
export declare function decodeNodeId(nodeId: string): NodeIdParts | undefined;
|
|
52
|
-
export declare function decodeNodeIdAssert(nodeId: string): NodeIdParts;
|
|
51
|
+
export declare function decodeNodeId(nodeId: string, domain: string, allowMissingThreadId?: "allowMissingThreadId"): NodeIdParts | undefined;
|
|
52
|
+
export declare function decodeNodeIdAssert(nodeId: string, domain: string, allowMissingThreadId?: "allowMissingThreadId"): NodeIdParts;
|
|
53
53
|
export declare function encodeNodeId(parts: NodeIdParts): string;
|
|
54
54
|
export declare function setIdentityCARaw(domain: string, json: string): Promise<void>;
|
|
55
55
|
export declare function loadIdentityCA(domain: string): Promise<void>;
|
|
56
56
|
export declare function getIdentityCA(domain: string): X509KeyPair;
|
|
57
57
|
export declare function getIdentityCAPromise(domain: string): MaybePromise<X509KeyPair>;
|
|
58
58
|
export declare function getOwnMachineId(domain: string): string;
|
|
59
|
+
export declare function getOwnThreadId(domain: string): string;
|
|
59
60
|
/** Part of the machineId comes from the publicKey, so we can use it to verify */
|
|
60
61
|
export declare function verifyMachineIdForPublicKey(config: {
|
|
61
62
|
machineId: string;
|
package/misc/https/certs.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { MaybePromise } from "socket-function/src/types";
|
|
|
19
19
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
20
20
|
import { resetAllNodeCallFactories } from "socket-function/src/nodeCache";
|
|
21
21
|
import { getKeyStore } from "./persistentLocalStorage";
|
|
22
|
+
import { ellipsize } from "../strings";
|
|
22
23
|
|
|
23
24
|
setFlag(require, "node-forge", "allowclient", true);
|
|
24
25
|
setFlag(require, "js-sha256", "allowclient", true);
|
|
@@ -27,7 +28,7 @@ const timeInDay = 1000 * 60 * 60 * 24;
|
|
|
27
28
|
|
|
28
29
|
export const CA_NOT_FOUND_ERROR = "18aa7318-f88f-4d2d-b41f-3daf4a433827";
|
|
29
30
|
|
|
30
|
-
export const identityStorageKey = "
|
|
31
|
+
export const identityStorageKey = "machineCA_11";
|
|
31
32
|
export type IdentityStorageType = { domain: string; certB64: string; keyB64: string };
|
|
32
33
|
|
|
33
34
|
function getIdentityStore(domain: string) {
|
|
@@ -99,21 +100,17 @@ export function createX509(
|
|
|
99
100
|
//{ type: 7, ip: "127.0.0.1" }
|
|
100
101
|
]
|
|
101
102
|
},
|
|
102
|
-
// NOTE: nameConstraints
|
|
103
|
+
// NOTE: nameConstraints require our forked node-forge:
|
|
103
104
|
// "node-forge": "https://github.com/sliftist/forge#e618181b469b07bdc70b968b0391beb8ef5fecd6",
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
// { type: 2, value: forge.util.encodeUtf8(domain) },
|
|
114
|
-
// { type: 2, value: forge.util.encodeUtf8(localHostDomain) },
|
|
115
|
-
// ]
|
|
116
|
-
// },
|
|
105
|
+
// Chrome ignores them (https://bugs.chromium.org/p/chromium/issues/detail?id=1072083),
|
|
106
|
+
// but our own validation (validateCACert/validateCertificate) enforces them.
|
|
107
|
+
{
|
|
108
|
+
name: "nameConstraints",
|
|
109
|
+
permittedSubtrees: [
|
|
110
|
+
{ type: 2, value: forge.util.encodeUtf8(domain) },
|
|
111
|
+
{ type: 2, value: forge.util.encodeUtf8(localHostDomain) },
|
|
112
|
+
]
|
|
113
|
+
},
|
|
117
114
|
]);
|
|
118
115
|
certObj.setExtensions(extensions);
|
|
119
116
|
|
|
@@ -191,7 +188,9 @@ export const sign = measureWrap(function sign(keyPair: { key: string | Buffer },
|
|
|
191
188
|
|
|
192
189
|
export function verify(cert: string, signature: string, data: unknown) {
|
|
193
190
|
let certObj = parseCert(cert);
|
|
194
|
-
|
|
191
|
+
if (!(certObj.publicKey as forge.pki.rsa.PublicKey).verify(JSON.stringify(data), signature)) {
|
|
192
|
+
throw new Error(`Signature verification failed. Signature: ${JSON.stringify(signature)} | Data: ${ellipsize(JSON.stringify(data), 1024)}`);
|
|
193
|
+
}
|
|
195
194
|
}
|
|
196
195
|
|
|
197
196
|
function normalizeCertToPEM(PEMorDER: string | Buffer): string {
|
|
@@ -211,7 +210,7 @@ function getDomainPartFromPublicKey(publicKey: { publicKeyBytes: Buffer } | forg
|
|
|
211
210
|
} else {
|
|
212
211
|
bytes = Buffer.from(new Uint32Array((publicKey as any).n.data).buffer);
|
|
213
212
|
}
|
|
214
|
-
return "b" + sha265.sha256(Buffer.from(bytes)).slice(0, 16).replaceAll("+", "-").replaceAll("/", "_");
|
|
213
|
+
return "b" + Buffer.from(sha265.sha256.array(Buffer.from(bytes))).toString("base64").slice(0, 16).replaceAll("+", "-").replaceAll("/", "_");
|
|
215
214
|
}
|
|
216
215
|
|
|
217
216
|
export function validateCACert(domain: string, cert: string | Buffer) {
|
|
@@ -440,8 +439,8 @@ export function createCertFromCA(config: {
|
|
|
440
439
|
});
|
|
441
440
|
}
|
|
442
441
|
|
|
443
|
-
export function getMachineId(
|
|
444
|
-
return
|
|
442
|
+
export function getMachineId(domainNameOrNodeId: string, domain: string) {
|
|
443
|
+
return decodeNodeIdAssert(domainNameOrNodeId, domain, "allowMissingThreadId").machineId;
|
|
445
444
|
}
|
|
446
445
|
|
|
447
446
|
export type NodeIdParts = {
|
|
@@ -450,13 +449,18 @@ export type NodeIdParts = {
|
|
|
450
449
|
domain: string;
|
|
451
450
|
port: number;
|
|
452
451
|
};
|
|
453
|
-
export function decodeNodeId(nodeId: string): NodeIdParts | undefined {
|
|
452
|
+
export function decodeNodeId(nodeId: string, domain: string, allowMissingThreadId?: "allowMissingThreadId"): NodeIdParts | undefined {
|
|
454
453
|
let locationObj = getNodeIdLocation(nodeId);
|
|
455
454
|
if (!locationObj) {
|
|
456
455
|
return undefined;
|
|
457
456
|
}
|
|
458
457
|
let parts = locationObj.address.split(".");
|
|
459
|
-
|
|
458
|
+
// NOTE: We have to only allow localhost domains on our own domain, as the underlying domain
|
|
459
|
+
// gets stripped when we're looking at the machineId. So if we allowed localhost domains on
|
|
460
|
+
// other domains, a server could trick us into connecting to it, and then once the connection
|
|
461
|
+
// is established, it could talk back and we would think it has a localhost machineId, which
|
|
462
|
+
// is implicitly trusted, which would then give it access to everything.
|
|
463
|
+
if (locationObj.address === `127-0-0-1.${domain}` && nodeId.includes(":")) {
|
|
460
464
|
return {
|
|
461
465
|
threadId: "",
|
|
462
466
|
machineId: parts.at(-3) || "",
|
|
@@ -464,7 +468,8 @@ export function decodeNodeId(nodeId: string): NodeIdParts | undefined {
|
|
|
464
468
|
port: locationObj.port,
|
|
465
469
|
};
|
|
466
470
|
}
|
|
467
|
-
|
|
471
|
+
let isValid = parts.length >= 4 || allowMissingThreadId && parts.length === 3;
|
|
472
|
+
if (!isValid) {
|
|
468
473
|
return undefined;
|
|
469
474
|
}
|
|
470
475
|
return {
|
|
@@ -474,10 +479,10 @@ export function decodeNodeId(nodeId: string): NodeIdParts | undefined {
|
|
|
474
479
|
port: locationObj.port,
|
|
475
480
|
};
|
|
476
481
|
}
|
|
477
|
-
export function decodeNodeIdAssert(nodeId: string): NodeIdParts {
|
|
478
|
-
let result = decodeNodeId(nodeId);
|
|
482
|
+
export function decodeNodeIdAssert(nodeId: string, domain: string, allowMissingThreadId?: "allowMissingThreadId"): NodeIdParts {
|
|
483
|
+
let result = decodeNodeId(nodeId, domain, allowMissingThreadId);
|
|
479
484
|
if (!result) {
|
|
480
|
-
throw new Error(`Invalid nodeId: ${nodeId}`);
|
|
485
|
+
throw new Error(`Invalid nodeId: ${JSON.stringify(nodeId)}`);
|
|
481
486
|
}
|
|
482
487
|
return result;
|
|
483
488
|
}
|
|
@@ -523,7 +528,10 @@ export function getIdentityCAPromise(domain: string): MaybePromise<X509KeyPair>
|
|
|
523
528
|
|
|
524
529
|
|
|
525
530
|
export function getOwnMachineId(domain: string) {
|
|
526
|
-
return getMachineId(getIdentityCA(domain).domain);
|
|
531
|
+
return getMachineId(getIdentityCA(domain).domain, domain);
|
|
532
|
+
}
|
|
533
|
+
export function getOwnThreadId(domain: string) {
|
|
534
|
+
return decodeNodeIdAssert(getThreadKeyCert(domain).domain, domain).threadId;
|
|
527
535
|
}
|
|
528
536
|
|
|
529
537
|
/** Part of the machineId comes from the publicKey, so we can use it to verify */
|
|
@@ -533,7 +541,8 @@ export function verifyMachineIdForPublicKey(config: {
|
|
|
533
541
|
}): boolean {
|
|
534
542
|
let { machineId, publicKey } = config;
|
|
535
543
|
let domainPart = getDomainPartFromPublicKey(publicKey);
|
|
536
|
-
|
|
544
|
+
// machineId is the bare key-hash label, but also accept legacy "hash.domain.tld" forms
|
|
545
|
+
return machineId.split(".")[0] === domainPart;
|
|
537
546
|
}
|
|
538
547
|
|
|
539
548
|
// NOTE: We don't have a cache per CA, as... the CA should be set first
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function ellipsize(text: string, maxLength: number): string;
|
package/misc/strings.ts
ADDED
package/package.json
CHANGED
|
@@ -41,6 +41,7 @@ export interface BuiltFile {
|
|
|
41
41
|
export declare function buildFileBuffer(rows: Record<string, unknown>[], times: number[], targetBytes?: number): BuiltFile[];
|
|
42
42
|
export declare function buildFileBufferRaw(rows: RawRow[], targetBytes?: number): BuiltFile[];
|
|
43
43
|
export type BaseBulkDatabaseReader = {
|
|
44
|
+
name?: string;
|
|
44
45
|
rowCount: number;
|
|
45
46
|
totalBytes: number;
|
|
46
47
|
minTime: number;
|
|
@@ -82,4 +83,5 @@ export declare function loadBulkHeader(getRange: (start: number, end: number) =>
|
|
|
82
83
|
export declare function loadBulkDatabase(config: {
|
|
83
84
|
totalBytes: number;
|
|
84
85
|
getRange: (start: number, end: number) => Promise<Buffer>;
|
|
86
|
+
name?: string;
|
|
85
87
|
}): Promise<BaseBulkDatabaseReader>;
|
|
@@ -410,6 +410,9 @@ export function buildFileBufferRaw(rows: RawRow[], targetBytes = TARGET_FILE_BYT
|
|
|
410
410
|
}
|
|
411
411
|
|
|
412
412
|
export type BaseBulkDatabaseReader = {
|
|
413
|
+
// Identifies the source this reader came from (the bulk file name, or "(streams)") so the join can
|
|
414
|
+
// name the offending file when one of its reads fails. Undefined for readers built without one.
|
|
415
|
+
name?: string;
|
|
413
416
|
rowCount: number;
|
|
414
417
|
totalBytes: number;
|
|
415
418
|
// Write-time bounds of this reader's data (0 if unknown — old bulk files).
|
|
@@ -468,6 +471,7 @@ export async function loadBulkHeader(getRange: (start: number, end: number) => P
|
|
|
468
471
|
export async function loadBulkDatabase(config: {
|
|
469
472
|
totalBytes: number;
|
|
470
473
|
getRange: (start: number, end: number) => Promise<Buffer>;
|
|
474
|
+
name?: string;
|
|
471
475
|
}): Promise<BaseBulkDatabaseReader> {
|
|
472
476
|
const headerLength = (await config.getRange(0, 4)).readUInt32LE(0);
|
|
473
477
|
if (headerLength <= 0 || headerLength > config.totalBytes) {
|
|
@@ -502,6 +506,7 @@ export async function loadBulkDatabase(config: {
|
|
|
502
506
|
: keys.map(() => header.maxTime || 0);
|
|
503
507
|
|
|
504
508
|
return {
|
|
509
|
+
name: config.name,
|
|
505
510
|
rowCount,
|
|
506
511
|
totalBytes: config.totalBytes,
|
|
507
512
|
minTime: header.minTime || 0,
|
|
@@ -257,7 +257,7 @@ export async function loadFileReader(name: string, storage: FileStorage, f: Bulk
|
|
|
257
257
|
const raw = await makeRawGetRange(storage, f.fileName);
|
|
258
258
|
const fileId = nullJoin(name, f.fileName);
|
|
259
259
|
const opened = await blockCache.open(fileId, raw.size, raw.rawGetRange);
|
|
260
|
-
const reader = await loadBulkDatabase({ totalBytes: opened.uncompressedSize, getRange: opened.getRange });
|
|
260
|
+
const reader = await loadBulkDatabase({ totalBytes: opened.uncompressedSize, getRange: opened.getRange, name: f.fileName });
|
|
261
261
|
cache.set(f.fileName, reader);
|
|
262
262
|
return reader;
|
|
263
263
|
}
|
|
@@ -327,6 +327,19 @@ function pruneSubCaches(bulkFiles: BulkFileInfo[], streamFiles: StreamFileInfo[]
|
|
|
327
327
|
for (const n of subCaches.stream.keys()) if (!liveStream.has(n)) subCaches.stream.delete(n);
|
|
328
328
|
}
|
|
329
329
|
|
|
330
|
+
// A corrupt/unreadable column in ONE underlying file must not break the whole joined read. When a
|
|
331
|
+
// source's column read throws, we drop that source for that column (falling through to older readers,
|
|
332
|
+
// exactly as if the file never set the column) and warn once per (file, column) so the corruption is
|
|
333
|
+
// visible without flooding the log on every rebuild.
|
|
334
|
+
const warnedCorruptReads = new Set<string>();
|
|
335
|
+
function warnCorruptRead(db: BaseBulkDatabaseReader, column: string, e: unknown): void {
|
|
336
|
+
const fileName = db.name || "(unknown source)";
|
|
337
|
+
const dedupeKey = nullJoin(fileName, column);
|
|
338
|
+
if (warnedCorruptReads.has(dedupeKey)) return;
|
|
339
|
+
warnedCorruptReads.add(dedupeKey);
|
|
340
|
+
console.warn(`${red("corrupt read")}: file ${fileName} column ${JSON.stringify(column)} could not be read - skipping it (its data is dropped from results): ${(e as Error).message}`);
|
|
341
|
+
}
|
|
342
|
+
|
|
330
343
|
function joinBulkDatabases(databases: BaseBulkDatabaseReader[]): ResolvedReader {
|
|
331
344
|
const deleteTime = new Map<string, number>();
|
|
332
345
|
for (const db of databases) {
|
|
@@ -367,8 +380,13 @@ function joinBulkDatabases(databases: BaseBulkDatabaseReader[]): ResolvedReader
|
|
|
367
380
|
async getColumn(column) {
|
|
368
381
|
const perReader = await Promise.all(databases.map(async db => {
|
|
369
382
|
if (!db.columns.some(c => c.column === column)) return undefined;
|
|
370
|
-
|
|
371
|
-
|
|
383
|
+
try {
|
|
384
|
+
const entries = await db.getColumn(column);
|
|
385
|
+
return new Map(entries.map(e => [e.key, { value: e.value, time: e.time }]));
|
|
386
|
+
} catch (e) {
|
|
387
|
+
warnCorruptRead(db, column, e);
|
|
388
|
+
return undefined;
|
|
389
|
+
}
|
|
372
390
|
}));
|
|
373
391
|
return keys.map(key => {
|
|
374
392
|
let bestTime = -Infinity;
|
|
@@ -391,7 +409,13 @@ function joinBulkDatabases(databases: BaseBulkDatabaseReader[]): ResolvedReader
|
|
|
391
409
|
let found = false;
|
|
392
410
|
for (const db of databases) {
|
|
393
411
|
if (!db.columns.some(c => c.column === column)) continue;
|
|
394
|
-
|
|
412
|
+
let r;
|
|
413
|
+
try {
|
|
414
|
+
r = await db.getSingleField(key, column);
|
|
415
|
+
} catch (e) {
|
|
416
|
+
warnCorruptRead(db, column, e);
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
395
419
|
if (r === ABSENT) continue;
|
|
396
420
|
if (r.time > bestTime) { bestTime = r.time; bestVal = r.value; found = true; }
|
|
397
421
|
}
|
|
@@ -106,6 +106,7 @@ export function streamReaderFromEntries(entries: StreamEntry[], totalBytes: numb
|
|
|
106
106
|
}
|
|
107
107
|
const keyIndexMap = buildKeyIndex(keys);
|
|
108
108
|
let reader: BaseBulkDatabaseReader = {
|
|
109
|
+
name: "(streams)",
|
|
109
110
|
totalBytes,
|
|
110
111
|
rowCount: keys.length,
|
|
111
112
|
minTime: times.size ? minTime : 0,
|
|
@@ -72,7 +72,7 @@ async function main() {
|
|
|
72
72
|
const sampleCount = 100;
|
|
73
73
|
for (let index = 0; index < sampleCount; index++) {
|
|
74
74
|
const query = allInputs[Math.floor(index * run.size / sampleCount)];
|
|
75
|
-
const hits = searchEmbeddings(database, query.embedding, {
|
|
75
|
+
const hits = searchEmbeddings(database, query.embedding, { probeBudget: 512, resultCount: 1 });
|
|
76
76
|
if (hits && hits.length && hits[0].ref === query.ref) {
|
|
77
77
|
found++;
|
|
78
78
|
}
|
|
@@ -31,7 +31,7 @@ export type SearchHit = {
|
|
|
31
31
|
};
|
|
32
32
|
export declare function rebuildStructure(database: Database<IvfEmbeddingRoot>): void;
|
|
33
33
|
export declare function searchEmbeddings(database: Database<IvfEmbeddingRoot>, query: StoredEmbedding, options: {
|
|
34
|
-
|
|
34
|
+
probeBudget: number;
|
|
35
35
|
resultCount: number;
|
|
36
36
|
}): SearchHit[] | undefined;
|
|
37
37
|
export declare function lookupEmbeddings(database: Database<IvfEmbeddingRoot>, refs: string[]): Map<string, StoredEmbedding> | undefined;
|
|
@@ -2,7 +2,6 @@ import cborx from "cbor-x";
|
|
|
2
2
|
import { Database, namespaceDatabase } from "./Database";
|
|
3
3
|
import { TransactionSetStore, transactionRead, transactionMutate, transactionDelete, replayTransactionStore } from "./transactionSet";
|
|
4
4
|
import { StoredEmbedding, EmbeddingFormat, getCloseness, embeddingToFloat32, releaseFloat32, encodeEmbedding, hashEmbedding } from "../embeddingFormats";
|
|
5
|
-
import { isDefined } from "../../misc/types";
|
|
6
5
|
|
|
7
6
|
export type IvfConfig = {
|
|
8
7
|
model: string;
|
|
@@ -218,8 +217,7 @@ export function rebuildStructure(database: Database<IvfEmbeddingRoot>): void {
|
|
|
218
217
|
export function searchEmbeddings(
|
|
219
218
|
database: Database<IvfEmbeddingRoot>,
|
|
220
219
|
query: StoredEmbedding,
|
|
221
|
-
|
|
222
|
-
options: { probeCellCount: number; resultCount: number },
|
|
220
|
+
options: { probeBudget: number; resultCount: number },
|
|
223
221
|
): SearchHit[] | undefined {
|
|
224
222
|
const config = database.readData(root => root.config);
|
|
225
223
|
if (!config) return undefined;
|
|
@@ -240,36 +238,14 @@ export function searchEmbeddings(
|
|
|
240
238
|
const centroids = transactionRead(centroidStore(database));
|
|
241
239
|
if (!centroids) return undefined;
|
|
242
240
|
if (!centroids.size) return [];
|
|
243
|
-
const probeCellCount = Math.
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
// A somewhat strange search, trying to load more and more data. The reason we can't load everything at once is because the server has no way of knowing what we want first, and so it'll just wait until it can give us everything at once. By waiting until all of the earlier values load before even requesting the later values, it means that we will receive the earlier values sooner. Which will result in the search immediately returning results with very few accesses, And then the results improving in quality as time goes on, which is very desirable.
|
|
248
|
-
{
|
|
249
|
-
let stopAtNextKeyPoint = false;
|
|
250
|
-
let keyPoints: number[] = [];
|
|
251
|
-
let currentProbeAdd = 4;
|
|
252
|
-
let currentTarget = currentProbeAdd;
|
|
253
|
-
while (currentTarget < probeCellCount) {
|
|
254
|
-
keyPoints.push(currentTarget);
|
|
255
|
-
currentTarget += currentProbeAdd;
|
|
256
|
-
currentProbeAdd *= 2;
|
|
257
|
-
}
|
|
258
|
-
for (let i = 0; i < probeCellCount; i++) {
|
|
259
|
-
if (keyPoints.includes(i) && stopAtNextKeyPoint) break;
|
|
260
|
-
let store = database.readData(root => root.cells[rankedCellIds[i]]);
|
|
261
|
-
if (!store) {
|
|
262
|
-
stopAtNextKeyPoint = true;
|
|
263
|
-
continue;
|
|
264
|
-
}
|
|
265
|
-
stores.push(store);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
241
|
+
const probeCellCount = Math.max(1, Math.ceil(options.probeBudget / config.cellTargetSize));
|
|
242
|
+
const probeCellIds = rankCellsByCloseness(query, centroids).slice(0, probeCellCount);
|
|
243
|
+
const stores = database.readData(root => probeCellIds.map(cellId => root.cells[cellId]));
|
|
244
|
+
if (!stores) return undefined;
|
|
269
245
|
for (const store of stores) {
|
|
270
246
|
const members = replayTransactionStore<StoredEmbedding>(store);
|
|
271
|
-
for (const
|
|
272
|
-
hits.push({ ref, closeness: getCloseness(query,
|
|
247
|
+
for (const ref of members.keys()) {
|
|
248
|
+
hits.push({ ref, closeness: getCloseness(query, members.get(ref)!) });
|
|
273
249
|
}
|
|
274
250
|
}
|
|
275
251
|
hits.sort((left, right) => right.closeness - left.closeness);
|