Interfaces
CustomMetadataEntry
One customMetadata row. id is a stable render key — keys are
user-editable, so they can’t key the rows themselves.
Properties
| Property | Type | Description |
|---|---|---|
error? | string | Validation error ('Key is required' / 'Duplicate key'). |
id | string | - |
key | string | - |
value | string | - |
DeleteSelectionWithConfirmProps
Properties
| Property | Type | Description |
|---|---|---|
body? | ReactNode | Confirm-dialog body. Default lists the selected paths. |
className? | string | Class forwarded to the default trigger button. |
confirmLabel? | string | - |
entries | StorageSelectionEntry[] | What to delete — useStorageSelection().selected (or any {kind, fullPath} rows). Folders delete recursively. |
gate? | Pick<UseStorageRulesGateResult, "verdictFor"> | Rules-aware affordance — pass useStorageRulesGate(storage). When ANY selected entry’s DELETE verdict denies, the trigger disables with the reason (default trigger: data-pyric-denied + data-pyric-denied-reason + title; renderTrigger receives deniedReason). For folder entries the verdict evaluates the folder path itself — an approximation of the recursive walk (descendants matched by {allPaths=**} rules share the verdict). |
impl? | StorageRecursiveDeleteImpl | Folder-walk impl override (default: the listAll-driven one). |
list? | Pick<UseStorageListResult, "insertItem" | "removeItem"> | Optimistic seam from useStorageList. |
onDeleted? | (outcome: StorageDeleteOutcome) => void | Fired after a run with NO failures (e.g. clear the selection + refresh the list). |
onFailed? | (outcome: StorageDeleteOutcome) => void | Fired after a run with failures (the toast already showed). |
renderTrigger? | (props: { deniedReason?: string; disabled: boolean; isRunning: boolean; onClick: () => void; progress: number; }) => ReactNode | Render override for the trigger button. |
storage | FirebaseStorage | The package’s single Storage handle prop. |
title? | string | Confirm-dialog title. Default derives from the entry count. |
DroppedFile
One dropped file, flattened from the drop’s file/folder tree.
Properties
MetadataEditorState
Properties
| Property | Type | Description |
|---|---|---|
draft | MetadataDraft | - |
errorCount | number | - |
initial | MetadataDraft | Snapshot for isDirty / reset — same reference-compare semantics as the document editor’s tree !== initial. |
ObjectBrowserProps
Properties
| Property | Type | Description |
|---|---|---|
className? | string | - |
emptyState? | ReactNode | - |
entries | StorageListEntry[] | The folders-first row model from useStorageList. |
error? | Error | Renders a role="alert" container with the message. Pair with the hook’s typed StorageError for code-driven copy. |
gate? | Pick<UseStorageRulesGateResult, "verdictFor"> | Rules-aware affordances — pass useStorageRulesGate(storage). Rows whose READ verdict denies are stamped data-pyric-denied (with the evaluator’s reason trace on data-pyric-denied-reason). A denied folder row would throw storage/unauthorized on listAll; the stamp warns BEFORE the click. Rows stay clickable — the affordance is advisory and the sandbox enforcement layer remains authoritative. |
onNavigate? | (path: string) => void | Folder row click — fired with the prefix’s fullPath. Wire to usePathState.enter (or setPath). |
onSelect? | (ref: StorageReference) => void | Object row click — fired with the object’s reference. |
renderEntry? | (entry: StorageListEntry) => ReactNode | Row-label slot. Default renders the entry name. The row button, its click wiring, and the data-* states stay with the component — the slot only owns the label content. |
renderRowAction? | (entry: StorageListEntry) => ReactNode | Optional per-row action rendered as a sibling of the navigation/select button. Use this for independent controls such as selection checkboxes. |
rowHeight? | number | (index: number) => number | Estimated row height when virtualizing. Default 36. |
selectedPath? | string | Marks the matching object row data-pyric-selected + aria-selected. |
status? | StorageListStatus | Drives the loading state ('loading' with no rows yet) and the idle short-circuit. Default 'success' for static usage. |
virtualizedHeight? | string | number | Scroll-container height when virtualized. Default '60vh'. |
virtualizeThreshold? | number | Above this row count, the list switches to virtualization via <VirtualList> — listAll has no pagination, so a big prefix arrives as one flat result and virtualization is the only defense. Default 100 (same as <DocumentList>). Infinity disables. |
ObjectInspectorProps
Properties
| Property | Type | Description |
|---|---|---|
children? | ReactNode | Extra content below the preview (metadata editor, delete button, …). |
className? | string | - |
path | string | Object path to inspect. null renders the idle shell — keep the inspector mounted and swap paths as the user selects rows. |
previews? | StoragePreview[] | Consumer previews, tried BEFORE the built-ins (first match wins) — the extension channel of the preview registry. |
renderMetadata? | (metadata: FullMetadata) => ReactNode | Metadata-section slot. Default renders the standard field list. The header, preview, and state wiring stay with the component. |
storage | FirebaseStorage | The package’s sandbox Storage handle. |
PathBreadcrumbProps
Properties
StorageDeleteFailure
One entry’s failure in a bulk run. error is the typed
StorageError (.code e.g. storage/unauthorized).
Properties
StorageDeleteOutcome
Properties
| Property | Type | Description |
|---|---|---|
deleted | string[] | fullPaths of entries fully deleted. |
failed | StorageDeleteFailure[] | - |
StorageDeleteProgress
Properties
| Property | Type | Description |
|---|---|---|
deletedCount | number | Objects deleted so far in this folder walk. |
done | boolean | True for the final emission. |
StorageGateVerdict
Per-path verdict. Upload and delete are evaluated with their granular
Storage request methods so request.method policies and missing incoming
payload semantics remain truthful. write retains its historical upload
meaning; callers use delete for delete affordances.
Properties
StorageListEntry
One row of the merged folder/object model, the prefix→folder
synthesis ported (as an idea, not code) from the emulator UI’s
useStorageFiles: listAll’s prefixes become kind: 'folder'
rows, its items become kind: 'object' rows, folders first.
Properties
| Property | Type | Description |
|---|---|---|
fullPath | string | Bucket-rooted path (no trailing slash, even for folders). |
kind | "object" | "folder" | - |
name | string | Last path segment, display name. |
ref | StorageReference | - |
StoragePreview
One entry in the content-type preview registry — the storage
counterpart of the Firestore field-editor registry, keyed by a
match predicate instead of a type name because content types are
open-ended. First match wins; consumer previews run BEFORE the
built-ins, so overriding image/* is just shipping your own
matcher.
Properties
| Property | Type | Description |
|---|---|---|
id | string | Diagnostic id — also stamped on the preview container as data-pyric-preview="<id>". |
match | (metadata: FullMetadata) => boolean | - |
maxBytes? | number | Skip the preview (and the blob download) for objects larger than this — the inspector renders its data-pyric-preview-too-large fallback instead. undefined = no cap. |
needsBlob? | boolean | Ask the inspector to loadBlob() before rendering. Default false (metadata-only previews render immediately). |
render | (ctx: StoragePreviewContext) => ReactNode | - |
StoragePreviewContext
What a preview’s render receives. blob/blobUrl are only
populated for previews that declared needsBlob.
Properties
| Property | Type |
|---|---|
blob | Blob |
blobUrl | string |
metadata | FullMetadata |
StorageRecursiveDeleteImpl
Recursive folder delete implementation — the same injection seam
as the Firestore half’s RecursiveDeleteImpl. Unlike Firestore
(where tree-walking needs sandbox introspection or a Cloud
Function), the public storage surface CAN walk a prefix, so the
package ships createListAllDeleteImpl as the default;
inject your own for server-driven deletes.
Properties
| Property | Type |
|---|---|
start | (target: StorageReference) => AsyncIterableIterator<StorageDeleteProgress> |
StorageSelectionEntry
What the selection tracks per row — a structural subset of
StorageListEntry, so useStorageList’s entries pass straight
in. The kind decides the delete verb later (object →
deleteObject, folder → recursive).
Properties
UploadDropzoneProps
Properties
| Property | Type | Description |
|---|---|---|
children? | ReactNode | Slot — the dropzone chrome (“Drop files here…”, a browse <input type="file">, anything). The component owns only the drag wiring + data-* states. |
className? | string | - |
disabled? | boolean | Ignore drops + suppress the dragging state. |
disabledReason? | string | Why the dropzone is disabled — stamped on data-disabled-reason (and only while disabled) so the chrome/styling can surface it. The canonical source is the rules gate: disabled={!gate.verdictFor(path).upload} + disabledReason={gate.verdictFor(path).reasons.write.join('; ')}. |
onFiles | (files: DroppedFile[]) => void | Fired once per drop with the flattened file list (folder drops are traversed recursively via webkitGetAsEntry; empty folders yield nothing — wire useObjectUpload.createFolder to your own “new folder” affordance instead). Not fired for empty drops. |
UploadEntry
Explicit-path upload input. path is relative to the hook’s
path option (the destination folder).
Properties
| Property | Type |
|---|---|
data | ArrayBuffer | Uint8Array<ArrayBufferLike> | Blob |
metadata? | SettableMetadata |
path | string |
UploadTask
One file’s upload, TASK-SHAPED for resumable forward-compat: the
byte counters and the onProgress callback are in the type NOW so
a future uploadBytesResumable-backed implementation emits real
intermediate snapshots without a breaking change. Today
(pyric/storage has no resumable uploads — COMPAT) a task
completes in one tick: onProgress fires once at 0 bytes and once
at totalBytes.
Properties
| Property | Type | Description |
|---|---|---|
bytesTransferred | number | - |
error? | Error | Populated on 'error' — a typed StorageError from the sandbox (.code is storage/<code>, e.g. storage/unauthorized for a rules-denied write). |
fullPath | string | Bucket-rooted destination path. |
id | string | Stable id — key task rows on this, not on fullPath (two uploads can target the same path). |
metadata? | FullMetadata | Populated on 'success'. |
status | UploadTaskStatus | - |
totalBytes | number | - |
UseMetadataEditorOptions
Properties
| Property | Type | Description |
|---|---|---|
initial? | SettableMetadata | The metadata being edited — the same shape useStorageObject’s metadata carries. Read once on mount (the editor is a stateful workspace, like the document editor); reset() + remount to re-initialize. |
UseMetadataEditorResult
Properties
| Property | Type | Description |
|---|---|---|
addCustomEntry | (key?: string, value?: string) => void | - |
cacheControl | string | - |
contentType | string | - |
custom | CustomMetadataEntry[] | - |
dispatch | (action: MetadataEditorAction) => void | Raw dispatch — prefer the named helpers. |
errorCount | number | - |
isDirty | boolean | true once any modifying action fired since the last reset/successful save. Reference-compare semantics — manual re-entry of the original values does NOT clear it. |
isSaving | boolean | - |
isValid | boolean | Convenience: errorCount === 0. |
removeCustomEntry | (id: string) => void | - |
reset | () => void | Restore the initial values. Clears isDirty. |
save | () => Promise<FullMetadata> | updateMetadata(ref(storage, path), toPatch()). Errors surface via saveError (typed StorageError), not throws — resolves undefined on failure or when the draft is invalid. On success the draft becomes the new baseline (isDirty clears) and the fresh FullMetadata is returned. |
saveError | Error | - |
setCacheControl | (value: string) => void | - |
setContentType | (value: string) => void | - |
setCustomKey | (id: string, key: string) => void | - |
setCustomValue | (id: string, value: string) => void | - |
toPatch | () => SettableMetadata | Serialize the draft to an updateMetadata patch. Empty contentType/cacheControl become undefined — which LEAVES the previous value (the sandbox doesn’t model null-clears; see pyric/storage’s updateMetadata doc). customMetadata is always included and replaces wholesale, so row removal works. |
UseObjectUploadOptions
Properties
| Property | Type | Description |
|---|---|---|
list? | Pick<UseStorageListResult, "insertItem" | "removeItem"> | Optimistic seam from useStorageList: each upload inserts its path immediately and rolls back via removeItem on failure. Caveat: rolling back an upload that was OVERWRITING an existing object drops that object’s row locally (the seam can’t tell an optimistic row from a listed one) — refresh() restores server truth. |
onComplete? | (task: UploadTask) => void | Fired once per task reaching 'success'. |
onError? | (task: UploadTask) => void | Fired once per task reaching 'error'. |
onProgress? | (task: UploadTask) => void | Task-shaped progress callback (see UploadTask). |
path? | string | Destination folder, bucket-rooted. Default '' (root). Wire to usePathState().path so uploads land in the browsed folder. |
UseObjectUploadResult
Properties
| Property | Type | Description |
|---|---|---|
clearCompleted | () => void | Drop settled (success/error) tasks from tasks. |
createFolder | (name: string) => Promise<void> | Create an empty folder under the hook’s path: writes the GCS placeholder convention — a zero-byte object named <path>/ (trailing slash). listAll hides the placeholder from items at every level (it only surfaces as a prefix), so the folder appears in the browser with no phantom file inside. ref() normalizes the trailing slash away, so the placeholder is written through a structural value-object reference the sandbox accepts. Throws the underlying error after rolling back the optimistic prefix insert. ALTERNATIVE: when the store must stay free of placeholder objects (Pyric Studio’s choice), use the client-side pending-prefix mechanism instead — see pendingPrefixes.ts for the reducer and the recorded tradeoff. |
isUploading | boolean | true while any task is 'running'. |
tasks | UploadTask[] | Every task started by this hook instance, oldest first. |
upload | (input: UploadInput | UploadInput[]) => Promise<UploadTask[]> | Upload one or many files. Tasks run concurrently; the promise resolves with the settled tasks once ALL finish and never rejects — per-file failures land on task.error (and onError), so one bad file doesn’t mask the others. |
UsePathStateOptions
Properties
UsePathStateResult
Properties
UseStorageDeleteOptions
Properties
| Property | Type | Description |
|---|---|---|
impl? | StorageRecursiveDeleteImpl | Folder-walk implementation. Default createListAllDeleteImpl. |
list? | Pick<UseStorageListResult, "insertItem" | "removeItem"> | Optimistic seam from useStorageList: entries vanish from the local list immediately and roll back (object → item, folder → trailing-slash prefix insert) on failure. |
UseStorageDeleteResult
Properties
| Property | Type | Description |
|---|---|---|
deleteEntries | (entries: StorageSelectionEntry[]) => Promise<StorageDeleteOutcome> | Delete a mixed object/folder selection (objects via deleteObject, folders via the recursive impl), sequentially in selection order. Resolves with the outcome and never rejects — per-entry failures land in outcome.failed (and error keeps the first one for simple renders). |
error | Error | First failure of the current/last run. Cleared on the next call. |
isRunning | boolean | - |
progress | number | Objects deleted in the current/last run (folder walks included). |
UseStorageListResult
Properties
| Property | Type | Description |
|---|---|---|
entries | StorageListEntry[] | Folders-first merged row model. Derived from prefixes + items. |
error | Error | StorageError (with a typed storage/<code> on .code) from the sandbox. A denied list is error.code === 'storage/unauthorized' (ST-B2). |
insertItem | (fullPath: string) => void | Optimistic seam (consumed by M3 upload / M6 bulk ops, exposed now so those hooks layer on without reshaping this one). Inserts fullPath into the local list immediately, applying the same prefix→folder synthesis listAll would: a direct child becomes an item, a deeper descendant surfaces as its first-segment folder. A trailing slash declares a folder (the GCS placeholder convention useObjectUpload.createFolder writes): a direct trailing-slash child inserts a prefix, not an item. No-op for paths outside the listed path, duplicates, or when status is 'idle'. What each call ACTUALLY inserted is recorded (keyed by the given fullPath) so removeItem can reverse it precisely. Rollback = removeItem or refresh. |
items | StorageReference[] | Direct child objects under path. Sorted by fullPath. |
prefixes | StorageReference[] | Synthetic folder prefixes under path. Sorted by fullPath. |
refresh | () => void | Re-run listAll for the current path. |
removeItem | (fullPath: string) => void | Optimistic counterpart. When fullPath was previously given to insertItem, this reverses EXACTLY what that call inserted: a deep upload that synthesized a first-segment folder row removes that folder row, and an insert that was a no-op (the row already existed — e.g. a real, listed folder) removes NOTHING, so a failed upload can never delete server-truth rows. For paths never seen by insertItem it removes the matching item/folder row directly (the optimistic-delete use). Rollback = refresh. |
status | StorageListStatus | 'idle' only when storage is null/undefined. |
UseStorageObjectResult
Properties
| Property | Type | Description |
|---|---|---|
blob | Blob | - |
blobError | Error | - |
blobStatus | StorageObjectStatus | Blob read state. Stays 'idle' until loadBlob(), the blob is LAZY; metadata alone never downloads bytes. |
blobUrl | string | URL.createObjectURL handle for the loaded blob, used as the local preview channel. Revoked automatically when the blob is replaced, the path changes, or the hook unmounts. |
error | Error | Typed StorageError (storage/object-not-found, storage/unauthorized, …). |
loadBlob | () => void | Fetch the bytes via getBlob. Subsequent calls re-fetch. |
metadata | FullMetadata | - |
refresh | () => void | Re-read the metadata (also resets the blob, the object may have been overwritten). |
status | StorageObjectStatus | Metadata read state. 'idle' when storage or path is null. |
UseStorageRulesGateOptions
Properties
| Property | Type | Description |
|---|---|---|
identity? | StorageAuth | Identity override. null is anonymous; OMIT the field to use the handle’s own identity (the sandbox context’s auth). |
paths? | string | readonly string[] | Path (or paths) to pre-evaluate into verdicts, keyed by the normalized path. Ad-hoc paths (e.g. browser rows) go through verdictFor instead — the two are the same evaluation. |
rules? | string | StorageRules | Explicit rules source — raw rules text (parsed here; a malformed string surfaces as status: 'error') or a pre-parsed StorageRules handle. Overrides the sandbox’s deployed ruleset when both exist. |
writeResource? | { contentType?: string; size: number; } | The about-to-write payload bound to request.resource for the CREATE evaluation — pass { size, contentType } when gating a specific upload so size/contentType-conditioned rules evaluate truthfully. When omitted, request.resource is unset, which is absent, upload/write remain a conservative deny for payload-dependent policies. Delete is evaluated independently and never receives this value. |
writeResource.contentType? | string | - |
writeResource.size | number | - |
UseStorageRulesGateResult
Properties
| Property | Type | Description |
|---|---|---|
advisory | boolean | Always false: pyric/storage handles are sandbox mirrors. |
error | Error | Rules-resolution failure (e.g. a malformed rules string). |
identity | StorageAuth | The identity the verdicts evaluate under. |
source | StorageRulesSource | Where the active ruleset came from. |
status | StorageRulesGateStatus | 'idle' only when storage is null/undefined. |
verdictFor | (path: string) => StorageGateVerdict | Evaluate an arbitrary path under the current ruleset + identity. Pure and synchronous once status is 'ready'; before that (and whenever no rules are reachable) it returns the allow-all verdict — the gate FAILS OPEN, because affordances are advisory and the real enforcement (sandbox throw / server denial) stays authoritative. |
verdicts | Record<string, StorageGateVerdict> | Pre-evaluated verdicts for options.paths, keyed by normalized path. |
UseStorageSelectionResult
Properties
| Property | Type | Description |
|---|---|---|
clear | () => void | - |
deselect | (fullPath: string) => void | - |
isSelected | (fullPath: string) => boolean | - |
select | (entry: StorageSelectionEntry) => void | - |
selectAll | (entries: StorageSelectionEntry[]) => void | Replace the selection (e.g. a “select all” over list.entries). |
selected | StorageSelectionEntry[] | Selected entries in selection order. |
size | number | - |
toggle | (entry: StorageSelectionEntry) => void | Add/remove — the checkbox verb. |
Type Aliases
MetadataEditorAction
type MetadataEditorAction =
| {
type: "setContentType";
value: string;
}
| {
type: "setCacheControl";
value: string;
}
| {
id: string;
key: string;
type: "setCustomKey";
}
| {
id: string;
type: "setCustomValue";
value: string;
}
| {
key?: string;
type: "addCustomEntry";
value?: string;
}
| {
id: string;
type: "removeCustomEntry";
}
| {
type: "reset";
}
| {
type: "commit";
};
Type Declaration
{
type: "setContentType";
value: string;
}
type
type: "setContentType";
value
value: string;
{
type: "setCacheControl";
value: string;
}
type
type: "setCacheControl";
value
value: string;
{
id: string;
key: string;
type: "setCustomKey";
}
id
id: string;
key
key: string;
type
type: "setCustomKey";
{
id: string;
type: "setCustomValue";
value: string;
}
id
id: string;
type
type: "setCustomValue";
value
value: string;
{
key?: string;
type: "addCustomEntry";
value?: string;
}
key?
optional key: string;
type
type: "addCustomEntry";
value?
optional value: string;
{
id: string;
type: "removeCustomEntry";
}
id
id: string;
type
type: "removeCustomEntry";
{
type: "reset";
}
type
type: "reset";
{
type: "commit";
}
type
type: "commit";
Internal — a successful save makes the draft the new baseline.
PendingPrefixAction
type PendingPrefixAction =
| {
path: string;
type: "create";
}
| {
path: string;
type: "materialize";
}
| {
path: string;
type: "discard";
}
| {
type: "clear";
};
Type Declaration
{
path: string;
type: "create";
}
path
path: string;
type
type: "create";
Create a folder at path (absolute, bucket-rooted; nested paths
allowed) — adds the full ancestor chain.
{
path: string;
type: "materialize";
}
path
path: string;
type
type: "materialize";
An object now exists directly under path: drop path and its
ancestors from pending (they are real prefixes now).
{
path: string;
type: "discard";
}
path
path: string;
type
type: "discard";
Remove a session-only folder and every pending descendant beneath it.
{
type: "clear";
}
type
type: "clear";
PendingPrefixState
type PendingPrefixState = readonly string[];
Sorted, deduped, normalized pending prefix paths.
StorageApi
type StorageApi = Pick<pyric-storage-reference-api,
| "ref"
| "listAll"
| "getMetadata"
| "getBlob"
| "uploadBytes"
| "uploadBytesResumable"
| "deleteObject">;
The modular Storage fns the browse/inspect hooks call, as an INJECTABLE
bundle (same pattern as @pyric/ui’s FirestoreApi / AuthApi).
Default = in-process pyric/storage, so existing consumers are unchanged.
Pyric Studio served mode injects the SharedWorker client bundle so the Storage
surface browses the live worker object store. These ops are already async, so
no sync/async wrinkle (unlike auth listUsers); the worker handles/refs are
runtime-compatible at the surface the hooks use (.fullPath / .name).
uploadBytes rides the same seam so useObjectUpload follows the injected
backend: in-process writes are uncapped; the worker client’s uploadBytes
(base64 storage.putBytes over the MessagePort) enforces an 8 MiB payload
cap on both ends — an over-cap upload fails that file’s task with the typed
storage/... too-large error and the rest of the batch proceeds.
NOTE the rules gate (useStorageRulesGate) is NOT here: it reads in-process
rules internals and no-ops on a handle without them (worker handles), which is
the correct degrade (the worker enforces read rules on listAll server-side).
StorageListStatus
type StorageListStatus = "idle" | "loading" | "success" | "error";
StorageObjectStatus
type StorageObjectStatus = "idle" | "loading" | "success" | "error";
StorageRulesGateStatus
type StorageRulesGateStatus = "idle" | "loading" | "ready" | "error";
StorageRulesSource
type StorageRulesSource = "option" | "sandbox" | "none";
Where the active ruleset came from:
'option'— the explicitrulesoption (string or pre-parsed).'sandbox'— the ruleset deployed on the sandbox handle (getStorageSandbox(ctx, { rules })), read off the handle’sStorageService.'none'— no rules reachable. Every verdict allows (open-by-default, the same semanticspyric/storage’s enforcement layer applies when no rules are configured).
UploadInput
type UploadInput = File | UploadEntry;
upload() accepts plain Files (destination = the file’s
webkitRelativePath when present — folder drops keep their
structure — else its name) or explicit UploadEntrys.
UploadTaskStatus
type UploadTaskStatus = "running" | "success" | "error";
Variables
defaultStoragePreviews
const defaultStoragePreviews: ReadonlyArray<StoragePreview>;
The section 3 defaults: image, text/json; everything else is
metadata-only (the inspector’s data-pyric-preview-none state).
imagePreview
const imagePreview: StoragePreview;
image/* → blob-URL <img>.
initialPendingPrefixes
const initialPendingPrefixes: PendingPrefixState;
TEXT_PREVIEW_MAX_BYTES
const TEXT_PREVIEW_MAX_BYTES: number;
256KB — the section 3 default cap for the text-family preview.
textPreview
const textPreview: StoragePreview;
text/* + application/json → text panel, 256KB cap (bigger
objects fall through to the too-large fallback). JSON is
pretty-printed when parseable.
Functions
createListAllDeleteImpl()
function createListAllDeleteImpl(api?: Pick<StorageApi, "listAll" | "deleteObject">): StorageRecursiveDeleteImpl;
The default, listAll-driven impl: walks the prefix tree,
deleteObjects every item (yielding progress per object), then
sweeps each visited folder’s <path>/ placeholder so emptied
create-folder folders disappear too (listAll hides placeholders,
so the walk alone would leave ghost folders). Placeholder sweeps
are best-effort — deletedCount counts listed objects only.
Parameters
| Parameter | Type |
|---|---|
api? | Pick<StorageApi, "listAll" | "deleteObject"> |
Returns
DeleteSelectionWithConfirm()
function DeleteSelectionWithConfirm(__namedParameters: DeleteSelectionWithConfirmProps): Element;
Bulk delete behind the confirm-dialog primitive, with toasts on
outcome — wires useConfirm + useStorageDelete + useToast the
way <DeleteWithConfirm> wires the Firestore trio. Requires
<ConfirmProvider> AND <ToastProvider> ancestors.
Outcome toasts: all-success → one success toast with the count;
any failure → an error toast listing each failed path with its
typed StorageError.code.
The default trigger styles via [data-pyric-ui="delete-selection"]
(+ [data-pyric-destructive], [data-pyric-running],
[data-pyric-denied] with the reason on
data-pyric-denied-reason/title); it disables while running,
when entries is empty, or when the rules gate denies the
selection.
Parameters
| Parameter | Type |
|---|---|
__namedParameters | DeleteSelectionWithConfirmProps |
Returns
Element
expandPathChain()
function expandPathChain(path: string): string[];
'a/b/c' → ['a', 'a/b', 'a/b/c']; '' → [].
Parameters
| Parameter | Type |
|---|---|
path | string |
Returns
string[]
folderInputError()
function folderInputError(input: string): string;
Validate a create-folder input (relative to the current folder;
nested a/b/c allowed — VS Code semantics). Returns an error
message or null when valid. Normalization tolerates stray/repeat
slashes; ./.. segments are rejected (GCS object names have no
dot-segment semantics — accepting them would create unreachable
names).
Parameters
| Parameter | Type |
|---|---|
input | string |
Returns
string
initMetadataEditorState()
function initMetadataEditorState(initial: SettableMetadata): MetadataEditorState;
Build the edit state from the metadata a getMetadata /
useStorageObject read returned.
Parameters
| Parameter | Type |
|---|---|
initial | SettableMetadata |
Returns
isPendingPrefix()
function isPendingPrefix(state: PendingPrefixState, path: string): boolean;
Whether path itself is a pending (session-only) folder.
Parameters
| Parameter | Type |
|---|---|
state | PendingPrefixState |
path | string |
Returns
boolean
metadataEditorReducer()
function metadataEditorReducer(state: MetadataEditorState, action: MetadataEditorAction): MetadataEditorState;
Pure reducer — exported (with initMetadataEditorState) so the edit state is testable without React, mirroring the document editor’s reducer/hook split.
Parameters
| Parameter | Type |
|---|---|
state | MetadataEditorState |
action | MetadataEditorAction |
Returns
normalizeStoragePath()
function normalizeStoragePath(path: string): string;
Strip leading/trailing slashes and collapse repeats — mirrors
pyric/storage’s reference normalization so usePathState and
useStorageList always agree on what a path is.
Parameters
| Parameter | Type |
|---|---|
path | string |
Returns
string
ObjectBrowser()
function ObjectBrowser(__namedParameters: ObjectBrowserProps): Element;
Headless storage browser shell — renders useStorageList’s merged
folder/object rows. Folder rows navigate (onNavigate with the
prefix path), object rows select (onSelect with the ref). Below
virtualizeThreshold renders a plain <ul>; above it, composes
the package’s <VirtualList>.
Ships no visual styling. Consumers style via:
[data-pyric-ui="object-browser"]— the root (stampsdata-size)…[data-pyric-loading]/[data-pyric-empty]/[data-pyric-error]…[data-pyric-virtualized]— virtualized mode[data-pyric-object-browser-items]— the<ul>in plain mode[data-pyric-storage-entry]— each row[data-pyric-storage-entry][data-pyric-entry-kind="folder"|"object"][data-pyric-storage-entry][data-pyric-entry-path="docs/a.txt"][data-pyric-storage-entry][data-pyric-denied]— read-denied row (rules gate; reason ondata-pyric-denied-reason)[data-pyric-entry-select]— the row button[data-pyric-entry-select][data-pyric-selected]— the selected object[data-pyric-storage-action]— optional sibling row action
Parameters
| Parameter | Type |
|---|---|
__namedParameters | ObjectBrowserProps |
Returns
Element
ObjectInspector()
function ObjectInspector(__namedParameters: ObjectInspectorProps): Element;
Headless inspector for one storage object: metadata + a
content-type-driven preview. Previews come from the registry
(image/* and text/* + application/json built in; extend via
previews). Blob bytes load lazily and ONLY when the matched
preview asks (needsBlob) and the object is within the preview’s
maxBytes cap; the blob URL is revoked on unmount/path change
(see useStorageObject).
Ships no visual styling. Consumers style via:
[data-pyric-ui="object-inspector"]— root (stampsdata-size)…[data-pyric-idle]/[data-pyric-loading]/[data-pyric-error][data-pyric-object-name]/[data-pyric-object-metadata][data-pyric-metadata-field="<field>"]— each metadata row[data-pyric-object-preview]— the preview container, stampingdata-pyric-preview="<id>"for the matched registry entry…[data-pyric-preview-loading]— blob in flight…[data-pyric-preview-error]— blob load failed…[data-pyric-preview-too-large]— over the preview’s cap…[data-pyric-preview-none]— no registry match (metadata-only)
Parameters
| Parameter | Type |
|---|---|
__namedParameters | ObjectInspectorProps |
Returns
Element
parseCopyCounter()
function parseCopyCounter(stem: string): {
base: string;
counter: number;
};
Trailing (n) counter: photo (3) → base photo, counter 3.
counter: null when the stem carries none. A counter beyond
Number.MAX_SAFE_INTEGER is treated as plain text (no counter):
incrementing it would be lossy — n + 1 === n in float land, which
turns resolveCollision’s probe loop into a hang — and the
candidate would render in scientific notation anyway.
Parameters
| Parameter | Type |
|---|---|
stem | string |
Returns
{
base: string;
counter: number;
}
base
base: string;
counter
counter: number;
PathBreadcrumb()
function PathBreadcrumb(__namedParameters: PathBreadcrumbProps): Element;
Headless breadcrumb for storage paths. Every crumb (including the
current one) is a real <button> — clicking the current crumb is
a cheap “refresh this level” affordance for consumers that wire
onNavigate to a path-keyed loader.
Ships no visual styling. Consumers style via:
[data-pyric-ui="path-breadcrumb"]— the<nav>root[data-pyric-breadcrumb-item]— each<li>[data-pyric-breadcrumb-link]— each crumb button[data-pyric-breadcrumb-link][data-pyric-current]— the current level[data-pyric-breadcrumb-root]— the root crumb’s button[data-pyric-breadcrumb-separator]— the separators
Parameters
| Parameter | Type |
|---|---|
__namedParameters | PathBreadcrumbProps |
Returns
Element
pendingChildFolders()
function pendingChildFolders(state: PendingPrefixState, parentPath: string): string[];
Direct-child folder NAMES pending under parentPath ('' = root),
sorted. The chain expansion guarantees every level is present, so a
simple parent match is exact.
Parameters
| Parameter | Type |
|---|---|
state | PendingPrefixState |
parentPath | string |
Returns
string[]
pendingPrefixReducer()
function pendingPrefixReducer(state: PendingPrefixState, action: PendingPrefixAction): PendingPrefixState;
Parameters
| Parameter | Type |
|---|---|
state | PendingPrefixState |
action | PendingPrefixAction |
Returns
planBatchNames()
function planBatchNames(relativePaths: readonly string[], taken: ReadonlySet<string>): string[];
Resolve a whole drop/pick batch against the destination folder’s
existing names, with OS drop semantics: collisions are detected and
renamed at the batch’s TOP LEVEL only (the names the OS drop
“creates” in the destination — a plain file’s name, or a dropped
folder’s root segment). Files inside a dropped folder ride their
folder’s rename and keep their inner structure untouched — exactly
like dropping photos/ next to an existing photos/ yields
photos (1)/… with the contents intact.
Within one batch:
- all paths sharing a top-level FOLDER segment share its resolution (one dropped folder = one rename), and
- top-level FILES resolve individually in order, each claiming its resolved name, so two same-named files in one batch get successive counters.
Only the destination’s DIRECT children can be checked — that is all
the drop target (one listAll level) knows. Deeper paths follow GCS
overwrite semantics, which the folder-level rename already shields
in practice (a colliding folder is renamed wholesale).
Returns resolved paths in input order.
Parameters
| Parameter | Type |
|---|---|
relativePaths | readonly string[] |
taken | ReadonlySet<string> |
Returns
string[]
resolveCollision()
function resolveCollision(name: string, taken: ReadonlySet<string>): string;
Resolve one name against a set of taken sibling names. Returns the
name unchanged when free; otherwise the first (n) candidate that
is free, per the module rule above.
Parameters
| Parameter | Type |
|---|---|
name | string |
taken | ReadonlySet<string> |
Returns
string
selectStoragePreview()
function selectStoragePreview(metadata: FullMetadata, consumerPreviews: readonly StoragePreview[]): StoragePreview;
Pick the preview for metadata: consumer previews first (override
channel), then the built-ins, first match wins. undefined
means metadata-only.
Parameters
| Parameter | Type |
|---|---|
metadata | FullMetadata |
consumerPreviews | readonly StoragePreview[] |
Returns
splitStorageName()
function splitStorageName(name: string): {
ext: string;
stem: string;
};
name split as the rule defines: ext includes the leading dot,
or is '' when the name has no extension (dotfiles, trailing dots,
extensionless names).
Parameters
| Parameter | Type |
|---|---|
name | string |
Returns
{
ext: string;
stem: string;
}
ext
ext: string;
stem
stem: string;
StorageApiProvider()
function StorageApiProvider(__namedParameters: {
children: ReactNode;
value: StorageApi;
}): FunctionComponentElement<ProviderProps<StorageApi>>;
Provide a Storage API bundle to the subtree (Studio’s worker client).
Parameters
| Parameter | Type |
|---|---|
__namedParameters | { children: ReactNode; value: StorageApi; } |
__namedParameters.children | ReactNode |
__namedParameters.value | StorageApi |
Returns
FunctionComponentElement<ProviderProps<StorageApi>>
UploadDropzone()
function UploadDropzone(__namedParameters: UploadDropzoneProps): Element;
Headless drop target for file + folder uploads. Slot-based: the
children render the chrome; the component owns drag wiring and
stamps data-dragging while a drag hovers (a counter tracks
enter/leave pairs so crossing child elements doesn’t flicker).
Ships no visual styling. Consumers style via:
[data-pyric-ui="upload-dropzone"]— the root…[data-dragging]— a drag is hovering…[data-disabled]…[data-disabled-reason="…"]— why (e.g. a denied write verdict)
Parameters
| Parameter | Type |
|---|---|
__namedParameters | UploadDropzoneProps |
Returns
Element
useMetadataEditor()
function useMetadataEditor(
storage: FirebaseStorage,
path: string,
options?: UseMetadataEditorOptions): UseMetadataEditorResult;
Headless metadata editor — the useDocumentEditor reducer pattern
over updateMetadata: a pure reducer owns the draft (contentType,
cacheControl, customMetadata k/v rows with stable ids +
empty/duplicate-key validation); the hook adds named dispatch
helpers and the save half.
Parameters
| Parameter | Type |
|---|---|
storage | FirebaseStorage |
path | string |
options? | UseMetadataEditorOptions |
Returns
useObjectUpload()
function useObjectUpload(storage: FirebaseStorage, options?: UseObjectUploadOptions): UseObjectUploadResult;
Multi-file upload over the package’s single Storage handle prop.
Headless: returns task state; render it however you like (the
<UploadDropzone> component is one producer of upload() calls).
Optimistic-with-rollback: with the list seam wired, each upload’s
row appears in useStorageList immediately and disappears again if
the write fails (typed StorageError on task.error).
Parameters
| Parameter | Type |
|---|---|
storage | FirebaseStorage |
options? | UseObjectUploadOptions |
Returns
usePathState()
function usePathState(options?: UsePathStateOptions): UsePathStateResult;
Path navigation state for the storage browser. Controlled when
path is provided (the owner re-renders with the next value),
uncontrolled otherwise — standard React value/defaultValue
semantics. All emitted paths are normalized (normalizeStoragePath).
Parameters
| Parameter | Type |
|---|---|
options? | UsePathStateOptions |
Returns
useStorageApi()
function useStorageApi(): StorageApi;
Read the active Storage API bundle (defaults to in-process pyric/storage).
Returns
useStorageDelete()
function useStorageDelete(storage: FirebaseStorage, options?: UseStorageDeleteOptions): UseStorageDeleteResult;
Drive bulk + recursive deletes from a React component — the
storage counterpart of useRecursiveDelete (same progress /
isRunning / error shape, same stale-run generation token), bulk
because storage selections are flat multi-row affairs.
Parameters
| Parameter | Type |
|---|---|
storage | FirebaseStorage |
options? | UseStorageDeleteOptions |
Returns
useStorageList()
function useStorageList(storage: FirebaseStorage, path: string): UseStorageListResult;
List the objects + synthetic folders directly under path :
listAll over the package’s sandbox Storage handle. Read-via-get,
not realtime: the list updates on refresh, path change, or the
optimistic seam. Pass '' (or the result of usePathState) for
the bucket root.
listAll has no pagination; a very large prefix arrives as one flat
result (virtualize the rendering, which <ObjectBrowser> does).
Parameters
| Parameter | Type |
|---|---|
storage | FirebaseStorage |
path | string |
Returns
useStorageObject()
function useStorageObject(storage: FirebaseStorage, path: string): UseStorageObjectResult;
One object’s metadata + lazily-loaded bytes, the data source for
<ObjectInspector>. Read-via-get like the rest of the storage
half: updates on refresh, path change, or loadBlob.
Parameters
| Parameter | Type |
|---|---|
storage | FirebaseStorage |
path | string |
Returns
useStorageRulesGate()
function useStorageRulesGate(storage: FirebaseStorage, options?: UseStorageRulesGateOptions): UseStorageRulesGateResult;
Pre-flight rules evaluation — the M7 differentiator. Evaluates the
current identity against paths BEFORE the click, so components can
annotate denied affordances (data-pyric-denied, disabled-with-
reason) instead of letting the user discover a denial via a thrown
storage/unauthorized.
Rules discovery: a sandbox handle carries its deployed ruleset
(getStorageSandbox(ctx, { rules }) parses it into the handle’s
StorageService) — the hook reads it through the handle’s target,
so sandbox callers pass nothing. Identity likewise defaults to the
handle’s SandboxContext.auth. An explicit rules or identity
option overrides the handle when evaluating a what-if scenario.
Evaluation contract (mirrors pyric/storage’s own enforcement):
resource (the existing object) is bound as null — the gate
pre-evaluates without fetching per-path metadata, matching how the
sandbox enforces listAll. Rules conditioned on existing-object
state (resource.*) evaluate as if the object doesn’t exist; the
common identity/path/payload-shaped rules evaluate exactly.
Parameters
| Parameter | Type |
|---|---|
storage | FirebaseStorage |
options? | UseStorageRulesGateOptions |
Returns
useStorageSelection()
function useStorageSelection(): UseStorageSelectionResult;
Multi-select state over storage rows, keyed by fullPath.
Deliberately dumb: it doesn’t watch the list, so clear it on path
change (or after a bulk op via the delete hook’s outcome) — the
consumer owns that policy.