sliftutils 1.7.6 → 1.7.8

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.
@@ -12,10 +12,22 @@ async function main() {
12
12
  if (!outputFolder) {
13
13
  throw new Error("No output folder provided. Please use the --outputFolder option.");
14
14
  }
15
- // We prefer production, as this is what the bundler uses internally. This ensures that in the build and when run, we will have the same environment, which will result in the same requires being called.
15
+ // We prefer production, as this is what the bundler uses internally. This ensures that in the build and when run, we will have the same environment, which will result in the same requires being called.
16
16
  process.env.NODE_ENV = process.env.NODE_ENV || "production";
17
17
  require(entryPoint);
18
18
 
19
+ // Warm pass: this process only exists to compile the entry's whole graph
20
+ // (and this bundler's own modules, compiled at our startup) into typenode's
21
+ // on-disk cache, then exit. The caller runs a SECOND, fresh process for the
22
+ // real bundle — that process reads everything from the warm cache and so
23
+ // never has typenode lazy-load the TypeScript compiler. Without this, a
24
+ // cold-cache build (e.g. a fresh CI/server checkout, or an entry whose files
25
+ // no other entry imports) leaves `typescript` in require.cache and the
26
+ // bundler serializes the entire 9 MB+ compiler into the output bundle.
27
+ if (process.argv[4] === "--warm") {
28
+ return;
29
+ }
30
+
19
31
  let name = path.basename(entryPoint);
20
32
  if (name.endsWith(".ts") || name.endsWith(".tsx")) {
21
33
  name = name.split(".").slice(0, -1).join(".");
@@ -10,5 +10,13 @@ export async function bundleEntryCaller(config: {
10
10
  entryPoint = path.resolve(entryPoint).replace(/\\/g, "/");
11
11
  outputFolder = path.resolve(outputFolder).replace(/\\/g, "/");
12
12
  let bundleEntryPath = path.resolve(__dirname, "bundleEntry.ts").replace(/\\/g, "/");
13
- await runPromise(`node -r ./node_modules/typenode/index.js ${JSON.stringify(bundleEntryPath)} ${JSON.stringify(entryPoint)} ${JSON.stringify(outputFolder)}`);
13
+ let base = `node -r ./node_modules/typenode/index.js ${JSON.stringify(bundleEntryPath)} ${JSON.stringify(entryPoint)} ${JSON.stringify(outputFolder)}`;
14
+ // Run twice in SEPARATE processes. The first ("--warm") only imports the
15
+ // entry, which compiles its whole graph into typenode's on-disk cache. The
16
+ // second is a fresh process that imports from that warm cache — so typenode
17
+ // never has to compile anything and never lazy-loads the TypeScript compiler,
18
+ // which would otherwise be captured in require.cache and serialized (9 MB+)
19
+ // into the output bundle. See bundleEntry.ts.
20
+ await runPromise(`${base} --warm`);
21
+ await runPromise(base);
14
22
  }
package/index.d.ts CHANGED
@@ -2423,6 +2423,7 @@ declare module "sliftutils/storage/fileSystemPointer" {
2423
2423
  handle: FileSystemFileHandle | FileSystemDirectoryHandle;
2424
2424
  }): Promise<FileSystemPointer>;
2425
2425
  export declare function deleteFileSystemPointer(pointer: FileSystemPointer): Promise<void>;
2426
+ export declare function findGrantedPointerHandle(mode: "read" | "readwrite"): Promise<FileSystemDirectoryHandle | undefined>;
2426
2427
  export declare function getFileSystemPointer(config: {
2427
2428
  pointer: FileSystemPointer;
2428
2429
  }): Promise<{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sliftutils",
3
- "version": "1.7.6",
3
+ "version": "1.7.8",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -1,5 +1,5 @@
1
1
  import preact from "preact";
2
- import { getFileSystemPointer, storeFileSystemPointer } from "./fileSystemPointer";
2
+ import { findGrantedPointerHandle, getFileSystemPointer, storeFileSystemPointer } from "./fileSystemPointer";
3
3
  import { observable } from "mobx";
4
4
  import { observer } from "../render-utils/observer";
5
5
  import { cache, lazy } from "socket-function/src/caching";
@@ -36,6 +36,9 @@ declare global {
36
36
  // DO NOT enable this is isNode
37
37
  const USE_INDEXED_DB = false;
38
38
 
39
+ // How often a worker rechecks the pointer IndexedDB for a granted handle when none is available yet.
40
+ const WORKER_POLL_INTERVAL_MS = 60 * 1000;
41
+
39
42
  // These mirror the subset of the native FileSystemFileHandle / FileSystemDirectoryHandle API we use, so
40
43
  // the native browser handles, the Node handles, and the remote handles are all interchangeable — and
41
44
  // code written against the native handle (e.g. a recursive walk over `handle.entries()`) works on any of
@@ -460,6 +463,20 @@ export const getDirectoryHandle = lazy(async function getDirectoryHandle(): Prom
460
463
  if (storageRootOverride) {
461
464
  return storageRootOverride as unknown as DirectoryWrapper;
462
465
  }
466
+ // A worker can't show the picker (no DOM / no user activation) and can't read localStorage, but
467
+ // it CAN open the pointer IndexedDB the main-thread picker persists to. Poll it for a handle
468
+ // whose permission is already granted; the owning window may also postMessage a handle in
469
+ // mid-poll (checked each iteration). This has to come before the isNode branch: typesafecss's
470
+ // isNode() returns true in workers (window is undefined), so a worker would otherwise fall into
471
+ // the Node branch and try to use fs/path.
472
+ if ("WorkerGlobalScope" in globalThis) {
473
+ while (true) {
474
+ if (storageRootOverride) return storageRootOverride as unknown as DirectoryWrapper;
475
+ let handle = await findGrantedPointerHandle("readwrite");
476
+ if (handle) return handle as unknown as DirectoryWrapper;
477
+ await new Promise(resolve => setTimeout(resolve, WORKER_POLL_INTERVAL_MS));
478
+ }
479
+ }
463
480
  if (isNode()) {
464
481
  return new NodeJSDirectoryHandleWrapper(path.resolve("./data/"));
465
482
  }
@@ -4,6 +4,7 @@ export declare function storeFileSystemPointer(config: {
4
4
  handle: FileSystemFileHandle | FileSystemDirectoryHandle;
5
5
  }): Promise<FileSystemPointer>;
6
6
  export declare function deleteFileSystemPointer(pointer: FileSystemPointer): Promise<void>;
7
+ export declare function findGrantedPointerHandle(mode: "read" | "readwrite"): Promise<FileSystemDirectoryHandle | undefined>;
7
8
  export declare function getFileSystemPointer(config: {
8
9
  pointer: FileSystemPointer;
9
10
  }): Promise<{
@@ -55,6 +55,36 @@ export async function deleteFileSystemPointer(pointer: FileSystemPointer) {
55
55
  });
56
56
  }
57
57
 
58
+ // Enumerates every stored pointer handle whose permission is already granted for `mode`, newest first
59
+ // (pointer keys start with Date.now(), which sorts lexicographically). Callable from a Web Worker —
60
+ // unlike getFileSystemPointer's onUserActivation flow, this only uses queryPermission, which does
61
+ // NOT require user activation.
62
+ export async function findGrantedPointerHandle(mode: "read" | "readwrite"): Promise<FileSystemDirectoryHandle | undefined> {
63
+ let database = await db();
64
+ if (!database) return undefined;
65
+ let store = database.transaction(objectStoreName, "readonly").objectStore(objectStoreName);
66
+ let keysReq = store.getAllKeys();
67
+ let valuesReq = store.getAll();
68
+ await new Promise((resolve, reject) => {
69
+ keysReq.addEventListener("success", resolve);
70
+ keysReq.addEventListener("error", reject);
71
+ });
72
+ await new Promise((resolve, reject) => {
73
+ valuesReq.addEventListener("success", resolve);
74
+ valuesReq.addEventListener("error", reject);
75
+ });
76
+ let keys = keysReq.result as string[];
77
+ let values = valuesReq.result as (FileSystemFileHandle | FileSystemDirectoryHandle)[];
78
+ let entries = keys.map((key, i) => ({ key, value: values[i], time: +key.split("_")[0] || 0 }))
79
+ .sort((a, b) => b.time - a.time);
80
+ for (let entry of entries) {
81
+ if (entry.value.kind !== "directory") continue;
82
+ let state = await (entry.value as any).queryPermission({ mode });
83
+ if (state === "granted") return entry.value;
84
+ }
85
+ return undefined;
86
+ }
87
+
58
88
  export async function getFileSystemPointer(config: {
59
89
  pointer: FileSystemPointer;
60
90
  }): Promise<{