Skip to content

File records and storage

@alneas/data-engine/integrations/files owns FileProvider, FileNode, and DataAccessFileProvider. React views, selection, local browser persistence and provider-specific SDK setup are outside this entry. It has no SDK runtime imports. Install the optional peers @alneas/data-access@0.1.0 and @alneas/universal-storage@0.1.0 when composing this integration. Engine core works without either peer; this integration’s declarations reference their types.

import { DataAccessFileProvider } from '@alneas/data-engine/integrations/files';
const files = new DataAccessFileProvider({
dataAccess, storage, collection: 'files', physicalPrefix: 'vault/',
searchMode: 'portable', deleteMode: 'metadata-only',
});
const node = await files.uploadFile('inbox/', new Blob(['hello']), 'report.txt');
await files.updateMetadata(node.id, { tags: 'finance,approved' });
const matches = await files.searchNodes({ query: 'REPORT', tags: ['finance'] });

dataAccess and storage are application-supplied adapter instances; the example assumes they are configured and authorized. No collection or credentials are created automatically. Provide columns id, folder, filename, physical_key, size, content_type, tags, updated_at, kind. Folders omit size/content_type; allow these columns to be nullable and tags optional. IDs must be application-unique; the default is Web Crypto randomUUID. Custom createId returns a fresh ID matching [A-Za-z0-9_-]{1,128}. Use an exclusively owned key namespace. Actual keys returned by storage are saved; IDs and display names must never be substituted for those keys.

Rename/move update only metadata, leaving the physical key unchanged. Folder rename/move/delete affect only the folder record, never descendants. Applications must implement recursive operations explicitly. updateMetadata persists only tags; it does not promise arbitrary columns or storage object metadata updates. listNodes(path) uses {folder:path} with ascending kind/filename sort. It has no pagination/cursor support and returns hasMore:false; the adapter must return the complete folder listing, or the application must implement a paged provider.

Default metadata-only deletion preserves existing behavior: delete DB rows only, sequentially, stopping at the first error. Objects remain and can incur storage costs. Choose storage-and-metadata explicitly to delete each file’s stored physical_key first, then its DB row. Missing rows are no-ops; a false storage delete result requires head(key) === null before proceeding. A storage/read-back failure retains metadata. A DB failure after deletion can leave a row pointing to an absent object. Retry/reconcile using FileProviderOperationError.stage, nodeId, physicalKey, storageDeleted, completedIds and cause. Later IDs in the batch are untouched. This is not an atomic DB/object transaction. It deletes the visible key; S3 versions/delete markers, retention and legal erasure are the application’s responsibility. Authorization, concurrent updates and shared-key ownership must be enforced by the application.

Upload precedes DB creation. The default uploadFailurePolicy:'retain' preserves the object on a rejected DB create, because the create may have committed but lost its acknowledgement. Public errors include the actual key and original cause. For opt-in compensate, provide canCompensateUpload(context). Cleanup runs only if read-back returns null and this callback returns true, confirming both definitive non-commit and exclusive ownership of this newly uploaded key. Eventual consistency or a missing read-back alone is insufficient. A present row is retained; uncertain read-back/confirmation is retained with compensation:'unconfirmed'. Successful cleanup reports deleted, failed cleanup failed with compensationError, and a declined confirmation retained. The original create still rejects in every case. A rejected storage upload is not automatically cleaned up because its result is also uncertain. Keep operational reconciliation outside the provider; no retry or orphan sweep runs automatically.

Portable default search spans all logical folders. query is a literal filename substring compared using JavaScript toLowerCase; regex metacharacters are literal. It performs no Unicode normalization or locale-aware collation. Empty query matches all. Tags are unquoted comma-separated strings, trimmed and compared as exact, case-sensitive membership; every requested tag must match (AND). Commas inside a tag are unsupported. Candidate order is preserved; no relevance sorting is added.

The default asks find(collection, {}, {paginateAll:true}) for the full authorized candidate set and filters locally. Adapters must honor exhaustive retrieval; it cannot overcome a backend’s result cap. maxSearchRecords (default 10,000) rejects oversized returned sets after retrieval, so it does not bound network traffic. For large stores, provide an indexed searchRecords(criteria) that returns the complete authorized candidate set for those criteria; the same portable predicates are still applied. Do not return a truncated page or silently omit valid results.

legacy-adapter preserves the former $regex/$options:'i' and $all filters exactly. Semantics and support then depend on the adapter; CSV tags do not magically become a DB array. The private facade keeps this legacy default and original DB create error identity; direct Public imports use portable search and structured upload errors. Opt into portable search in private callers deliberately.