querysub 0.590.0 → 0.592.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/bin/stop-machine.js +4 -0
- package/package.json +5 -2
- package/src/-a-archives/archiveCache.ts +28 -7
- package/src/-a-archives/archiveCache2.ts +5 -2
- package/src/-a-archives/archives.ts +2 -63
- package/src/-a-archives/archivesDisk.ts +2 -13
- package/src/-a-archives/archivesMemoryCache.ts +27 -17
- package/src/-a-archives/archivesMemoryCache2.ts +23 -0
- package/src/-d-trust/NetworkTrust2.ts +2 -2
- package/src/-e-certs/certAuthority.ts +2 -2
- package/src/-f-node-discovery/NodeDiscovery.ts +2 -2
- package/src/0-path-value-core/AuthorityLookup.ts +0 -4
- package/src/0-path-value-core/ShardPrefixes.ts +2 -4
- package/src/0-path-value-core/archiveLocks/ArchiveLocks2.ts +23 -10
- package/src/0-path-value-core/archiveLocks/archiveSnapshots.ts +0 -1
- package/src/0-path-value-core/pathValueArchives.ts +1 -0
- package/src/4-deploy/edgeBootstrap.ts +50 -11
- package/src/4-deploy/edgeNodes.ts +7 -13
- package/src/4-querysub/Querysub.ts +2 -2
- package/src/config2.ts +1 -7
- package/src/deployManager/components/ServiceMeasureBars.tsx +60 -50
- package/src/deployManager/components/ServicesListPage.tsx +29 -13
- package/src/deployManager/machineDaemonShared.ts +2 -0
- package/src/deployManager/machineSchema.ts +5 -6
- package/src/deployManager/setupMachineMain.ts +1 -2
- package/src/deployManager/stopMachineMain.ts +45 -0
- package/src/diagnostics/logs/IndexedLogs/IndexedLogs.ts +34 -21
- package/src/diagnostics/logs/IndexedLogs/MCPIndexedLogs.ts +3 -3
- package/src/diagnostics/logs/IndexedLogs/TimeFileTree.ts +11 -2
- package/src/diagnostics/logs/IndexedLogs/moveIndexLogsToPublic.ts +3 -4
- package/src/diagnostics/logs/errorTickets/tickets.ts +3 -4
- package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycles.tsx +2 -4
- package/src/diagnostics/watchdog.ts +0 -5
- package/src/-a-archives/archivesBackBlaze.ts +0 -156
- package/src/-a-archives/archivesCborT.ts +0 -52
- package/src/-a-archives/archivesLimitedCache.ts +0 -307
- package/src/-a-archives/archivesPrivateFileSystem.ts +0 -326
- package/src/-a-archives/copyLocalToBackblaze.ts +0 -24
- package/src/-b-authorities/cdnAuthority.ts +0 -53
|
@@ -1,326 +0,0 @@
|
|
|
1
|
-
/// <reference path="../src.d.ts" />
|
|
2
|
-
|
|
3
|
-
import { measureFnc } from "socket-function/src/profiling/measure";
|
|
4
|
-
import { blue } from "socket-function/src/formatting/logColors";
|
|
5
|
-
import { Archives } from "./archives";
|
|
6
|
-
import { cache, lazy } from "socket-function/src/caching";
|
|
7
|
-
import { delay } from "socket-function/src/batching";
|
|
8
|
-
import { isDefined } from "../misc";
|
|
9
|
-
import { formatTime } from "socket-function/src/formatting/format";
|
|
10
|
-
|
|
11
|
-
let getRootDirectory = lazy(async () => {
|
|
12
|
-
await navigator.storage.persist();
|
|
13
|
-
return navigator.storage.getDirectory();
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
class ArchivesPrivateFileSystem {
|
|
17
|
-
public LAG = 0;
|
|
18
|
-
private rootDir: string;
|
|
19
|
-
|
|
20
|
-
private logging = false;
|
|
21
|
-
public enableLogging() {
|
|
22
|
-
this.logging = true;
|
|
23
|
-
}
|
|
24
|
-
private log(text: string) {
|
|
25
|
-
if (!this.logging) return;
|
|
26
|
-
console.log(text);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
constructor(rootDir: string) {
|
|
30
|
-
this.rootDir = rootDir;
|
|
31
|
-
// Ensure rootDir ends with "/" for consistency
|
|
32
|
-
if (!this.rootDir.endsWith("/")) {
|
|
33
|
-
this.rootDir = this.rootDir + "/";
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
public getDebugName() {
|
|
38
|
-
return "privateFS/" + this.rootDir;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
private async getOrCreateDirectory(path: string): Promise<FileSystemDirectoryHandle> {
|
|
42
|
-
const root = await getRootDirectory();
|
|
43
|
-
const pathParts = (this.rootDir + path).split("/").filter(part => part.length > 0);
|
|
44
|
-
|
|
45
|
-
let currentDir = root;
|
|
46
|
-
for (const part of pathParts) {
|
|
47
|
-
currentDir = await currentDir.getDirectoryHandle(part, { create: true });
|
|
48
|
-
}
|
|
49
|
-
return currentDir;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
private async ensureDirsExist(path: string) {
|
|
53
|
-
const pathParts = path.split("/");
|
|
54
|
-
// Create directories up to the parent of the file (don't include the file itself)
|
|
55
|
-
if (pathParts.length > 1) {
|
|
56
|
-
const dirPath = pathParts.slice(0, -1).join("/");
|
|
57
|
-
await this.getOrCreateDirectory(dirPath);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
@measureFnc
|
|
62
|
-
public async set(fileName: string, data: Buffer): Promise<void> {
|
|
63
|
-
this.log(blue(`Setting file ${fileName} = ${data.length} bytes`));
|
|
64
|
-
|
|
65
|
-
await this.ensureDirsExist(fileName);
|
|
66
|
-
|
|
67
|
-
const pathParts = fileName.split("/");
|
|
68
|
-
const filename = pathParts[pathParts.length - 1];
|
|
69
|
-
const dirPath = pathParts.length > 1 ? pathParts.slice(0, -1).join("/") : "";
|
|
70
|
-
|
|
71
|
-
const directory = await this.getOrCreateDirectory(dirPath);
|
|
72
|
-
const fileHandle = await directory.getFileHandle(filename, { create: true });
|
|
73
|
-
const writable = await fileHandle.createWritable();
|
|
74
|
-
await writable.write(data);
|
|
75
|
-
await writable.close();
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
@measureFnc
|
|
79
|
-
public async append(fileName: string, data: Buffer): Promise<void> {
|
|
80
|
-
this.log(blue(`Appending to file ${fileName} += ${data.length} bytes`));
|
|
81
|
-
|
|
82
|
-
await this.ensureDirsExist(fileName);
|
|
83
|
-
|
|
84
|
-
const pathParts = fileName.split("/");
|
|
85
|
-
const filename = pathParts[pathParts.length - 1];
|
|
86
|
-
const dirPath = pathParts.length > 1 ? pathParts.slice(0, -1).join("/") : "";
|
|
87
|
-
|
|
88
|
-
const directory = await this.getOrCreateDirectory(dirPath);
|
|
89
|
-
const fileHandle = await directory.getFileHandle(filename, { create: true });
|
|
90
|
-
|
|
91
|
-
// Get current file to find the size for appending at the end
|
|
92
|
-
const file = await fileHandle.getFile();
|
|
93
|
-
const currentSize = file.size;
|
|
94
|
-
|
|
95
|
-
const writable = await fileHandle.createWritable({ keepExistingData: true });
|
|
96
|
-
await writable.write({ type: "write", position: currentSize, data });
|
|
97
|
-
await writable.close();
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
@measureFnc
|
|
101
|
-
public async del(fileName: string): Promise<void> {
|
|
102
|
-
this.log(blue(`Deleting file ${fileName}`));
|
|
103
|
-
|
|
104
|
-
const pathParts = fileName.split("/");
|
|
105
|
-
const filename = pathParts[pathParts.length - 1];
|
|
106
|
-
const dirPath = pathParts.length > 1 ? pathParts.slice(0, -1).join("/") : "";
|
|
107
|
-
|
|
108
|
-
try {
|
|
109
|
-
const directory = await this.getOrCreateDirectory(dirPath);
|
|
110
|
-
await directory.removeEntry(filename);
|
|
111
|
-
} catch (e: any) {
|
|
112
|
-
// File might not exist, which is fine for delete operations
|
|
113
|
-
if (e.name !== "NotFoundError") {
|
|
114
|
-
throw e;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
public async setLargeFile(config: { path: string; getNextData(): Promise<Buffer | undefined>; }): Promise<void> {
|
|
120
|
-
let { path } = config;
|
|
121
|
-
await this.ensureDirsExist(path);
|
|
122
|
-
|
|
123
|
-
const pathParts = path.split("/");
|
|
124
|
-
const filename = pathParts[pathParts.length - 1];
|
|
125
|
-
const dirPath = pathParts.length > 1 ? pathParts.slice(0, -1).join("/") : "";
|
|
126
|
-
|
|
127
|
-
const directory = await this.getOrCreateDirectory(dirPath);
|
|
128
|
-
const fileHandle = await directory.getFileHandle(filename, { create: true });
|
|
129
|
-
const writable = await fileHandle.createWritable();
|
|
130
|
-
|
|
131
|
-
try {
|
|
132
|
-
while (true) {
|
|
133
|
-
let data = await config.getNextData();
|
|
134
|
-
if (!data?.length) break;
|
|
135
|
-
await writable.write(data);
|
|
136
|
-
}
|
|
137
|
-
} finally {
|
|
138
|
-
await writable.close();
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
@measureFnc
|
|
143
|
-
public async get(fileName: string, config?: { range?: { start: number; end: number; }; retryCount?: number }): Promise<Buffer | undefined> {
|
|
144
|
-
this.log(blue(`Start read file ${fileName}`));
|
|
145
|
-
|
|
146
|
-
const pathParts = fileName.split("/");
|
|
147
|
-
const filename = pathParts[pathParts.length - 1];
|
|
148
|
-
const dirPath = pathParts.length > 1 ? pathParts.slice(0, -1).join("/") : "";
|
|
149
|
-
|
|
150
|
-
try {
|
|
151
|
-
const directory = await this.getOrCreateDirectory(dirPath);
|
|
152
|
-
const fileHandle = await directory.getFileHandle(filename, { create: false });
|
|
153
|
-
const file = await fileHandle.getFile();
|
|
154
|
-
|
|
155
|
-
if (config?.range) {
|
|
156
|
-
const start = config.range.start;
|
|
157
|
-
const end = config.range.end;
|
|
158
|
-
const slice = file.slice(start, end);
|
|
159
|
-
const arrayBuffer = await slice.arrayBuffer();
|
|
160
|
-
return Buffer.from(arrayBuffer);
|
|
161
|
-
} else {
|
|
162
|
-
const arrayBuffer = await file.arrayBuffer();
|
|
163
|
-
return Buffer.from(arrayBuffer);
|
|
164
|
-
}
|
|
165
|
-
} catch (e: any) {
|
|
166
|
-
if (e.name === "NotFoundError") {
|
|
167
|
-
return undefined;
|
|
168
|
-
}
|
|
169
|
-
throw e;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
@measureFnc
|
|
174
|
-
public async find(prefix: string, config?: { shallow?: boolean; type: "files" | "folders" }): Promise<string[]> {
|
|
175
|
-
this.log(blue(`findFileNames ${prefix}`));
|
|
176
|
-
|
|
177
|
-
let fileNames: string[] = [];
|
|
178
|
-
let folderNames: string[] = [];
|
|
179
|
-
|
|
180
|
-
async function readDir(directory: FileSystemDirectoryHandle, currentPath: string) {
|
|
181
|
-
for await (const [name, handle] of directory) {
|
|
182
|
-
const fullPath = currentPath + (currentPath ? "/" : "") + name;
|
|
183
|
-
|
|
184
|
-
if (handle.kind === "directory") {
|
|
185
|
-
folderNames.push(fullPath);
|
|
186
|
-
if (!config?.shallow) {
|
|
187
|
-
await readDir(handle, fullPath);
|
|
188
|
-
}
|
|
189
|
-
} else {
|
|
190
|
-
fileNames.push(fullPath);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
try {
|
|
196
|
-
let readDirTime = Date.now();
|
|
197
|
-
// Start from the root directory of our namespace
|
|
198
|
-
const rootDirectory = await this.getOrCreateDirectory("");
|
|
199
|
-
await readDir(rootDirectory, "");
|
|
200
|
-
console.log(`readDir took ${formatTime(Date.now() - readDirTime)}`);
|
|
201
|
-
|
|
202
|
-
let results = config?.type === "folders" ? folderNames : fileNames;
|
|
203
|
-
results = results.filter(name => name.startsWith(prefix));
|
|
204
|
-
|
|
205
|
-
if (config?.shallow) {
|
|
206
|
-
let targetDepth = prefix.split("/").length;
|
|
207
|
-
results = results.filter(name => name.split("/").length === targetDepth);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
return results;
|
|
211
|
-
} catch (e: any) {
|
|
212
|
-
if (e.name === "NotFoundError") {
|
|
213
|
-
return [];
|
|
214
|
-
}
|
|
215
|
-
throw e;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
@measureFnc
|
|
220
|
-
public async findInfo(prefix: string, config?: { shallow?: boolean; type: "files" | "folders" }): Promise<{ path: string; createTime: number; size: number; }[]> {
|
|
221
|
-
let files = await this.find(prefix, config);
|
|
222
|
-
return (await Promise.all(files.map(async file => {
|
|
223
|
-
try {
|
|
224
|
-
const info = await this.getInfo(file);
|
|
225
|
-
if (!info) return undefined;
|
|
226
|
-
return { path: file, createTime: info.writeTime, size: info.size };
|
|
227
|
-
} catch {
|
|
228
|
-
return undefined;
|
|
229
|
-
}
|
|
230
|
-
}))).filter(isDefined);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
@measureFnc
|
|
234
|
-
public async getInfo(pathInput: string): Promise<{
|
|
235
|
-
writeTime: number;
|
|
236
|
-
size: number;
|
|
237
|
-
} | undefined> {
|
|
238
|
-
const pathParts = pathInput.split("/");
|
|
239
|
-
const filename = pathParts[pathParts.length - 1];
|
|
240
|
-
const dirPath = pathParts.length > 1 ? pathParts.slice(0, -1).join("/") : "";
|
|
241
|
-
|
|
242
|
-
try {
|
|
243
|
-
const directory = await this.getOrCreateDirectory(dirPath);
|
|
244
|
-
const fileHandle = await directory.getFileHandle(filename, { create: false });
|
|
245
|
-
const file = await fileHandle.getFile();
|
|
246
|
-
|
|
247
|
-
return {
|
|
248
|
-
writeTime: file.lastModified,
|
|
249
|
-
size: file.size,
|
|
250
|
-
};
|
|
251
|
-
} catch (e: any) {
|
|
252
|
-
if (e.name === "NotFoundError") {
|
|
253
|
-
return undefined;
|
|
254
|
-
}
|
|
255
|
-
throw e;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
public async assertPathValid(path: string) {
|
|
260
|
-
// Private file system has fewer restrictions than regular file system
|
|
261
|
-
// Most characters are allowed, so we just check for obviously problematic patterns
|
|
262
|
-
if (path.includes("..")) {
|
|
263
|
-
throw new Error(`Invalid path contains '..': ${path}`);
|
|
264
|
-
}
|
|
265
|
-
if (path.startsWith("/")) {
|
|
266
|
-
throw new Error(`Invalid path starts with '/': ${path}`);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
@measureFnc
|
|
271
|
-
public async move(config: {
|
|
272
|
-
path: string;
|
|
273
|
-
target: Archives;
|
|
274
|
-
targetPath: string;
|
|
275
|
-
}) {
|
|
276
|
-
let { path, target, targetPath } = config;
|
|
277
|
-
|
|
278
|
-
// Unwrap any nested archives
|
|
279
|
-
while (true) {
|
|
280
|
-
let targetUnwrapped = target.getBaseArchives?.();
|
|
281
|
-
if (!targetUnwrapped) break;
|
|
282
|
-
target = targetUnwrapped.archives;
|
|
283
|
-
targetPath = targetUnwrapped.parentPath + targetPath;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// For moves, we can optimize if both source and target are private file system
|
|
287
|
-
if (target instanceof ArchivesPrivateFileSystem) {
|
|
288
|
-
// Private File System API doesn't have a native move operation
|
|
289
|
-
// So we fall back to copy + delete
|
|
290
|
-
await this.copy({ path, target, targetPath });
|
|
291
|
-
await this.del(path);
|
|
292
|
-
} else {
|
|
293
|
-
// Moving to a different archive type - copy then delete
|
|
294
|
-
let data = await this.get(path);
|
|
295
|
-
if (!data) throw new Error(`File not found to move: ${path}`);
|
|
296
|
-
await target.set(targetPath, data);
|
|
297
|
-
await this.del(path);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
@measureFnc
|
|
302
|
-
public async copy(config: {
|
|
303
|
-
path: string;
|
|
304
|
-
target: Archives;
|
|
305
|
-
targetPath: string;
|
|
306
|
-
}) {
|
|
307
|
-
let { path, target, targetPath } = config;
|
|
308
|
-
|
|
309
|
-
// Unwrap any nested archives
|
|
310
|
-
while (true) {
|
|
311
|
-
let targetUnwrapped = target.getBaseArchives?.();
|
|
312
|
-
if (!targetUnwrapped) break;
|
|
313
|
-
target = targetUnwrapped.archives;
|
|
314
|
-
targetPath = targetUnwrapped.parentPath + targetPath;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// Get the data and set it in the target
|
|
318
|
-
let data = await this.get(path);
|
|
319
|
-
if (!data) throw new Error(`File not found to copy: ${path}`);
|
|
320
|
-
await target.set(targetPath, data);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
export const getArchivesPrivateFileSystem = cache((rootDir: string): Archives => {
|
|
325
|
-
return new ArchivesPrivateFileSystem(rootDir);
|
|
326
|
-
});
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { formatNumber } from "socket-function/src/formatting/format";
|
|
2
|
-
import { getDomain } from "../config";
|
|
3
|
-
import { getArchivesBackblaze } from "./archivesBackBlaze";
|
|
4
|
-
import { getArchivesLocal } from "./archivesDisk";
|
|
5
|
-
|
|
6
|
-
async function main() {
|
|
7
|
-
const domain = getDomain();
|
|
8
|
-
let local = getArchivesLocal(domain);
|
|
9
|
-
let remote = getArchivesBackblaze(domain);
|
|
10
|
-
|
|
11
|
-
let files = await local.find("");
|
|
12
|
-
let allRemoteFiles = new Set(await remote.find(""));
|
|
13
|
-
for (let file of files) {
|
|
14
|
-
if (allRemoteFiles.has(file)) continue;
|
|
15
|
-
let buffer = await local.get(file);
|
|
16
|
-
if (!buffer) continue;
|
|
17
|
-
console.log(`Copying ${file} size ${formatNumber(buffer.length)}B`);
|
|
18
|
-
await remote.set(file, buffer);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
main().catch(console.error).finally(() => process.exit(0));
|
|
22
|
-
|
|
23
|
-
// archives = getArchivesLocal(domain);
|
|
24
|
-
// archives = getArchivesBackblaze(domain);
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { Archives } from "../-a-archives/archives";
|
|
2
|
-
import { ArchivesBackblaze } from "../-a-archives/archivesBackBlaze";
|
|
3
|
-
import { cloudflareGETCall, cloudflarePOSTCall } from "sliftutils/misc/https/cloudflareHelpers";
|
|
4
|
-
import { setRecord, getZoneId } from "./dnsAuthority";
|
|
5
|
-
import debugbreak from "debugbreak";
|
|
6
|
-
|
|
7
|
-
// servicestest.querysub.com/servicesIndex.json
|
|
8
|
-
|
|
9
|
-
export async function hostArchives(config: {
|
|
10
|
-
archives: Archives;
|
|
11
|
-
// Ex, "archives"
|
|
12
|
-
subdomain: string;
|
|
13
|
-
// Ex, "example.com"
|
|
14
|
-
domain: string;
|
|
15
|
-
}): Promise<{
|
|
16
|
-
getURL: (path: string) => Promise<string>;
|
|
17
|
-
}> {
|
|
18
|
-
let { archives, subdomain, domain } = config;
|
|
19
|
-
|
|
20
|
-
let base = archives.getBaseArchives?.();
|
|
21
|
-
let parentPath = base?.parentPath ?? "";
|
|
22
|
-
archives = base?.archives ?? archives;
|
|
23
|
-
if (parentPath && !parentPath.endsWith("/")) {
|
|
24
|
-
parentPath += "/";
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (!(archives instanceof ArchivesBackblaze)) {
|
|
28
|
-
throw new Error(`hostArchives not implemented for ${archives.constructor.name}, only "ArchivesBackblaze" is implemented at the moment`);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
let archiveT = archives as ArchivesBackblaze;
|
|
32
|
-
let baseURL = await archiveT.getURL!("");
|
|
33
|
-
// Remove the trailing slash if it exists
|
|
34
|
-
if (baseURL.endsWith("/")) {
|
|
35
|
-
baseURL = baseURL.slice(0, -1);
|
|
36
|
-
}
|
|
37
|
-
let backblazeHost = new URL(baseURL).hostname;
|
|
38
|
-
|
|
39
|
-
// HACK: Assuming cloudflare is still our DNS provider. If it isn't... this won't work.
|
|
40
|
-
await setRecord("CNAME", subdomain + "." + domain, backblazeHost, "proxied");
|
|
41
|
-
|
|
42
|
-
async function getURL(path: string) {
|
|
43
|
-
if (path.startsWith("/")) {
|
|
44
|
-
path = path.slice(1);
|
|
45
|
-
}
|
|
46
|
-
let targetPath = await archiveT.getURL!(parentPath + path);
|
|
47
|
-
let url = new URL(targetPath);
|
|
48
|
-
url.hostname = subdomain + "." + domain;
|
|
49
|
-
return url.toString();
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return { getURL };
|
|
53
|
-
}
|