import { type CollectionReference, type DocumentData } from 'firebase/firestore'; import { type FirebaseStorage } from 'firebase/storage'; import { type StorageData } from './storage-helpers.js'; export { storagePathFor, extFromPath, contentByteSize, computeContentHash, isTextMimeType, mimeTypeFromExt, } from './storage-helpers.js'; interface BlobLocation { storage: FirebaseStorage; orgId: string; fileId: string; ext: string; } export interface UploadBlobInput extends BlobLocation { data: StorageData; mimeType?: string; } interface ScopedFolderQuery { filesRef: CollectionReference; scope: string; teamId?: string | null | undefined; userId?: string | undefined; } export interface EnsureFolderChainInput extends ScopedFolderQuery { docPath: string; now?: string | undefined; } export interface PushFolderInput extends ScopedFolderQuery { docPath: string; now?: string | undefined; } export type PushFolderOutcome = { action: 'skipped'; reason: 'shared-scope'; } | { action: 'ensured'; folderId: string | null; }; export interface DeleteFolderInput { filesRef: CollectionReference; storage: FirebaseStorage; orgId: string; scope: string; teamId?: string | null | undefined; userId?: string | undefined; docPath: string; } export type DeleteFolderOutcome = { action: 'skipped'; reason: 'shared-scope'; } | { action: 'not-found'; } | { action: 'deleted'; folderId: string | null; childCount: number; }; export interface PushFileInput { filesRef: CollectionReference; storage: FirebaseStorage; orgId: string; userId: string; scope: string; teamId?: string | null | undefined; docPath: string; data: StorageData; mimeType?: string | undefined; now?: string | undefined; localModifiedAt?: string | null | undefined; } export interface PushedFileData { type: 'file'; name: string; parentId: string | null; path: string; createdAt: string; updatedAt: string; scope: string; teamId: string | null; ownerId: string | null; sharedWith: string[]; storagePath: string; contentHash: string; size: number; mimeType: string; textAuthority: 'blob'; } export type PushFileToCloudOutcome = { action: 'skipped'; reason: 'shared-scope'; } | { action: 'noop'; fileId: string; } | { action: 'skipped-stale'; fileId: string; } | { action: 'created'; fileId: string; fileData: PushedFileData; storageError: unknown | null; } | { action: 'updated'; fileId: string; storageError: unknown | null; }; export interface DeleteFileInput { filesRef: CollectionReference; storage: FirebaseStorage; orgId: string; userId: string; scope: string; teamId?: string | null | undefined; docPath: string; } export type DeleteFileOutcome = { action: 'skipped'; reason: 'shared-scope'; } | { action: 'not-found'; fileId?: string; } | { action: 'deleted'; fileId: string; }; export declare function uploadBlob({ storage, orgId, fileId, ext, data, mimeType }: UploadBlobInput): Promise; export declare function deleteBlob({ storage, orgId, fileId, ext }: BlobLocation): Promise; /** * Read a blob from Cloud Storage as raw bytes. Throws on any failure * (missing blob, permission error, network). */ export declare function readBlob({ storage, orgId, fileId, ext }: BlobLocation): Promise; /** * Read a blob and decode as UTF-8 text. Convenience for text/markdown * callers. */ export declare function readBlobAsText(input: BlobLocation): Promise; /** * Ensure folder docs exist for every segment of `docPath` and return * the id of the leaf folder. Idempotent: existing folder docs are * reused; missing ones are created; folder docs missing the `path` * field are backfilled. * * `docPath` is the FULL slash-joined path of the folder itself (NOT a * file path). Pass `'A/B'` to ensure both `A` and `A/B` exist; the * returned id is for `A/B`. * * Returns null if `docPath` is empty (root). */ export declare function ensureFolderChainInCloud({ filesRef, scope, teamId, userId, docPath, now, }: EnsureFolderChainInput): Promise; /** * Find or create the folder doc at `docPath`. Convenience wrapper * around ensureFolderChainInCloud for callers that want to upsert a * folder explicitly (e.g., chokidar `addDir` events). * * Returns: * { action: 'skipped', reason: 'shared-scope' } * { action: 'ensured', folderId } */ export declare function pushFolderToCloud({ filesRef, scope, teamId, userId, docPath, now, }: PushFolderInput): Promise; /** * Delete the folder doc at `docPath` AND every descendant (files and * sub-folders) in the same scope. Cascade is authoritative: it does * not rely on per-child `unlink` events to clean up children first. * * Pre-DOU-223 this function deleted only the folder doc, on the * (wrong) assumption that chokidar would emit `unlink` for every * child before the `unlinkDir` for the folder. macOS FSEvents * coalescing and burst-event ordering broke that assumption, * leaving orphan file docs that reconstituted the folder on the web * via `getFoldersAndFiles` (which derives folder names from paths). * The cascade closes the gap; if children's `unlink` events also * fire in time, the cascade is a no-op for them. * * Storage blobs for descendant files are deleted before their * Firestore docs so the Storage rule (which reads the doc to * authorize) still passes. * * Returns: * { action: 'skipped', reason: 'shared-scope' } * { action: 'not-found' } * { action: 'deleted', folderId, childCount } */ export declare function deleteFolderFromCloud({ filesRef, storage, orgId, scope, teamId, userId, docPath, }: DeleteFolderInput): Promise; /** * Push a file's content to the cloud: ensure the parent folder chain * exists, upsert the Firestore metadata doc (with canonical fields * including `type: 'file'`, `name`, `parentId`), and upload the blob. * * Args: * - data: the file's content. String, Buffer, or Uint8Array. * - mimeType: the file's mime type. Stored on the metadata doc and * used as the blob's Content-Type. Optional; if absent * for an existing file the existing mime is preserved, * and for new files defaults to 'text/markdown' to keep * legacy callers working. * * - localModifiedAt: ISO timestamp of the local file's last * modification (mtime). When provided, a push that would * overwrite a server copy whose `updatedAt` is strictly * newer is skipped instead, so an older local file cannot * clobber a newer remote edit (last-write-wins, symmetric * with the firestore -> local pull guard). Omit it to * force the push regardless of timestamps (legacy callers). * * Returns one of: * { action: 'skipped', reason: 'shared-scope' } * { action: 'noop', fileId } * { action: 'skipped-stale', fileId } // server copy is newer * { action: 'created', fileId, fileData, storageError? } * { action: 'updated', fileId, storageError? } */ export declare function pushFileToCloud({ filesRef, storage, orgId, userId, scope, teamId, docPath, data, mimeType, now, localModifiedAt, }: PushFileInput): Promise; /** * Delete a file from Firestore and the corresponding Storage blob. * * Returns one of: * { action: 'skipped', reason: 'shared-scope' } * { action: 'not-found' } * { action: 'deleted', fileId } * * Blob deletion tolerates `storage/object-not-found`. */ export declare function deleteFileFromCloud({ filesRef, storage, orgId, userId, scope, teamId, docPath, }: DeleteFileInput): Promise; //# sourceMappingURL=files.d.ts.map