@vryzel/file-next-headless 0.4.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/LICENSE +21 -0
- package/README.md +18 -0
- package/dist/index.cjs +559 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +271 -0
- package/dist/index.d.ts +271 -0
- package/dist/index.js +552 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/use-file-browser.ts","../src/use-uploader.ts","../src/use-file-actions.ts","../src/use-file-url.ts","../src/use-download-progress.ts","../src/use-file-explorer.ts"],"names":["initial","reducer","useReducer","useCallback","useRef","useEffect","FileSystemError"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * `useFileBrowser` — fetch a file listing via an injected callback\n * and surface loading / empty / error states via a `useReducer`-\n * driven state machine.\n *\n * Spec: `headless#1` — the hook must surface loading/empty/error.\n *\n * Architecture notes:\n * - Dependency injection: the consumer passes `listFiles` as a\n * prop. The hook does NOT import from `@vryzel/file-next/server`\n * directly — the headless package is client-only, and the\n * consumer wires their own auth / RSC layer around the callback.\n * - State machine: idle → loading → (success | error). Errors are\n * preserved as-is (typed `FileSystemError`) so the consumer can\n * discriminate on `code` for retry / fall-through logic.\n * - No zustand, no global state lib. Local `useReducer` keeps the\n * tree-shake small and the consumer's state lib unopinionated.\n */\nimport { useCallback, useEffect, useReducer, type Reducer } from \"react\";\nimport type { FileNode, FileSystemError, Result, TenantId } from \"@vryzel/file-next\";\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/** Narrow shape of what the hook needs from the server action. */\nexport type ListFilesInput = {\n readonly tenantId: TenantId;\n readonly parentId: string | null;\n readonly prefix?: string;\n readonly limit?: number;\n};\n\nexport type ListFilesOutput = {\n readonly items: ReadonlyArray<FileNode>;\n};\n\n/** The injected dependency. Returns the same Result shape the server uses. */\nexport type ListFilesFn = (input: ListFilesInput) => Promise<Result<ListFilesOutput, FileSystemError>>;\n\nexport interface UseFileBrowserOptions {\n /** The list callback (injected; typically wraps a server action). */\n readonly listFiles: ListFilesFn;\n /** Tenant scope — forwarded to every listFiles call. */\n readonly tenantId: TenantId;\n /** Folder to list (null = root). */\n readonly parentId: string | null;\n /** Optional S3 prefix filter. */\n readonly prefix?: string;\n /** Optional cap on items per call. */\n readonly limit?: number;\n /**\n * Auto-fetch on mount (default: false). The spec describes the\n * state machine as `idle → user calls refetch() → loading → ...`,\n * so the hook is imperative by default. Set `true` to fetch as\n * soon as the component mounts (typical for full-page file\n * browsers that want to show data immediately).\n */\n readonly autoFetch?: boolean;\n}\n\nexport type UseFileBrowserStatus = \"idle\" | \"loading\" | \"success\" | \"error\";\n\nexport interface UseFileBrowserState {\n readonly status: UseFileBrowserStatus;\n readonly files: ReadonlyArray<FileNode>;\n readonly error: FileSystemError | null;\n}\n\nexport interface UseFileBrowserReturn {\n readonly status: UseFileBrowserStatus;\n readonly files: ReadonlyArray<FileNode>;\n readonly error: FileSystemError | null;\n /** Re-run the listFiles call. Safe to call any time; cancels nothing. */\n readonly refetch: () => Promise<void>;\n}\n\n// ---------------------------------------------------------------------------\n// State machine\n// ---------------------------------------------------------------------------\n\ntype Action =\n | { type: \"FETCH_START\" }\n | { type: \"FETCH_SUCCESS\"; files: ReadonlyArray<FileNode> }\n | { type: \"FETCH_ERROR\"; error: FileSystemError };\n\nconst initial: UseFileBrowserState = {\n status: \"idle\",\n files: [],\n error: null,\n};\n\nconst reducer: Reducer<UseFileBrowserState, Action> = (state, action) => {\n switch (action.type) {\n case \"FETCH_START\":\n return { status: \"loading\", files: [], error: null };\n case \"FETCH_SUCCESS\":\n return { status: \"success\", files: action.files, error: null };\n case \"FETCH_ERROR\":\n return { status: \"error\", files: [], error: action.error };\n }\n};\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\n/**\n * useFileBrowser — list files for a folder via an injected callback.\n *\n * The hook fires the list on mount (unless `autoFetch: false`) and\n * exposes a stable `refetch` to re-run. The state is local: every\n * consumer instance has its own state machine.\n */\nexport function useFileBrowser(options: UseFileBrowserOptions): UseFileBrowserReturn {\n const { listFiles, tenantId, parentId, prefix, limit, autoFetch = false } = options;\n const [state, dispatch] = useReducer(reducer, initial);\n\n const run = useCallback(async (): Promise<void> => {\n dispatch({ type: \"FETCH_START\" });\n const result = await listFiles({ tenantId, parentId, prefix, limit });\n if (result.ok) {\n dispatch({ type: \"FETCH_SUCCESS\", files: result.value.items });\n } else {\n dispatch({ type: \"FETCH_ERROR\", error: result.error });\n }\n }, [listFiles, tenantId, parentId, prefix, limit]);\n\n useEffect(() => {\n if (autoFetch) {\n void run();\n }\n }, [autoFetch, run]);\n\n return {\n status: state.status,\n files: state.files,\n error: state.error,\n refetch: run,\n };\n}\n","/**\n * `useUploader` — upload a file via XHR with real progress events,\n * and expose `cancel()` to abort the in-flight request.\n *\n * Spec: `headless#2` — the hook must:\n * - Use XHR (not fetch) so we get real upload progress events.\n * - Surface a `cancel()` that aborts the XHR and stops any post-\n * upload confirmation callback.\n * - Surface success / error / aborted states via `useReducer`.\n * - On XHR error, return a typed `FileSystemError` so the consumer\n * can discriminate on `code` for retry / fall-through logic.\n *\n * Architecture notes:\n * - Dependency injection: the consumer passes `uploadUrl` and an\n * optional `confirmUpload` callback. The hook does NOT import\n * from `@vryzel/file-next/server` directly — the consumer wires their\n * own auth / RSC layer.\n * - State machine: idle → uploading → (success | error | aborted).\n * Local `useReducer` keeps the tree-shake small (no zustand).\n * - The current XHR is held in a `useRef` so `cancel()` can abort\n * the right instance even after re-renders.\n * - React 18 strict-mode double-invokes effects in dev; this hook\n * does not use useEffect for fetching (uploads are imperative\n * via `upload()`), so double-invocation is not an issue.\n */\nimport { useCallback, useReducer, useRef, type Reducer } from \"react\";\nimport { FileSystemError } from \"@vryzel/file-next/errors\";\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/** Narrow file shape — the hook doesn't need the full DOM File. */\nexport interface UploaderFile {\n readonly name: string;\n readonly size: number;\n readonly type: string;\n readonly content: Blob;\n}\n\n/** Optional post-upload confirm callback. Receives the XHR + the file. */\nexport type ConfirmUploadFn = (\n xhr: XMLHttpRequest,\n file: UploaderFile,\n meta?: { id?: string },\n) => void;\n\nexport interface RequestUploadResult {\n readonly url: string;\n readonly method?: \"PUT\" | \"POST\";\n readonly headers?: Record<string, string>;\n readonly id?: string;\n}\n\nexport interface UseUploaderOptions {\n /** The presigned URL to PUT the file to. Ignored when `requestUpload` is set. */\n readonly uploadUrl?: string;\n /** Resolve a fresh presigned URL per file (preferred). */\n readonly requestUpload?: (file: UploaderFile) => Promise<RequestUploadResult>;\n /**\n * Optional callback fired once the upload completes successfully.\n * Typically used to trigger a server action that records metadata.\n * NEVER fires if the upload is canceled.\n */\n readonly confirmUpload?: ConfirmUploadFn;\n}\n\nexport type UseUploaderStatus = \"idle\" | \"uploading\" | \"success\" | \"error\" | \"aborted\";\n\nexport interface UseUploaderState {\n readonly status: UseUploaderStatus;\n readonly progress: number; // 0..100, integer\n readonly error: FileSystemError | null;\n}\n\nexport interface UseUploaderReturn {\n readonly status: UseUploaderStatus;\n readonly progress: number;\n readonly error: FileSystemError | null;\n /** Kick off an upload. No-op if an upload is already in flight. */\n readonly upload: (file: UploaderFile) => void;\n /** Abort the in-flight upload. No-op if idle / done. */\n readonly cancel: () => void;\n}\n\n// ---------------------------------------------------------------------------\n// State machine\n// ---------------------------------------------------------------------------\n\ntype Action =\n | { type: \"UPLOAD_START\" }\n | { type: \"PROGRESS\"; loaded: number; total: number }\n | { type: \"UPLOAD_SUCCESS\" }\n | { type: \"UPLOAD_ERROR\"; error: FileSystemError }\n | { type: \"UPLOAD_ABORTED\" };\n\nconst initial: UseUploaderState = {\n status: \"idle\",\n progress: 0,\n error: null,\n};\n\nconst reducer: Reducer<UseUploaderState, Action> = (state, action) => {\n switch (action.type) {\n case \"UPLOAD_START\":\n return { status: \"uploading\", progress: 0, error: null };\n case \"PROGRESS\": {\n if (action.total <= 0) return state;\n const pct = Math.min(100, Math.round((action.loaded / action.total) * 100));\n return { ...state, progress: pct };\n }\n case \"UPLOAD_SUCCESS\":\n return { status: \"success\", progress: 100, error: null };\n case \"UPLOAD_ERROR\":\n return { status: \"error\", progress: state.progress, error: action.error };\n case \"UPLOAD_ABORTED\":\n return { status: \"aborted\", progress: state.progress, error: null };\n }\n};\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\n/**\n * useUploader — upload via XHR with progress + cancel.\n *\n * The hook is intentionally imperative: the consumer calls\n * `upload(file)` to start, and may call `cancel()` to abort.\n * No effects fire automatically (uploads should not happen until\n * the user picks a file).\n */\nexport function useUploader(options: UseUploaderOptions): UseUploaderReturn {\n const { uploadUrl, requestUpload, confirmUpload } = options;\n const [state, dispatch] = useReducer(reducer, initial);\n\n // Hold the live XHR in a ref so cancel() can abort the right one\n // even across re-renders or after a successful upload.\n const xhrRef = useRef<XMLHttpRequest | null>(null);\n // Tracks whether cancel() was called for the current XHR, so\n // load/error listeners don't override the canceled state.\n const canceledRef = useRef<boolean>(false);\n\n const startXhr = useCallback(\n (file: UploaderFile, target: RequestUploadResult) => {\n if (xhrRef.current !== null) return;\n\n const xhr = new XMLHttpRequest();\n xhrRef.current = xhr;\n canceledRef.current = false;\n\n xhr.upload.addEventListener(\"progress\", (event) => {\n if (event.lengthComputable) {\n dispatch({ type: \"PROGRESS\", loaded: event.loaded, total: event.total });\n }\n });\n\n xhr.addEventListener(\"load\", () => {\n if (xhrRef.current !== xhr || canceledRef.current) return;\n xhrRef.current = null;\n if (xhr.status < 200 || xhr.status >= 300) {\n let message = `Upload failed (${xhr.status})`;\n try {\n const parsed = JSON.parse(xhr.responseText) as {\n error?: { message?: string };\n };\n if (parsed.error?.message) message = parsed.error.message;\n } catch {\n /* keep status fallback */\n }\n dispatch({\n type: \"UPLOAD_ERROR\",\n error: new FileSystemError({\n code: \"NetworkError\",\n retryable: true,\n message,\n cause: { code: \"HttpError\", message: `status ${xhr.status}` },\n }),\n });\n return;\n }\n dispatch({ type: \"UPLOAD_SUCCESS\" });\n void Promise.resolve(confirmUpload?.(xhr, file, { id: target.id })).catch(\n (error: unknown) => {\n dispatch({\n type: \"UPLOAD_ERROR\",\n error: new FileSystemError({\n code: \"InternalError\",\n retryable: false,\n message: error instanceof Error ? error.message : \"confirmUpload failed\",\n }),\n });\n },\n );\n });\n\n xhr.addEventListener(\"error\", () => {\n if (xhrRef.current !== xhr || canceledRef.current) return;\n xhrRef.current = null;\n dispatch({\n type: \"UPLOAD_ERROR\",\n error: new FileSystemError({\n code: \"NetworkError\",\n retryable: true,\n message:\n \"XHR upload failed (usually CORS). Same-origin /api/upload avoids this.\",\n cause: { code: \"XhrError\", message: \"XHR error event\" },\n }),\n });\n });\n\n dispatch({ type: \"UPLOAD_START\" });\n xhr.open(target.method ?? \"PUT\", target.url);\n xhr.setRequestHeader(\"Content-Type\", file.type || \"application/octet-stream\");\n for (const [header, value] of Object.entries(target.headers ?? {})) {\n xhr.setRequestHeader(header, value);\n }\n xhr.send(file.content);\n },\n [confirmUpload],\n );\n\n const upload = useCallback(\n (file: UploaderFile) => {\n if (xhrRef.current !== null) return;\n if (requestUpload) {\n dispatch({ type: \"UPLOAD_START\" });\n void requestUpload(file)\n .then((target) => {\n if (canceledRef.current) return;\n xhrRef.current = null;\n startXhr(file, target);\n })\n .catch((error: unknown) => {\n dispatch({\n type: \"UPLOAD_ERROR\",\n error: new FileSystemError({\n code: \"NetworkError\",\n retryable: true,\n message: error instanceof Error ? error.message : \"requestUpload failed\",\n }),\n });\n });\n return;\n }\n if (!uploadUrl) return;\n startXhr(file, { url: uploadUrl, method: \"PUT\" });\n },\n [requestUpload, startXhr, uploadUrl],\n );\n\n const cancel = useCallback(() => {\n const xhr = xhrRef.current;\n if (xhr === null) return;\n xhr.abort();\n canceledRef.current = true;\n xhrRef.current = null;\n // Dispatch synchronously: tests (and consumers) expect state to\n // flip to 'aborted' the moment cancel() returns, not when the\n // browser eventually fires the abort event (jsdom never fires it).\n dispatch({ type: \"UPLOAD_ABORTED\" });\n }, []);\n\n return {\n status: state.status,\n progress: state.progress,\n error: state.error,\n upload,\n cancel,\n };\n}\n","/**\n * `useFileActions` — optimistic delete / move / copy with rollback.\n *\n * Spec: `headless#3` — the hook must:\n * - Apply an optimistic UI update immediately on call.\n * - Restore the pre-mutation snapshot on action failure.\n * - Surface the error via the `error` field so the consumer can\n * decide whether to retry, log, or toast.\n * - Track `isPending` so the consumer can disable buttons during\n * the round-trip.\n *\n * Architecture notes:\n * - Dependency injection: the consumer passes `actions` (the 3\n * server-action callbacks) and `files` / `setFiles` (their state\n * pair). The hook does NOT import from `@vryzel/file-next/server` and\n * does NOT take ownership of the file list — the consumer\n * remains the source of truth for the array.\n * - State machine: idle → pending → (success | error). The\n * `files` / `setFiles` pair lives OUTSIDE the reducer (it's the\n * consumer's state). The reducer only tracks isPending + error.\n * - The \"snapshot\" is just the original `files` reference. On\n * failure we call `setFiles(snapshot)` to restore — because the\n * snapshot is the same array reference, React skips re-render if\n * the consumer memoizes on identity. On success we call\n * `setFiles(next)` with the optimistic update.\n */\nimport { useCallback, useReducer, type Reducer } from \"react\";\nimport type { FileNode, FileSystemError, Result } from \"@vryzel/file-next\";\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport interface DeleteFileInput {\n readonly id: string;\n}\nexport interface DeleteFileOutput {\n readonly id: string;\n}\n\nexport interface MoveFileInput {\n readonly id: string;\n readonly newParentId: string;\n}\nexport interface MoveFileOutput {\n readonly id: string;\n}\n\nexport interface CopyFileInput {\n readonly id: string;\n readonly newParentId: string;\n}\nexport interface CopyFileOutput {\n readonly node: FileNode;\n}\n\nexport type DeleteFileFn = (input: DeleteFileInput) => Promise<Result<DeleteFileOutput, FileSystemError>>;\nexport type MoveFileFn = (input: MoveFileInput) => Promise<Result<MoveFileOutput, FileSystemError>>;\nexport type CopyFileFn = (input: CopyFileInput) => Promise<Result<CopyFileOutput, FileSystemError>>;\nexport type RenameFileFn = (id: string, newName: string) => Promise<void>;\n\nexport interface UseFileActionsOptions {\n /** The current file list (consumer-owned). */\n readonly files: ReadonlyArray<FileNode>;\n /** The state setter for the file list (consumer-owned). */\n readonly setFiles: (next: ReadonlyArray<FileNode>) => void;\n /** The three server-action callbacks (typically wrappers around RSC actions). */\n readonly actions: {\n readonly deleteFile: DeleteFileFn;\n readonly moveFile: MoveFileFn;\n readonly copyFile: CopyFileFn;\n readonly renameFile: RenameFileFn;\n };\n}\n\nexport type UseFileActionsStatus = \"idle\" | \"pending\";\n\nexport interface UseFileActionsState {\n readonly status: UseFileActionsStatus;\n readonly error: FileSystemError | null;\n}\n\nexport interface UseFileActionsReturn {\n readonly status: UseFileActionsStatus;\n readonly isPending: boolean;\n readonly error: FileSystemError | null;\n readonly deleteFile: (id: string) => Promise<void>;\n readonly moveFile: (id: string, newParentId: string) => Promise<void>;\n readonly copyFile: (id: string, newParentId: string) => Promise<void>;\n readonly renameFile: (id: string, newName: string) => Promise<void>;\n}\n\n// ---------------------------------------------------------------------------\n// State machine\n// ---------------------------------------------------------------------------\n\ntype Action =\n | { type: \"START\" }\n | { type: \"SUCCESS\" }\n | { type: \"ERROR\"; error: FileSystemError };\n\nconst initial: UseFileActionsState = {\n status: \"idle\",\n error: null,\n};\n\nconst reducer: Reducer<UseFileActionsState, Action> = (state, action) => {\n switch (action.type) {\n case \"START\":\n return { status: \"pending\", error: null };\n case \"SUCCESS\":\n return { status: \"idle\", error: null };\n case \"ERROR\":\n return { status: \"idle\", error: action.error };\n }\n};\n\n// ---------------------------------------------------------------------------\n// Pure helpers (exported for testability)\n// ---------------------------------------------------------------------------\n\n/** Optimistic removal: drop the node with this id. */\nexport function applyDelete(\n files: ReadonlyArray<FileNode>,\n id: string,\n): ReadonlyArray<FileNode> {\n return files.filter((f) => f.id !== id);\n}\n\n/** Optimistic move: change the parentId of the node with this id. */\nexport function applyMove(\n files: ReadonlyArray<FileNode>,\n id: string,\n newParentId: string,\n): ReadonlyArray<FileNode> {\n return files.map((f) => (f.id === id ? { ...f, parentId: newParentId } : f));\n}\n\n/** Optimistic copy: append the new sibling node. */\nexport function applyCopy(\n files: ReadonlyArray<FileNode>,\n node: FileNode,\n): ReadonlyArray<FileNode> {\n return [...files, node];\n}\n\nexport function applyRename(\n files: ReadonlyArray<FileNode>,\n id: string,\n newName: string,\n): ReadonlyArray<FileNode> {\n return files.map((file) => (file.id === id ? { ...file, name: newName } : file));\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\n/**\n * useFileActions — optimistic mutations with rollback.\n *\n * Each mutator:\n * 1. Snapshots the current `files` reference.\n * 2. Dispatches `START` (isPending = true).\n * 3. Calls `setFiles(optimistic)` immediately.\n * 4. Awaits the action.\n * 5. On `ok: true`, dispatches `SUCCESS` (no further setFiles — the\n * server's response is the new truth; the optimistic value\n * either matches or the consumer reconciles via refresh).\n * 6. On `ok: false`, calls `setFiles(snapshot)` to roll back, then\n * dispatches `ERROR`.\n */\nexport function useFileActions(options: UseFileActionsOptions): UseFileActionsReturn {\n const { files, setFiles, actions } = options;\n const [state, dispatch] = useReducer(reducer, initial);\n\n // Generic runner: snapshot + optimistic apply + rollback on err.\n const run = useCallback(\n async <TInput, TOutput>(args: {\n input: TInput;\n optimistic: () => ReadonlyArray<FileNode>;\n act: (input: TInput) => Promise<Result<TOutput, FileSystemError>>;\n /** Extract any additional file-list mutation from a successful result. */\n onSuccess?: (output: TOutput) => ReadonlyArray<FileNode>;\n }): Promise<void> => {\n const snapshot = files;\n const optimisticNext = args.optimistic();\n dispatch({ type: \"START\" });\n // Skip setFiles when the optimistic update returns the same\n // reference (e.g., copyFile — we don't know the new id until\n // the server responds). Calling setFiles with identity-equal\n // data is a no-op for React but still fires the spy.\n if (optimisticNext !== snapshot) {\n setFiles(optimisticNext);\n }\n const result = await args.act(args.input);\n if (result.ok) {\n if (args.onSuccess) {\n setFiles(args.onSuccess(result.value));\n }\n dispatch({ type: \"SUCCESS\" });\n } else {\n setFiles(snapshot);\n dispatch({ type: \"ERROR\", error: result.error });\n }\n },\n [files, setFiles],\n );\n\n const deleteFile = useCallback(\n (id: string) =>\n run({\n input: { id },\n optimistic: () => applyDelete(files, id),\n act: actions.deleteFile,\n }),\n [run, actions.deleteFile, files],\n );\n\n const moveFile = useCallback(\n (id: string, newParentId: string) =>\n run({\n input: { id, newParentId },\n optimistic: () => applyMove(files, id, newParentId),\n act: actions.moveFile,\n }),\n [run, actions.moveFile, files],\n );\n\n const copyFile = useCallback(\n (id: string, newParentId: string) =>\n run({\n input: { id, newParentId },\n optimistic: () => files, // copy doesn't remove anything\n act: actions.copyFile,\n onSuccess: (output) => applyCopy(files, output.node),\n }),\n [run, actions.copyFile, files],\n );\n\n const renameFile = useCallback(\n async (id: string, newName: string) => {\n const snapshot = files;\n dispatch({ type: \"START\" });\n setFiles(applyRename(files, id, newName));\n try {\n await actions.renameFile(id, newName);\n dispatch({ type: \"SUCCESS\" });\n } catch (error) {\n setFiles(snapshot);\n dispatch({ type: \"ERROR\", error: error as FileSystemError });\n }\n },\n [actions.renameFile, files, setFiles],\n );\n\n return {\n status: state.status,\n isPending: state.status === \"pending\",\n error: state.error,\n deleteFile,\n moveFile,\n copyFile,\n renameFile,\n };\n}\n","/**\n * `useFileUrl` — resolve a presigned URL via an injected callback.\n *\n * Spec (lighter than the other hooks):\n * - Status: `idle` (disabled or pre-mount) → `loading` → `success`\n * | `error`.\n * - On key change, re-fetch.\n * - When `enabled` flips to false, the URL stays in place (we\n * don't clear it) so the consumer can hide the UI without\n * re-triggering a flash.\n *\n * Architecture notes:\n * - Dependency injection: `getDownloadUrl` is a callback the\n * consumer passes in. The hook does NOT import from\n * `@vryzel/file-next/server`.\n * - `useState` (not `useReducer`) — only 3 fields, 4 transitions.\n * Per the design decision, simpler hooks can use useState.\n * - React 18 strict-mode double-invokes effects in dev — guarded\n * with a `useRef` \"already fetching for this key\" flag.\n * - Cancellation: if the key changes mid-flight, the in-flight\n * result is discarded (we compare the key against a ref before\n * committing state).\n */\nimport { useEffect, useRef, useState } from \"react\";\nimport type { FileSystemError, Result } from \"@vryzel/file-next\";\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport interface GetDownloadUrlInput {\n readonly key: string;\n}\n\nexport interface GetDownloadUrlOutput {\n readonly url: string;\n}\n\nexport type GetDownloadUrlFn = (\n input: GetDownloadUrlInput,\n) => Promise<Result<GetDownloadUrlOutput, FileSystemError>>;\n\nexport interface UseFileUrlOptions {\n /** The injected download-URL resolver (wraps a server action). */\n readonly getDownloadUrl: GetDownloadUrlFn;\n /** The object key to resolve a URL for. */\n readonly key: string;\n /**\n * When false, the fetch is skipped (status stays `idle`, url is\n * null). Useful for click-to-load patterns and conditional prefetch.\n * Default: true.\n */\n readonly enabled?: boolean;\n}\n\nexport type UseFileUrlStatus = \"idle\" | \"loading\" | \"success\" | \"error\";\n\nexport interface UseFileUrlReturn {\n readonly url: string | null;\n readonly status: UseFileUrlStatus;\n readonly error: FileSystemError | null;\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\nexport function useFileUrl(options: UseFileUrlOptions): UseFileUrlReturn {\n const { getDownloadUrl, key, enabled = true } = options;\n\n const [url, setUrl] = useState<string | null>(null);\n const [error, setError] = useState<FileSystemError | null>(null);\n // status is derived from the other two fields — no separate state.\n // But we need to distinguish \"loading\" from \"idle but enabled\", so\n // we track it explicitly.\n const [status, setStatus] = useState<UseFileUrlStatus>(enabled ? \"loading\" : \"idle\");\n\n // Refs to support cancellation across re-renders.\n // - latestKey: the key the most recent in-flight fetch was started for\n // - latestEnabled: ditto for enabled\n const latestKeyRef = useRef<string | null>(null);\n const latestEnabledRef = useRef<boolean | null>(null);\n\n useEffect(() => {\n if (!enabled) {\n latestEnabledRef.current = false;\n setStatus(\"idle\");\n return;\n }\n latestEnabledRef.current = true;\n latestKeyRef.current = key;\n setStatus(\"loading\");\n setError(null);\n\n let cancelled = false;\n void (async () => {\n const result = await getDownloadUrl({ key });\n // Bail if the key changed or the hook was disabled mid-flight.\n if (cancelled) return;\n if (latestKeyRef.current !== key) return;\n if (latestEnabledRef.current === false) return;\n if (result.ok) {\n setUrl(result.value.url);\n setStatus(\"success\");\n } else {\n setError(result.error);\n setStatus(\"error\");\n }\n })();\n\n return () => {\n cancelled = true;\n };\n }, [getDownloadUrl, key, enabled]);\n\n return { url, status, error };\n}\n","/**\n * `useDownloadProgress` — download a file via fetch + reader,\n * emitting progress 0..100 and exposing the final blob.\n *\n * Spec:\n * - Status: idle → downloading → (success | error | aborted).\n * - `cancel()` aborts the in-flight fetch via AbortController.\n * - On success, expose a Blob with the full contents.\n * - On error, surface a typed FileSystemError.\n *\n * Architecture notes:\n * - `useReducer` for state — 5 fields, 5 transitions.\n * - AbortController is created per-fetch and stored in a ref so\n * `cancel()` can abort the right one across re-renders.\n * - React 18 strict-mode double-invokes effects in dev — guarded\n * by a \"latest fetch id\" ref so the second invocation is a\n * no-op (its result is discarded).\n * - `fetch` is global; tests stub it via `vi.stubGlobal('fetch', ...)`.\n */\nimport { useCallback, useEffect, useReducer, useRef, type Reducer } from \"react\";\nimport { FileSystemError } from \"@vryzel/file-next/errors\";\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport interface UseDownloadProgressOptions {\n /** The URL to download from. */\n readonly url: string;\n /**\n * Optional AbortSignal to share cancellation with the consumer's\n * own logic (e.g., the consumer unmounts the component).\n * If provided, the hook's `cancel()` AND this signal both abort.\n */\n readonly externalSignal?: AbortSignal;\n}\n\nexport type UseDownloadProgressStatus =\n | \"idle\"\n | \"loading\"\n | \"success\"\n | \"error\"\n | \"aborted\";\n\nexport interface UseDownloadProgressState {\n readonly status: UseDownloadProgressStatus;\n readonly progress: number; // 0..100\n readonly blob: Blob | null;\n readonly error: FileSystemError | null;\n}\n\nexport interface UseDownloadProgressReturn {\n readonly status: UseDownloadProgressStatus;\n readonly progress: number;\n readonly blob: Blob | null;\n readonly error: FileSystemError | null;\n readonly cancel: () => void;\n}\n\n// ---------------------------------------------------------------------------\n// State machine\n// ---------------------------------------------------------------------------\n\ntype Action =\n | { type: \"START\" }\n | { type: \"PROGRESS\"; loaded: number; total: number }\n | { type: \"SUCCESS\"; blob: Blob }\n | { type: \"ERROR\"; error: FileSystemError }\n | { type: \"ABORTED\" };\n\nconst initial: UseDownloadProgressState = {\n status: \"idle\",\n progress: 0,\n blob: null,\n error: null,\n};\n\nconst reducer: Reducer<UseDownloadProgressState, Action> = (state, action) => {\n switch (action.type) {\n case \"START\":\n return { status: \"loading\", progress: 0, blob: null, error: null };\n case \"PROGRESS\": {\n if (action.total <= 0) return state;\n const pct = Math.min(100, Math.round((action.loaded / action.total) * 100));\n return { ...state, progress: pct };\n }\n case \"SUCCESS\":\n return { status: \"success\", progress: 100, blob: action.blob, error: null };\n case \"ERROR\":\n return { ...state, status: \"error\", error: action.error };\n case \"ABORTED\":\n return { ...state, status: \"aborted\" };\n }\n};\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\nexport function useDownloadProgress(\n options: UseDownloadProgressOptions,\n): UseDownloadProgressReturn {\n const { url, externalSignal } = options;\n const [state, dispatch] = useReducer(reducer, initial);\n\n // Per-fetch AbortController; replaced on each new download.\n const acRef = useRef<AbortController | null>(null);\n // Monotonic id to ignore stale fetch completions (strict-mode\n // double-invocation, key changes, abort+restart).\n const fetchIdRef = useRef(0);\n\n useEffect(() => {\n const fetchId = ++fetchIdRef.current;\n const ac = new AbortController();\n acRef.current = ac;\n\n // Link the external signal if provided: aborting it aborts us.\n let onExternalAbort: (() => void) | null = null;\n if (externalSignal) {\n onExternalAbort = () => ac.abort();\n if (externalSignal.aborted) {\n ac.abort();\n } else {\n externalSignal.addEventListener(\"abort\", onExternalAbort);\n }\n }\n\n dispatch({ type: \"START\" });\n\n void (async () => {\n try {\n const res = await fetch(url, { signal: ac.signal });\n if (!res.ok) {\n throw new Error(`HTTP ${res.status}`);\n }\n const body = res.body;\n if (!body) {\n // No body — finish immediately with an empty Blob.\n if (fetchId !== fetchIdRef.current) return;\n dispatch({ type: \"SUCCESS\", blob: new Blob([]) });\n return;\n }\n const reader = body.getReader();\n const chunks: Uint8Array[] = [];\n let loaded = 0;\n // Total may be unknown (no Content-Length header). Default to\n // a sentinel that lets PROGRESS report incrementally as best\n // we can — fall back to \"loaded\" alone, capped at 99 until done.\n const totalRaw = res.headers?.get?.(\"content-length\");\n const total = totalRaw ? Number(totalRaw) : 0;\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n if (!value) continue;\n chunks.push(value);\n loaded += value.byteLength;\n if (fetchId !== fetchIdRef.current) return;\n if (total > 0) {\n dispatch({ type: \"PROGRESS\", loaded, total });\n }\n }\n\n if (fetchId !== fetchIdRef.current) return;\n const blob = new Blob(chunks as BlobPart[]);\n dispatch({ type: \"SUCCESS\", blob });\n } catch (err) {\n if (fetchId !== fetchIdRef.current) return;\n // AbortError from fetch: surface as `aborted` (not error).\n if (ac.signal.aborted || (err instanceof Error && err.name === \"AbortError\")) {\n dispatch({ type: \"ABORTED\" });\n return;\n }\n dispatch({\n type: \"ERROR\",\n error: new FileSystemError({\n code: \"NetworkError\",\n retryable: true,\n message: err instanceof Error ? err.message : \"fetch failed\",\n }),\n });\n } finally {\n if (onExternalAbort && externalSignal) {\n externalSignal.removeEventListener(\"abort\", onExternalAbort);\n }\n }\n })();\n\n return () => {\n // Effect cleanup: abort the fetch so the in-flight promise resolves.\n ac.abort();\n };\n }, [url, externalSignal]);\n\n const cancel = useCallback(() => {\n acRef.current?.abort();\n // Dispatch ABORTED synchronously so the consumer sees the state\n // flip the moment cancel() returns. The async IIFE's catch\n // block will ALSO try to dispatch ABORTED on fetch rejection,\n // but the idempotent `if (fetchId !== fetchIdRef.current) return`\n // guard (already in place) covers that — once acRef is cleared,\n // the in-flight fetch rejects, the catch sees acRef.current === null\n // OR the fetchId mismatch, and bails.\n // For full determinism in case the reader loop is awaiting an\n // unresponsive stub, also dispatch directly:\n dispatch({ type: \"ABORTED\" });\n }, []);\n\n return {\n status: state.status,\n progress: state.progress,\n blob: state.blob,\n error: state.error,\n cancel,\n };\n}\n","/**\n * `useFileExplorer` — view mode + selection + drag state for the\n * `<FileExplorer />` orchestrator.\n *\n * Wraps `useFileBrowser` (the existing list-fetching hook) and\n * layers on top:\n * - view mode (list | grid)\n * - selected item ids (single-select; multi-select deferred to v2)\n * - drag-drop state (what's being dragged, what target it's over)\n * - context-menu target (which item the right-click was on)\n *\n * Why a separate hook:\n * - `<FileExplorer />` is the visible orchestrator, but the\n * headless state machine is testable in isolation.\n * - Consumers who want only the data (no UI) can use this hook\n * and roll their own toolbar + render.\n *\n * Architecture:\n * - Pure local state via `useReducer`; no globals, no zustand.\n * - View mode persists across parentId changes (kept in a ref).\n * - Selection clears when parentId changes (new folder = fresh\n * selection).\n */\nimport {\n useCallback,\n useEffect,\n useMemo,\n useReducer,\n useRef,\n} from \"react\";\nimport { useFileBrowser } from \"./use-file-browser\";\nimport type { FileNode, TenantId } from \"@vryzel/file-next\";\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport type ViewMode = \"list\" | \"grid\";\n\nexport interface UseFileExplorerOptions {\n /** List callback (injected). */\n readonly listFiles: Parameters<typeof useFileBrowser>[0][\"listFiles\"];\n /** Tenant scope. */\n readonly tenantId: TenantId;\n /** Current folder (null = root). */\n readonly parentId: string | null;\n /** Initial view mode. Default: \"list\". */\n readonly initialView?: ViewMode;\n /** Auto-fetch on mount. Default: true (the explorer expects data). */\n readonly autoFetch?: boolean;\n /** Optional callback fired when the user picks a file/folder via click or keyboard. */\n readonly onActivate?: (file: FileNode) => void;\n readonly onMove?: (input: {\n itemIds: ReadonlyArray<string>;\n destinationFolderId: string;\n }) => Promise<void> | void;\n}\n\nexport interface UseFileExplorerReturn {\n readonly status: ReturnType<typeof useFileBrowser>[\"status\"];\n readonly files: ReadonlyArray<FileNode>;\n readonly error: ReturnType<typeof useFileBrowser>[\"error\"];\n readonly refetch: () => Promise<void>;\n\n readonly view: ViewMode;\n readonly setView: (v: ViewMode) => void;\n\n readonly selectedId: string | null;\n readonly setSelected: (id: string | null) => void;\n\n // Context menu target — set by the consumer's onContextMenu handler.\n readonly contextTarget: FileNode | null;\n readonly openContextMenu: (file: FileNode | null) => void;\n\n // Drag-drop state. The consumer wires `onDragStart` to call\n // beginDrag and `onDragOver`/`onDragLeave` on drop targets to\n // call setDropTarget. The orchestrator calls `endDrag` on\n // drop or escape.\n readonly draggingId: string | null;\n readonly dropTargetId: string | null;\n readonly beginDrag: (id: string) => void;\n readonly setDropTarget: (id: string | null) => void;\n readonly commitDrop: (destinationFolderId: string) => void;\n readonly endDrag: () => void;\n}\n\n// ---------------------------------------------------------------------------\n// Internal state\n// ---------------------------------------------------------------------------\n\ninterface State {\n view: ViewMode;\n selectedId: string | null;\n contextTarget: FileNode | null;\n draggingId: string | null;\n dropTargetId: string | null;\n}\n\ntype Action =\n | { type: \"SET_VIEW\"; view: ViewMode }\n | { type: \"SELECT\"; id: string | null }\n | { type: \"OPEN_CONTEXT\"; file: FileNode | null }\n | { type: \"BEGIN_DRAG\"; id: string }\n | { type: \"SET_DROP_TARGET\"; id: string | null }\n | { type: \"END_DRAG\" };\n\nconst reducer = (state: State, action: Action): State => {\n switch (action.type) {\n case \"SET_VIEW\":\n return { ...state, view: action.view };\n case \"SELECT\":\n return { ...state, selectedId: action.id };\n case \"OPEN_CONTEXT\":\n return { ...state, contextTarget: action.file };\n case \"BEGIN_DRAG\":\n return { ...state, draggingId: action.id, dropTargetId: null };\n case \"SET_DROP_TARGET\":\n return { ...state, dropTargetId: action.id };\n case \"END_DRAG\":\n return { ...state, draggingId: null, dropTargetId: null };\n }\n};\n\nconst initialState = (initialView: ViewMode): State => ({\n view: initialView,\n selectedId: null,\n contextTarget: null,\n draggingId: null,\n dropTargetId: null,\n});\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\nexport function useFileExplorer(\n options: UseFileExplorerOptions,\n): UseFileExplorerReturn {\n const {\n listFiles,\n tenantId,\n parentId,\n initialView = \"list\",\n autoFetch = true,\n onActivate,\n onMove,\n } = options;\n\n const [state, dispatch] = useReducer(reducer, initialView, initialState);\n\n // Reset selection / context target / drag when the folder changes.\n const lastParentId = useRef<string | null>(parentId);\n useEffect(() => {\n if (lastParentId.current !== parentId) {\n lastParentId.current = parentId;\n dispatch({ type: \"SELECT\", id: null });\n dispatch({ type: \"OPEN_CONTEXT\", file: null });\n dispatch({ type: \"END_DRAG\" });\n }\n }, [parentId]);\n\n const { status, files, error, refetch } = useFileBrowser({\n listFiles,\n tenantId,\n parentId,\n autoFetch,\n });\n\n const setView = useCallback((view: ViewMode) => {\n dispatch({ type: \"SET_VIEW\", view });\n }, []);\n\n const setSelected = useCallback((id: string | null) => {\n dispatch({ type: \"SELECT\", id });\n if (id && onActivate) {\n const file = files.find((f: FileNode) => f.id === id);\n if (file) onActivate(file);\n }\n }, [files, onActivate]);\n\n const openContextMenu = useCallback((file: FileNode | null) => {\n dispatch({ type: \"OPEN_CONTEXT\", file });\n }, []);\n\n const beginDrag = useCallback((id: string) => {\n dispatch({ type: \"BEGIN_DRAG\", id });\n }, []);\n\n const setDropTarget = useCallback((id: string | null) => {\n dispatch({ type: \"SET_DROP_TARGET\", id });\n }, []);\n\n const commitDrop = useCallback((destinationFolderId: string) => {\n const destination = files.find((file) => file.id === destinationFolderId);\n if (\n state.draggingId &&\n destination?.kind === \"folder\" &&\n state.draggingId !== destinationFolderId\n ) {\n void onMove?.({\n itemIds: [state.draggingId],\n destinationFolderId,\n });\n }\n dispatch({ type: \"END_DRAG\" });\n }, [files, onMove, state.draggingId]);\n\n const endDrag = useCallback(() => {\n dispatch({ type: \"END_DRAG\" });\n }, []);\n\n return useMemo(\n () => ({\n status,\n files,\n error,\n refetch,\n view: state.view,\n setView,\n selectedId: state.selectedId,\n setSelected,\n contextTarget: state.contextTarget,\n openContextMenu,\n draggingId: state.draggingId,\n dropTargetId: state.dropTargetId,\n beginDrag,\n setDropTarget,\n commitDrop,\n endDrag,\n }),\n [\n status,\n files,\n error,\n refetch,\n state.view,\n state.selectedId,\n state.contextTarget,\n state.draggingId,\n state.dropTargetId,\n setView,\n setSelected,\n openContextMenu,\n beginDrag,\n setDropTarget,\n commitDrop,\n endDrag,\n ],\n );\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vryzel/file-next-headless",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Headless React hooks for file-next: behavior-only state machines for file browsers, uploaders, file actions, presigned URL resolution, and download progress. No UI, no Tailwind, no Radix. State is local per-hook (useReducer); server actions are dependency-injected.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": "https://github.com/Vryzel/file-next.git",
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public",
|
|
10
|
+
"registry": "https://registry.npmjs.org"
|
|
11
|
+
},
|
|
12
|
+
"sideEffects": false,
|
|
13
|
+
"main": "./dist/index.cjs",
|
|
14
|
+
"module": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js",
|
|
20
|
+
"require": "./dist/index.cjs",
|
|
21
|
+
"default": "./dist/index.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"README.md"
|
|
27
|
+
],
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"react": ">=18.0.0",
|
|
30
|
+
"react-dom": ">=18.0.0",
|
|
31
|
+
"@vryzel/file-next": "^0.4.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@testing-library/jest-dom": "^6.6.3",
|
|
35
|
+
"@testing-library/react": "^16.0.1",
|
|
36
|
+
"@types/react": "^18.3.12",
|
|
37
|
+
"@types/react-dom": "^18.3.1",
|
|
38
|
+
"@vitejs/plugin-react": "^4.3.3",
|
|
39
|
+
"jsdom": "^25.0.1",
|
|
40
|
+
"react": "^18.3.1",
|
|
41
|
+
"react-dom": "^18.3.1",
|
|
42
|
+
"tsup": "^8.3.5",
|
|
43
|
+
"vitest": "^2.1.5",
|
|
44
|
+
"@vryzel/file-next": "0.4.0"
|
|
45
|
+
},
|
|
46
|
+
"keywords": [
|
|
47
|
+
"react",
|
|
48
|
+
"hooks",
|
|
49
|
+
"file-upload",
|
|
50
|
+
"headless",
|
|
51
|
+
"state-machine",
|
|
52
|
+
"useReducer"
|
|
53
|
+
],
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "tsup",
|
|
56
|
+
"dev": "tsup --watch",
|
|
57
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
58
|
+
"test": "vitest",
|
|
59
|
+
"test:run": "vitest run"
|
|
60
|
+
}
|
|
61
|
+
}
|