sliftutils 1.2.38 → 1.3.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/.claude/settings.local.json +5 -1
- package/index.d.ts +268 -0
- package/misc/getSecret.d.ts +1 -0
- package/misc/getSecret.ts +7 -0
- package/misc/https/httpsCerts.d.ts +17 -0
- package/misc/https/httpsCerts.ts +4 -4
- package/package.json +2 -2
- package/render-utils/mobxTyped.d.ts +1 -0
- package/render-utils/mobxTyped.ts +4 -0
- package/storage/BulkDatabase2/BulkDatabase2.d.ts +8 -0
- package/storage/BulkDatabase2/BulkDatabase2.ts +41 -0
- package/storage/BulkDatabase2/BulkDatabaseBase.d.ts +84 -0
- package/storage/BulkDatabase2/BulkDatabaseBase.ts +797 -0
- package/storage/BulkDatabase2/BulkDatabaseFormat.d.ts +24 -0
- package/storage/BulkDatabase2/BulkDatabaseFormat.ts +285 -0
- package/storage/BulkDatabase2/blockCache.d.ts +17 -0
- package/storage/BulkDatabase2/blockCache.ts +173 -0
- package/storage/BulkDatabase2/streamLog.d.ts +25 -0
- package/storage/BulkDatabase2/streamLog.ts +116 -0
- package/storage/BulkDatabase2/syncClient.d.ts +9 -0
- package/storage/BulkDatabase2/syncClient.ts +81 -0
- package/storage/DiskCollection.d.ts +2 -0
- package/storage/DiskCollection.ts +17 -1
- package/storage/FileFolderAPI.d.ts +56 -0
- package/storage/FileFolderAPI.tsx +93 -19
- package/yarn.lock +4 -4
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { isNode } from "typesafecss";
|
|
2
|
+
|
|
3
|
+
// Browser-side cross-tab write sync for BulkDatabase2, over BroadcastChannel (one channel per
|
|
4
|
+
// collection, same-origin tabs). It does NOT persist anything — each tab writes to disk itself; this
|
|
5
|
+
// just relays live writes to other open tabs and, when a tab starts up, asks peers for writes they've
|
|
6
|
+
// made recently that may not be on disk yet. No-op in Node / where BroadcastChannel is unavailable, so
|
|
7
|
+
// BulkDatabase2 can call these unconditionally. This is an optional feature, so there is no fallback.
|
|
8
|
+
|
|
9
|
+
export type RemoteWrite = { key: string; time: number; deleted?: boolean; value?: unknown };
|
|
10
|
+
|
|
11
|
+
// Writes older than this are assumed already flushed to disk (a freshly-opened tab gets those by
|
|
12
|
+
// reading disk), so a tab only replays writes newer than this when a peer says hello.
|
|
13
|
+
const RECENT_WINDOW_MS = 60_000;
|
|
14
|
+
|
|
15
|
+
type Channel = {
|
|
16
|
+
bc: BroadcastChannel;
|
|
17
|
+
subscribers: ((write: RemoteWrite) => void)[];
|
|
18
|
+
// This tab's own recent writes, kept so it can answer another tab's "hello".
|
|
19
|
+
recent: RemoteWrite[];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const channels = new Map<string, Channel>();
|
|
23
|
+
|
|
24
|
+
export function isSyncSupported(): boolean {
|
|
25
|
+
return !isNode() && typeof BroadcastChannel !== "undefined";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function pruneRecent(channel: Channel) {
|
|
29
|
+
const cutoff = Date.now() - RECENT_WINDOW_MS;
|
|
30
|
+
channel.recent = channel.recent.filter(w => w.time >= cutoff);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function deliver(channel: Channel, write: RemoteWrite) {
|
|
34
|
+
for (const sub of channel.subscribers) sub(write);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function ensure(collection: string): Channel | undefined {
|
|
38
|
+
if (!isSyncSupported()) return undefined;
|
|
39
|
+
let channel = channels.get(collection);
|
|
40
|
+
if (channel) return channel;
|
|
41
|
+
// BroadcastChannel never delivers a message back to the instance that sent it, so a tab never
|
|
42
|
+
// hears its own writes — only the other open tabs do.
|
|
43
|
+
const bc = new BroadcastChannel(`bulkDatabase2:${collection}`);
|
|
44
|
+
const created: Channel = { bc, subscribers: [], recent: [] };
|
|
45
|
+
bc.onmessage = (event: MessageEvent) => {
|
|
46
|
+
const msg = event.data as { type: string; write?: RemoteWrite; writes?: RemoteWrite[] };
|
|
47
|
+
if (!msg) return;
|
|
48
|
+
if (msg.type === "write" && msg.write) {
|
|
49
|
+
deliver(created, msg.write);
|
|
50
|
+
} else if (msg.type === "recent" && msg.writes) {
|
|
51
|
+
for (const write of msg.writes) deliver(created, write);
|
|
52
|
+
} else if (msg.type === "hello") {
|
|
53
|
+
// Another tab just started; replay our recent writes so it doesn't miss any. The reply goes
|
|
54
|
+
// to every tab, but peers that already have a write ignore it (its timestamp isn't newer
|
|
55
|
+
// than what they hold), so the redundant broadcast is harmless.
|
|
56
|
+
pruneRecent(created);
|
|
57
|
+
if (created.recent.length) created.bc.postMessage({ type: "recent", writes: created.recent });
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
channels.set(collection, created);
|
|
61
|
+
return created;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Subscribe to remote writes for a collection. Recent writes from already-open tabs arrive through the
|
|
65
|
+
// same onWrite callback (as the reply to our hello), so the returned array is always empty — it's kept
|
|
66
|
+
// only for API compatibility with callers that await it.
|
|
67
|
+
export function connect(collection: string, onWrite: (write: RemoteWrite) => void): Promise<RemoteWrite[]> {
|
|
68
|
+
const channel = ensure(collection);
|
|
69
|
+
if (!channel) return Promise.resolve([]);
|
|
70
|
+
channel.subscribers.push(onWrite);
|
|
71
|
+
channel.bc.postMessage({ type: "hello" });
|
|
72
|
+
return Promise.resolve([]);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function broadcast(collection: string, write: RemoteWrite): void {
|
|
76
|
+
const channel = ensure(collection);
|
|
77
|
+
if (!channel) return;
|
|
78
|
+
channel.recent.push(write);
|
|
79
|
+
pruneRecent(channel);
|
|
80
|
+
channel.bc.postMessage({ type: "write", write });
|
|
81
|
+
}
|
|
@@ -6,6 +6,8 @@ import { TransactionStorage } from "./TransactionStorage";
|
|
|
6
6
|
export declare class DiskCollection<T> implements IStorageSync<T> {
|
|
7
7
|
private collectionName;
|
|
8
8
|
private config?;
|
|
9
|
+
static getForceNoPrompt(): boolean;
|
|
10
|
+
static setForceNoPrompt(forceNoPrompt: boolean): void;
|
|
9
11
|
constructor(collectionName: string, config?: {
|
|
10
12
|
writeDelay?: number | undefined;
|
|
11
13
|
cbor?: boolean | undefined;
|
|
@@ -11,7 +11,23 @@ import { PrivateFileSystemStorage } from "./PrivateFileSystemStorage";
|
|
|
11
11
|
import { isInChromeExtension, isInChromeExtensionBackground } from "../misc/environment";
|
|
12
12
|
import { CBORStorage } from "./CBORStorage";
|
|
13
13
|
|
|
14
|
+
const FORCE_NO_PROMPT_KEY = "DiskCollection_forceNoPrompt";
|
|
15
|
+
|
|
14
16
|
export class DiskCollection<T> implements IStorageSync<T> {
|
|
17
|
+
// Globally forces every DiskCollection into noPrompt mode (browser only), overriding per-collection config.
|
|
18
|
+
public static getForceNoPrompt(): boolean {
|
|
19
|
+
if (isNode()) return false;
|
|
20
|
+
return !!localStorage.getItem(FORCE_NO_PROMPT_KEY);
|
|
21
|
+
}
|
|
22
|
+
public static setForceNoPrompt(forceNoPrompt: boolean) {
|
|
23
|
+
if (isNode()) return;
|
|
24
|
+
if (forceNoPrompt) {
|
|
25
|
+
localStorage.setItem(FORCE_NO_PROMPT_KEY, "1");
|
|
26
|
+
} else {
|
|
27
|
+
localStorage.removeItem(FORCE_NO_PROMPT_KEY);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
15
31
|
constructor(
|
|
16
32
|
private collectionName: string,
|
|
17
33
|
private config?: {
|
|
@@ -29,7 +45,7 @@ export class DiskCollection<T> implements IStorageSync<T> {
|
|
|
29
45
|
// If a Chrome extension, just return null.
|
|
30
46
|
if (isInChromeExtensionBackground()) return null as any;
|
|
31
47
|
let curCollection: IStorageRaw;
|
|
32
|
-
if (this.config?.noPrompt
|
|
48
|
+
if (!isNode() && (this.config?.noPrompt || DiskCollection.getForceNoPrompt())) {
|
|
33
49
|
curCollection = await new PrivateFileSystemStorage(`collections/${this.collectionName}`);
|
|
34
50
|
} else {
|
|
35
51
|
let fileStorage = await getFileStorage();
|
|
@@ -70,6 +70,52 @@ type DirectoryWrapper = {
|
|
|
70
70
|
]>;
|
|
71
71
|
};
|
|
72
72
|
export declare function setFileAPIKey(key: string): void;
|
|
73
|
+
export declare class NodeJSFileHandleWrapper implements FileWrapper {
|
|
74
|
+
private filePath;
|
|
75
|
+
constructor(filePath: string);
|
|
76
|
+
getFile(): Promise<{
|
|
77
|
+
size: number;
|
|
78
|
+
lastModified: number;
|
|
79
|
+
arrayBuffer: () => Promise<ArrayBuffer>;
|
|
80
|
+
slice: (start: number, end: number) => {
|
|
81
|
+
arrayBuffer: () => Promise<ArrayBuffer>;
|
|
82
|
+
};
|
|
83
|
+
}>;
|
|
84
|
+
createWritable(config?: {
|
|
85
|
+
keepExistingData?: boolean;
|
|
86
|
+
}): Promise<{
|
|
87
|
+
seek: (offset: number) => Promise<void>;
|
|
88
|
+
write: (value: Buffer) => Promise<void>;
|
|
89
|
+
close: () => Promise<void>;
|
|
90
|
+
}>;
|
|
91
|
+
}
|
|
92
|
+
export declare class NodeJSDirectoryHandleWrapper implements DirectoryWrapper {
|
|
93
|
+
private rootPath;
|
|
94
|
+
constructor(rootPath: string);
|
|
95
|
+
removeEntry(key: string, options?: {
|
|
96
|
+
recursive?: boolean;
|
|
97
|
+
}): Promise<void>;
|
|
98
|
+
getFileHandle(key: string, options?: {
|
|
99
|
+
create?: boolean;
|
|
100
|
+
}): Promise<FileWrapper>;
|
|
101
|
+
getDirectoryHandle(key: string, options?: {
|
|
102
|
+
create?: boolean;
|
|
103
|
+
}): Promise<DirectoryWrapper>;
|
|
104
|
+
[Symbol.asyncIterator](): AsyncIterableIterator<[
|
|
105
|
+
string,
|
|
106
|
+
{
|
|
107
|
+
kind: "file";
|
|
108
|
+
name: string;
|
|
109
|
+
getFile(): Promise<FileWrapper>;
|
|
110
|
+
} | {
|
|
111
|
+
kind: "directory";
|
|
112
|
+
name: string;
|
|
113
|
+
getDirectoryHandle(key: string, options?: {
|
|
114
|
+
create?: boolean;
|
|
115
|
+
}): Promise<DirectoryWrapper>;
|
|
116
|
+
}
|
|
117
|
+
]>;
|
|
118
|
+
}
|
|
73
119
|
export declare const getDirectoryHandle: {
|
|
74
120
|
(): Promise<DirectoryWrapper>;
|
|
75
121
|
reset(): void;
|
|
@@ -83,6 +129,14 @@ export declare const getFileStorageNested: {
|
|
|
83
129
|
getAllKeys(): string[];
|
|
84
130
|
get(key: string): Promise<FileStorage> | undefined;
|
|
85
131
|
};
|
|
132
|
+
export declare const getFileStorageNested2: {
|
|
133
|
+
(key: string): Promise<FileStorage>;
|
|
134
|
+
clear(key: string): void;
|
|
135
|
+
clearAll(): void;
|
|
136
|
+
forceSet(key: string, value: Promise<FileStorage>): void;
|
|
137
|
+
getAllKeys(): string[];
|
|
138
|
+
get(key: string): Promise<FileStorage> | undefined;
|
|
139
|
+
};
|
|
86
140
|
export declare const getFileStorage: {
|
|
87
141
|
(): Promise<FileStorage>;
|
|
88
142
|
reset(): void;
|
|
@@ -98,4 +152,6 @@ export type NestedFileStorage = {
|
|
|
98
152
|
export type FileStorage = IStorageRaw & {
|
|
99
153
|
folder: NestedFileStorage;
|
|
100
154
|
};
|
|
155
|
+
export declare function wrapHandle(handle: DirectoryWrapper): FileStorage;
|
|
156
|
+
export declare function tryToLoadPointer(pointer: string): Promise<DirectoryWrapper | undefined>;
|
|
101
157
|
export {};
|
|
@@ -96,7 +96,7 @@ class DirectoryPrompter extends preact.Component {
|
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
class NodeJSFileHandleWrapper implements FileWrapper {
|
|
99
|
+
export class NodeJSFileHandleWrapper implements FileWrapper {
|
|
100
100
|
constructor(private filePath: string) {
|
|
101
101
|
}
|
|
102
102
|
|
|
@@ -159,7 +159,7 @@ class NodeJSFileHandleWrapper implements FileWrapper {
|
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
class NodeJSDirectoryHandleWrapper implements DirectoryWrapper {
|
|
162
|
+
export class NodeJSDirectoryHandleWrapper implements DirectoryWrapper {
|
|
163
163
|
constructor(private rootPath: string) {
|
|
164
164
|
}
|
|
165
165
|
|
|
@@ -241,6 +241,7 @@ class NodeJSDirectoryHandleWrapper implements DirectoryWrapper {
|
|
|
241
241
|
|
|
242
242
|
|
|
243
243
|
// NOTE: Blocks until the user provides a directory
|
|
244
|
+
// NOTE: If you want to override the location, you can explicitly call getDirectoryHandle.set with your own directory wrapper. Which if your server side can be the Node.js directory handle wrapper, or if your client side it can just be a pointer from tryToLoadPointer/fileSystemPointer.ts.
|
|
244
245
|
export const getDirectoryHandle = lazy(async function getDirectoryHandle(): Promise<DirectoryWrapper> {
|
|
245
246
|
if (isNode()) {
|
|
246
247
|
return new NodeJSDirectoryHandleWrapper(path.resolve("./data/"));
|
|
@@ -378,6 +379,33 @@ export const getFileStorageNested = cache(async function getFileStorage(path: st
|
|
|
378
379
|
}
|
|
379
380
|
return wrapHandle(base);
|
|
380
381
|
});
|
|
382
|
+
// Supports if the user selects the folder that contains the data folder or the data folder directly. If pathStr is an absolute path (and we're in nodejs) we use it directly.
|
|
383
|
+
export const getFileStorageNested2 = cache(async function getFileStorage(pathStr: string): Promise<FileStorage> {
|
|
384
|
+
let base: DirectoryWrapper;
|
|
385
|
+
pathStr = pathStr.replaceAll("\\", "/");
|
|
386
|
+
if (isNode()) {
|
|
387
|
+
if (path.isAbsolute(pathStr)) {
|
|
388
|
+
return wrapHandle(new NodeJSDirectoryHandleWrapper(pathStr));
|
|
389
|
+
}
|
|
390
|
+
base = new NodeJSDirectoryHandleWrapper(path.resolve("./data/"));
|
|
391
|
+
} else {
|
|
392
|
+
base = await getDirectoryHandle();
|
|
393
|
+
let dirs: string[] = [];
|
|
394
|
+
for await (const [name, entry] of base) {
|
|
395
|
+
if (entry.kind === "directory") {
|
|
396
|
+
dirs.push(name);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
if (dirs.includes(".git") || dirs.includes("data")) {
|
|
400
|
+
base = await base.getDirectoryHandle("data", { create: true });
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
for (let part of pathStr.split("/")) {
|
|
404
|
+
if (!part) continue;
|
|
405
|
+
base = await base.getDirectoryHandle(part, { create: true });
|
|
406
|
+
}
|
|
407
|
+
return wrapHandle(base);
|
|
408
|
+
});
|
|
381
409
|
export const getFileStorage = lazy(async function getFileStorage(): Promise<FileStorage> {
|
|
382
410
|
if (USE_INDEXED_DB) {
|
|
383
411
|
return await getFileStorageIndexDB();
|
|
@@ -434,40 +462,78 @@ async function fixedGetFileHandle(config: {
|
|
|
434
462
|
return await config.handle.getFileHandle(config.key, { create: true });
|
|
435
463
|
}
|
|
436
464
|
|
|
465
|
+
// A file that genuinely does not exist. We must NOT retry these, otherwise reading many missing
|
|
466
|
+
// files (a common pattern) gets catastrophically slow.
|
|
467
|
+
function isMissingError(error: unknown): boolean {
|
|
468
|
+
let name = (error as { name?: string })?.name;
|
|
469
|
+
let code = (error as { code?: string })?.code;
|
|
470
|
+
return name === "NotFoundError" || code === "ENOENT";
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const MAX_READ_RETRIES = 6;
|
|
474
|
+
|
|
475
|
+
// The browser File System Access API can transiently fail reads under heavy load (it appears to
|
|
476
|
+
// throttle when a page issues many reads at once). Those failures surface as errors OTHER than
|
|
477
|
+
// "not found", so we retry them with backoff. A real missing file (NotFoundError / ENOENT) returns
|
|
478
|
+
// undefined immediately with no retry.
|
|
479
|
+
async function readWithRetry<T>(label: string, key: string, read: () => Promise<T>): Promise<T | undefined> {
|
|
480
|
+
let backoff = 25;
|
|
481
|
+
for (let attempt = 0; ; attempt++) {
|
|
482
|
+
try {
|
|
483
|
+
return await read();
|
|
484
|
+
} catch (error) {
|
|
485
|
+
if (isMissingError(error)) return undefined;
|
|
486
|
+
let name = (error as { name?: string })?.name || "Error";
|
|
487
|
+
let message = (error as { message?: string })?.message || String(error);
|
|
488
|
+
if (attempt >= MAX_READ_RETRIES) {
|
|
489
|
+
console.warn(`${label} gave up on ${JSON.stringify(key)} after ${attempt} retries (${name}): ${message.slice(0, 200)}`);
|
|
490
|
+
return undefined;
|
|
491
|
+
}
|
|
492
|
+
console.warn(`${label} retrying ${JSON.stringify(key)} (attempt ${attempt + 1}, ${name}): ${message.slice(0, 200)}`);
|
|
493
|
+
await new Promise(resolve => setTimeout(resolve, backoff));
|
|
494
|
+
backoff = Math.min(backoff * 2, 1000);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
437
499
|
function wrapHandleFiles(handle: DirectoryWrapper): IStorageRaw {
|
|
438
500
|
return {
|
|
439
501
|
async getInfo(key: string) {
|
|
440
|
-
|
|
502
|
+
return readWithRetry("getInfo", key, async () => {
|
|
441
503
|
const file = await handle.getFileHandle(key);
|
|
442
504
|
const fileContent = await file.getFile();
|
|
443
505
|
return {
|
|
444
506
|
size: fileContent.size,
|
|
445
507
|
lastModified: fileContent.lastModified,
|
|
446
508
|
};
|
|
447
|
-
}
|
|
448
|
-
return undefined;
|
|
449
|
-
}
|
|
509
|
+
});
|
|
450
510
|
},
|
|
451
511
|
async get(key: string): Promise<Buffer | undefined> {
|
|
452
|
-
|
|
512
|
+
return readWithRetry("get", key, async () => {
|
|
453
513
|
const file = await handle.getFileHandle(key);
|
|
454
514
|
const fileContent = await file.getFile();
|
|
455
515
|
const arrayBuffer = await fileContent.arrayBuffer();
|
|
516
|
+
// Under load the FS Access API can resolve with a truncated buffer and no error.
|
|
517
|
+
// Treat a short read as transient so readWithRetry retries it.
|
|
518
|
+
if (arrayBuffer.byteLength !== fileContent.size) {
|
|
519
|
+
throw Object.assign(new Error(`Short read: got ${arrayBuffer.byteLength} of ${fileContent.size} bytes`), { name: "ShortReadError" });
|
|
520
|
+
}
|
|
456
521
|
return Buffer.from(arrayBuffer);
|
|
457
|
-
}
|
|
458
|
-
return undefined;
|
|
459
|
-
}
|
|
522
|
+
});
|
|
460
523
|
},
|
|
461
524
|
|
|
462
525
|
async getRange(key: string, config: { start: number; end: number }): Promise<Buffer | undefined> {
|
|
463
|
-
|
|
526
|
+
return readWithRetry("getRange", key, async () => {
|
|
464
527
|
const file = await handle.getFileHandle(key);
|
|
465
528
|
const fileContent = await file.getFile();
|
|
529
|
+
const clampedStart = Math.min(Math.max(config.start, 0), fileContent.size);
|
|
530
|
+
const clampedEnd = Math.min(Math.max(config.end, clampedStart), fileContent.size);
|
|
466
531
|
const arrayBuffer = await fileContent.slice(config.start, config.end).arrayBuffer();
|
|
532
|
+
if (arrayBuffer.byteLength !== clampedEnd - clampedStart) {
|
|
533
|
+
throw Object.assign(new Error(`Short range read: got ${arrayBuffer.byteLength} of ${clampedEnd - clampedStart} bytes`), { name: "ShortReadError" });
|
|
534
|
+
}
|
|
467
535
|
return Buffer.from(arrayBuffer);
|
|
468
|
-
}
|
|
469
|
-
return undefined;
|
|
470
|
-
}
|
|
536
|
+
});
|
|
471
537
|
},
|
|
472
538
|
|
|
473
539
|
async append(key: string, value: Buffer): Promise<void> {
|
|
@@ -496,10 +562,18 @@ function wrapHandleFiles(handle: DirectoryWrapper): IStorageRaw {
|
|
|
496
562
|
|
|
497
563
|
async getKeys(includeFolders: boolean = false): Promise<string[]> {
|
|
498
564
|
const keys: string[] = [];
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
565
|
+
try {
|
|
566
|
+
for await (const [name, entry] of handle) {
|
|
567
|
+
if (entry.kind === "file" || includeFolders) {
|
|
568
|
+
keys.push(entry.name);
|
|
569
|
+
}
|
|
502
570
|
}
|
|
571
|
+
} catch (error) {
|
|
572
|
+
let name = (error as { name?: string })?.name || "Error";
|
|
573
|
+
let message = (error as { message?: string })?.message || String(error);
|
|
574
|
+
// A failure mid-iteration would silently truncate the listing, so surface it loudly.
|
|
575
|
+
console.error(`getKeys directory iteration failed after ${keys.length} entries (${name}): ${message.slice(0, 300)}`);
|
|
576
|
+
throw error;
|
|
503
577
|
}
|
|
504
578
|
return keys;
|
|
505
579
|
},
|
|
@@ -544,14 +618,14 @@ function wrapHandleNested(handle: DirectoryWrapper): NestedFileStorage {
|
|
|
544
618
|
};
|
|
545
619
|
}
|
|
546
620
|
|
|
547
|
-
function wrapHandle(handle: DirectoryWrapper): FileStorage {
|
|
621
|
+
export function wrapHandle(handle: DirectoryWrapper): FileStorage {
|
|
548
622
|
return {
|
|
549
623
|
...wrapHandleFiles(handle),
|
|
550
624
|
folder: wrapHandleNested(handle),
|
|
551
625
|
};
|
|
552
626
|
}
|
|
553
627
|
|
|
554
|
-
async function tryToLoadPointer(pointer: string) {
|
|
628
|
+
export async function tryToLoadPointer(pointer: string) {
|
|
555
629
|
let result = await getFileSystemPointer({ pointer });
|
|
556
630
|
if (!result) return;
|
|
557
631
|
let handle = await result?.onUserActivation();
|
package/yarn.lock
CHANGED
|
@@ -1940,10 +1940,10 @@ slash@^3.0.0:
|
|
|
1940
1940
|
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
|
|
1941
1941
|
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
|
|
1942
1942
|
|
|
1943
|
-
socket-function@^1.1.
|
|
1944
|
-
version "1.1.
|
|
1945
|
-
resolved "https://registry.yarnpkg.com/socket-function/-/socket-function-1.1.
|
|
1946
|
-
integrity sha512
|
|
1943
|
+
socket-function@^1.1.42:
|
|
1944
|
+
version "1.1.42"
|
|
1945
|
+
resolved "https://registry.yarnpkg.com/socket-function/-/socket-function-1.1.42.tgz#79694906fe37c77da720f0fcc46a812021349062"
|
|
1946
|
+
integrity sha512-+nhKi73LNv8m99w/sShaQbxQbUA1qgO7QvP0PX7MdV9SvhAbnOY9O4siaqTfqtJy5cMuX8Xnbfz/pptvvPDTaA==
|
|
1947
1947
|
dependencies:
|
|
1948
1948
|
"@types/pako" "^2.0.3"
|
|
1949
1949
|
"@types/ws" "^8.5.3"
|