sliftutils 1.2.19 → 1.2.21
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
CHANGED
|
@@ -859,6 +859,21 @@ declare module "sliftutils/storage/DiskCollection" {
|
|
|
859
859
|
} | undefined>;
|
|
860
860
|
reset(): Promise<void>;
|
|
861
861
|
}
|
|
862
|
+
export declare class DiskCollectionRawSynced {
|
|
863
|
+
private collectionName;
|
|
864
|
+
constructor(collectionName: string);
|
|
865
|
+
initStorage(): Promise<IStorage<Buffer>>;
|
|
866
|
+
private synced;
|
|
867
|
+
get(key: string): Buffer | undefined;
|
|
868
|
+
getPromise(key: string): Promise<Buffer | undefined>;
|
|
869
|
+
set(key: string, value: Buffer): void;
|
|
870
|
+
getKeys(): Promise<string[]>;
|
|
871
|
+
getInfo(key: string): Promise<{
|
|
872
|
+
size: number;
|
|
873
|
+
lastModified: number;
|
|
874
|
+
} | undefined>;
|
|
875
|
+
reset(): Promise<void>;
|
|
876
|
+
}
|
|
862
877
|
export declare class DiskCollectionRawBrowser {
|
|
863
878
|
private collectionName;
|
|
864
879
|
constructor(collectionName: string);
|
|
@@ -1336,7 +1351,7 @@ declare module "sliftutils/storage/fileSystemPointer" {
|
|
|
1336
1351
|
export declare function getFileSystemPointer(config: {
|
|
1337
1352
|
pointer: FileSystemPointer;
|
|
1338
1353
|
}): Promise<{
|
|
1339
|
-
onUserActivation(modeOverride?: "read" | "readwrite"): Promise<FileSystemFileHandle | FileSystemDirectoryHandle
|
|
1354
|
+
onUserActivation(modeOverride?: "read" | "readwrite"): Promise<FileSystemFileHandle | FileSystemDirectoryHandle>;
|
|
1340
1355
|
} | undefined>;
|
|
1341
1356
|
|
|
1342
1357
|
}
|
package/package.json
CHANGED
|
@@ -67,6 +67,21 @@ export declare class DiskCollectionRaw implements IStorage<Buffer> {
|
|
|
67
67
|
} | undefined>;
|
|
68
68
|
reset(): Promise<void>;
|
|
69
69
|
}
|
|
70
|
+
export declare class DiskCollectionRawSynced {
|
|
71
|
+
private collectionName;
|
|
72
|
+
constructor(collectionName: string);
|
|
73
|
+
initStorage(): Promise<IStorage<Buffer>>;
|
|
74
|
+
private synced;
|
|
75
|
+
get(key: string): Buffer | undefined;
|
|
76
|
+
getPromise(key: string): Promise<Buffer | undefined>;
|
|
77
|
+
set(key: string, value: Buffer): void;
|
|
78
|
+
getKeys(): Promise<string[]>;
|
|
79
|
+
getInfo(key: string): Promise<{
|
|
80
|
+
size: number;
|
|
81
|
+
lastModified: number;
|
|
82
|
+
} | undefined>;
|
|
83
|
+
reset(): Promise<void>;
|
|
84
|
+
}
|
|
70
85
|
export declare class DiskCollectionRawBrowser {
|
|
71
86
|
private collectionName;
|
|
72
87
|
constructor(collectionName: string);
|
|
@@ -174,6 +174,42 @@ export class DiskCollectionRaw implements IStorage<Buffer> {
|
|
|
174
174
|
}
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
+
export class DiskCollectionRawSynced {
|
|
178
|
+
constructor(private collectionName: string) { }
|
|
179
|
+
async initStorage(): Promise<IStorage<Buffer>> {
|
|
180
|
+
let fileStorage = await getFileStorage();
|
|
181
|
+
let collections = await fileStorage.folder.getStorage("collections");
|
|
182
|
+
let baseStorage = await collections.folder.getStorage(this.collectionName);
|
|
183
|
+
return baseStorage;
|
|
184
|
+
}
|
|
185
|
+
private synced = new StorageSync(
|
|
186
|
+
new PendingStorage(`Collection (${this.collectionName})`,
|
|
187
|
+
new DelayedStorage(this.initStorage())
|
|
188
|
+
)
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
public get(key: string): Buffer | undefined {
|
|
192
|
+
return this.synced.get(key);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
public async getPromise(key: string): Promise<Buffer | undefined> {
|
|
196
|
+
return await this.synced.get(key);
|
|
197
|
+
}
|
|
198
|
+
public set(key: string, value: Buffer) {
|
|
199
|
+
this.synced.set(key, value);
|
|
200
|
+
}
|
|
201
|
+
public async getKeys(): Promise<string[]> {
|
|
202
|
+
return await this.synced.getKeys();
|
|
203
|
+
}
|
|
204
|
+
public async getInfo(key: string) {
|
|
205
|
+
return await this.synced.getInfo(key);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
public async reset() {
|
|
209
|
+
await this.synced.reset();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
177
213
|
export class DiskCollectionRawBrowser {
|
|
178
214
|
constructor(private collectionName: string) { }
|
|
179
215
|
async initStorage(): Promise<IStorage<Buffer>> {
|
|
@@ -221,146 +221,8 @@ class NodeJSDirectoryHandleWrapper implements DirectoryWrapper {
|
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
|
|
224
|
-
function isNotAllowedError(e: unknown): boolean {
|
|
225
|
-
return !!e && typeof e === "object" && (e as any).name === "NotAllowedError";
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// Deduplicates concurrent retry-prompts so only one button is shown to the user,
|
|
229
|
-
// and all in-flight callers wait on the same grant.
|
|
230
|
-
let pendingPermissionPrompt: Promise<void> | undefined;
|
|
231
|
-
async function requestPermissionInteractive(handle: any, mode: "read" | "readwrite" = "readwrite"): Promise<void> {
|
|
232
|
-
if (pendingPermissionPrompt) return pendingPermissionPrompt;
|
|
233
|
-
pendingPermissionPrompt = (async () => {
|
|
234
|
-
// Some browsers grant without user activation (persistent permissions, multi-tab).
|
|
235
|
-
// Try once cheaply before showing UI.
|
|
236
|
-
try {
|
|
237
|
-
if (typeof handle?.requestPermission === "function") {
|
|
238
|
-
const result = await handle.requestPermission({ mode });
|
|
239
|
-
if (result === "granted") return;
|
|
240
|
-
}
|
|
241
|
-
} catch { /* user activation required — fall through to button */ }
|
|
242
|
-
|
|
243
|
-
let root = document.createElement("div");
|
|
244
|
-
document.body.appendChild(root);
|
|
245
|
-
preact.render(<DirectoryPrompter />, root);
|
|
246
|
-
try {
|
|
247
|
-
await new Promise<void>(resolve => {
|
|
248
|
-
displayData.ui = (
|
|
249
|
-
<button
|
|
250
|
-
className={css.fontSize(40).pad2(80, 40)}
|
|
251
|
-
onClick={async () => {
|
|
252
|
-
try {
|
|
253
|
-
const result = await handle.requestPermission({ mode });
|
|
254
|
-
if (result === "granted") {
|
|
255
|
-
resolve();
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
} catch (err) {
|
|
259
|
-
console.error("requestPermission failed:", (err as Error).stack ?? err);
|
|
260
|
-
}
|
|
261
|
-
// Permission still not granted — fall back to a fresh directory pick.
|
|
262
|
-
try {
|
|
263
|
-
const pickedHandle = await window.showDirectoryPicker();
|
|
264
|
-
const grantResult = await (pickedHandle as any).requestPermission({ mode: "readwrite" });
|
|
265
|
-
if (grantResult !== "granted") return;
|
|
266
|
-
let newStoredId = await storeFileSystemPointer({ mode: "readwrite", handle: pickedHandle });
|
|
267
|
-
localStorage.setItem(getFileAPIKey(), newStoredId);
|
|
268
|
-
// Reload so all storage layers re-bind to the new root.
|
|
269
|
-
window.location.reload();
|
|
270
|
-
} catch (err) {
|
|
271
|
-
console.error("showDirectoryPicker failed:", (err as Error).stack ?? err);
|
|
272
|
-
}
|
|
273
|
-
}}
|
|
274
|
-
>
|
|
275
|
-
Click to allow file system access
|
|
276
|
-
</button>
|
|
277
|
-
);
|
|
278
|
-
});
|
|
279
|
-
} finally {
|
|
280
|
-
displayData.ui = undefined;
|
|
281
|
-
preact.render(null, root);
|
|
282
|
-
root.remove();
|
|
283
|
-
}
|
|
284
|
-
})();
|
|
285
|
-
try {
|
|
286
|
-
await pendingPermissionPrompt;
|
|
287
|
-
} finally {
|
|
288
|
-
pendingPermissionPrompt = undefined;
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
async function withPermissionRetry<T>(permissionHandle: any, op: () => Promise<T>): Promise<T> {
|
|
293
|
-
try {
|
|
294
|
-
return await op();
|
|
295
|
-
} catch (e) {
|
|
296
|
-
if (!isNotAllowedError(e)) throw e;
|
|
297
|
-
await requestPermissionInteractive(permissionHandle);
|
|
298
|
-
return await op();
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
function wrapFileWithRetry(file: FileWrapper, permissionHandle: any): FileWrapper {
|
|
303
|
-
return {
|
|
304
|
-
getFile: () => withPermissionRetry(permissionHandle, () => file.getFile()),
|
|
305
|
-
createWritable: async (config) => {
|
|
306
|
-
const writable = await withPermissionRetry(permissionHandle, () => file.createWritable(config));
|
|
307
|
-
return {
|
|
308
|
-
seek: (offset) => withPermissionRetry(permissionHandle, () => writable.seek(offset)),
|
|
309
|
-
write: (value) => withPermissionRetry(permissionHandle, () => writable.write(value)),
|
|
310
|
-
close: () => withPermissionRetry(permissionHandle, () => writable.close()),
|
|
311
|
-
};
|
|
312
|
-
},
|
|
313
|
-
};
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
function wrapDirectoryWithRetry(handle: DirectoryWrapper): DirectoryWrapper {
|
|
317
|
-
const permissionHandle = handle;
|
|
318
|
-
return {
|
|
319
|
-
removeEntry: (key, options) => withPermissionRetry(permissionHandle, () => handle.removeEntry(key, options)),
|
|
320
|
-
getFileHandle: (async (key: string, options?: { create?: boolean }) =>
|
|
321
|
-
wrapFileWithRetry(
|
|
322
|
-
await withPermissionRetry(permissionHandle, () => handle.getFileHandle(key, options)),
|
|
323
|
-
permissionHandle,
|
|
324
|
-
)) as DirectoryWrapper["getFileHandle"],
|
|
325
|
-
getDirectoryHandle: async (key, options) =>
|
|
326
|
-
wrapDirectoryWithRetry(await withPermissionRetry(permissionHandle, () => handle.getDirectoryHandle(key, options))),
|
|
327
|
-
async *[Symbol.asyncIterator]() {
|
|
328
|
-
let iter: AsyncIterator<any> | undefined;
|
|
329
|
-
while (true) {
|
|
330
|
-
let result: IteratorResult<any>;
|
|
331
|
-
try {
|
|
332
|
-
if (!iter) iter = handle[Symbol.asyncIterator]();
|
|
333
|
-
result = await iter.next();
|
|
334
|
-
} catch (e) {
|
|
335
|
-
if (!isNotAllowedError(e)) throw e;
|
|
336
|
-
await requestPermissionInteractive(permissionHandle);
|
|
337
|
-
// Restart iteration after re-grant. Callers of getKeys/reset
|
|
338
|
-
// collect into arrays, so a fresh full iteration is correct.
|
|
339
|
-
iter = undefined;
|
|
340
|
-
continue;
|
|
341
|
-
}
|
|
342
|
-
if (result.done) return;
|
|
343
|
-
const [name, entry] = result.value;
|
|
344
|
-
if (entry.kind === "directory") {
|
|
345
|
-
yield [name, {
|
|
346
|
-
...entry,
|
|
347
|
-
getDirectoryHandle: async (k: string, o?: { create?: boolean }) =>
|
|
348
|
-
wrapDirectoryWithRetry(await withPermissionRetry(permissionHandle, () => entry.getDirectoryHandle(k, o))),
|
|
349
|
-
}];
|
|
350
|
-
} else {
|
|
351
|
-
yield [name, {
|
|
352
|
-
...entry,
|
|
353
|
-
getFile: async () =>
|
|
354
|
-
wrapFileWithRetry(await withPermissionRetry(permissionHandle, () => entry.getFile()), permissionHandle),
|
|
355
|
-
}];
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
},
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
|
|
362
224
|
// NOTE: Blocks until the user provides a directory
|
|
363
|
-
const
|
|
225
|
+
export const getDirectoryHandle = lazy(async function getDirectoryHandle(): Promise<DirectoryWrapper> {
|
|
364
226
|
if (isNode()) {
|
|
365
227
|
return new NodeJSDirectoryHandleWrapper(path.resolve("./data/"));
|
|
366
228
|
}
|
|
@@ -467,12 +329,6 @@ const getDirectoryHandleRaw = lazy(async function getDirectoryHandle(): Promise<
|
|
|
467
329
|
}
|
|
468
330
|
});
|
|
469
331
|
|
|
470
|
-
export const getDirectoryHandle = lazy(async function getDirectoryHandle(): Promise<DirectoryWrapper> {
|
|
471
|
-
const handle = await getDirectoryHandleRaw();
|
|
472
|
-
if (isNode()) return handle;
|
|
473
|
-
return wrapDirectoryWithRetry(handle);
|
|
474
|
-
});
|
|
475
|
-
|
|
476
332
|
export const getFileStorageNested = cache(async function getFileStorage(path: string): Promise<FileStorage> {
|
|
477
333
|
let base = await getDirectoryHandle();
|
|
478
334
|
for (let part of path.split("/")) {
|
|
@@ -7,5 +7,5 @@ export declare function deleteFileSystemPointer(pointer: FileSystemPointer): Pro
|
|
|
7
7
|
export declare function getFileSystemPointer(config: {
|
|
8
8
|
pointer: FileSystemPointer;
|
|
9
9
|
}): Promise<{
|
|
10
|
-
onUserActivation(modeOverride?: "read" | "readwrite"): Promise<FileSystemFileHandle | FileSystemDirectoryHandle
|
|
10
|
+
onUserActivation(modeOverride?: "read" | "readwrite"): Promise<FileSystemFileHandle | FileSystemDirectoryHandle>;
|
|
11
11
|
} | undefined>;
|
|
@@ -63,7 +63,7 @@ export async function getFileSystemPointer(config: {
|
|
|
63
63
|
// IMPORTANT! In some circumstances user activation is not required (with multiple tabs,
|
|
64
64
|
// and potentially with https://developer.chrome.com/blog/persistent-permissions-for-the-file-system-access-api),
|
|
65
65
|
// so... trying to call onUserActivation immmediately is a good idea (although it might throw).
|
|
66
|
-
onUserActivation(modeOverride?: "read" | "readwrite"): Promise<FileSystemFileHandle | FileSystemDirectoryHandle
|
|
66
|
+
onUserActivation(modeOverride?: "read" | "readwrite"): Promise<FileSystemFileHandle | FileSystemDirectoryHandle>
|
|
67
67
|
} | undefined
|
|
68
68
|
> {
|
|
69
69
|
const handle = await read(config.pointer);
|
|
@@ -71,14 +71,10 @@ export async function getFileSystemPointer(config: {
|
|
|
71
71
|
let mode = config.pointer.split("_").at(-1);
|
|
72
72
|
return {
|
|
73
73
|
async onUserActivation(modeOverride) {
|
|
74
|
-
let
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
permission = await (handle as any).requestPermission({ mode: requestedMode });
|
|
74
|
+
let testMode = await (handle as any).queryPermission({ mode: mode });
|
|
75
|
+
if (testMode !== mode) {
|
|
76
|
+
await (handle as any).requestPermission({ mode: modeOverride ?? mode });
|
|
78
77
|
}
|
|
79
|
-
// If the user denied (or otherwise didn't grant) the request, treat the
|
|
80
|
-
// stored pointer as unusable so callers can fall back to showDirectoryPicker.
|
|
81
|
-
if (permission !== "granted") return undefined;
|
|
82
78
|
return handle;
|
|
83
79
|
}
|
|
84
80
|
};
|