hazo_files 2.0.0 → 2.1.1
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/CHANGE_LOG.md +43 -0
- package/README.md +272 -0
- package/dist/index.d.mts +96 -9
- package/dist/index.d.ts +96 -9
- package/dist/index.js +113 -2
- package/dist/index.mjs +107 -1
- package/dist/server/index.d.mts +44 -1
- package/dist/server/index.d.ts +44 -1
- package/dist/server/index.js +192 -0
- package/dist/server/index.mjs +190 -0
- package/dist/testing/index.d.mts +62 -0
- package/dist/testing/index.d.ts +62 -0
- package/dist/testing/index.js +71 -0
- package/dist/testing/index.mjs +44 -0
- package/docs/superpowers/plans/2026-05-23-test-app-v2-providers.md +1 -1
- package/package.json +8 -2
package/CHANGE_LOG.md
CHANGED
|
@@ -5,6 +5,49 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## 2.1.1 — 2026-05-24
|
|
9
|
+
|
|
10
|
+
**Docs + housekeeping** — no API changes.
|
|
11
|
+
|
|
12
|
+
- README: added documentation for `NamingTemplate`, `writeWithCollisionRetry`, and the pure `addRef`/`removeRef`/`countRefs` helpers introduced in 2.1.0.
|
|
13
|
+
- `.gitignore`: untracked test-app runtime artifacts (`data/`, `logs/`, `tsconfig.tsbuildinfo`, `*.tgz`).
|
|
14
|
+
|
|
15
|
+
## 2.1.0 — 2026-05-24
|
|
16
|
+
|
|
17
|
+
**Additive** — no breaking changes from 2.0.x.
|
|
18
|
+
|
|
19
|
+
- `NamingTemplate` DSL with formatters (`slug`, `pad:N`, `upper`, `lower`, `truncate:N`, `date:FMT`). 200-char cap; sanitises `/\:*?"<>|` → `_`; throws on missing variable; dotted-path resolution. Formatters `pad` and `truncate` throw on missing/NaN arg; `date` throws on empty value.
|
|
20
|
+
- `writeWithCollisionRetry` helper with 999-attempt cap; throws `StorageCollisionExhausted`. Renders the `{index}` variable per attempt.
|
|
21
|
+
- Reference tracking helpers: `addRef` / `removeRef` / `countRefs` over `{ file_refs, ref_count }`. Idempotent on same `ref_id`; soft-tombstone via `removed_at`.
|
|
22
|
+
|
|
23
|
+
### Migration from 2.0.x
|
|
24
|
+
|
|
25
|
+
No required changes. Existing imports continue to work. Use the new helpers to compute filenames and track usage references.
|
|
26
|
+
|
|
27
|
+
## 2.0.1 (2026-05-23)
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- **`FileStorageProvider` interface** (`hazo_files/server`) — lightweight storage abstraction with `put`, `get`, `delete`, `exists`, `getSignedUrl`, and `probe` methods. Complements the existing `StorageModule`/`FileManager` stack for simpler use cases that don't need folder trees or metadata tracking.
|
|
32
|
+
- **`AppFileServerProvider`** (`hazo_files/server`) — local-filesystem provider with HMAC-SHA256-signed download URLs. Constructor options: `root` (filesystem root), `hmac_secret`, and `default_ttl_seconds` (default 300s). Includes `verifySignedUrl(token, path)` for use in the `/api/files/serve` API route. Path traversal is blocked at the `resolve()` layer.
|
|
33
|
+
- **`InMemoryProvider`** (`hazo_files/testing`) — in-memory `FileStorageProvider` backed by a `Map<string, Buffer>`. Intended for unit tests. `getSignedUrl` returns a `data:` URL so tests never need a real HTTP server. Includes `snapshot()` escape hatch for assertions.
|
|
34
|
+
- **`GoogleDriveProvider`** (`hazo_files/server`) — service-account Google Drive provider for Shared Drives. Accepts `service_account_json`, `shared_drive_id`, and a `DrivePathCache` for lazy folder-ID resolution. Unlike `GoogleDriveModule` (which uses OAuth), this provider is designed for server-side use with a GCP service account.
|
|
35
|
+
- **`hazo_files/testing` subpath export** — re-exports `InMemoryProvider` for use in test suites without pulling in server-only modules.
|
|
36
|
+
- **Error classes**: `StorageCollisionExhausted`, `StorageNotConfigured`, `StorageUnavailable` — exported from `hazo_files/server`.
|
|
37
|
+
- **Types**: `PutOpts`, `PutResult`, `SignedUrlOpts`, `ProbeResult`, `DrivePathCache` — exported from `hazo_files/server`.
|
|
38
|
+
|
|
39
|
+
### Breaking changes from 1.x
|
|
40
|
+
None. All additions are new exports; existing `FileManager`/`TrackedFileManager` API is unchanged.
|
|
41
|
+
|
|
42
|
+
## 2.0.0 (2026-05-22)
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- **`googleapis` and `mime-types` promoted to direct `dependencies`** (previously optional peer deps) — the package now bundles them so consumers no longer need to install them separately for Google Drive or MIME detection. This is a major bump because it changes the transitive dependency surface for all consumers.
|
|
47
|
+
|
|
48
|
+
### Not breaking
|
|
49
|
+
The public API is identical to 1.6.0. No migration required.
|
|
50
|
+
|
|
8
51
|
## 1.6.0 (2026-05-21)
|
|
9
52
|
|
|
10
53
|
### Added
|
package/README.md
CHANGED
|
@@ -7,6 +7,10 @@ A powerful, modular file management package for Node.js and React applications w
|
|
|
7
7
|
|
|
8
8
|
## Features
|
|
9
9
|
|
|
10
|
+
- **`FileStorageProvider` Interface**: Lightweight `put/get/delete/exists/getSignedUrl/probe` abstraction for simpler use cases
|
|
11
|
+
- **`AppFileServerProvider`**: Local filesystem + HMAC-signed download URLs — no cloud account required
|
|
12
|
+
- **`GoogleDriveProvider`**: Service-account Google Drive for Shared Drives with lazy path-cache
|
|
13
|
+
- **`InMemoryProvider`**: Zero-dependency in-memory store for unit tests (via `hazo_files/testing`)
|
|
10
14
|
- **Multiple Storage Providers**: Local filesystem, Google Drive, and Dropbox support out of the box
|
|
11
15
|
- **Modular Architecture**: Easily add custom storage providers
|
|
12
16
|
- **Unified API**: Single consistent interface across all storage providers
|
|
@@ -72,6 +76,17 @@ For the background-upload sonner toast bridge (optional):
|
|
|
72
76
|
npm install sonner # Toast notifications for background upload pipelines
|
|
73
77
|
```
|
|
74
78
|
|
|
79
|
+
### Subpath exports
|
|
80
|
+
|
|
81
|
+
| Import | Contents |
|
|
82
|
+
|--------|---------|
|
|
83
|
+
| `hazo_files` | Core types, utilities, naming helpers |
|
|
84
|
+
| `hazo_files/ui` | React components (FileBrowser, NamingRuleConfigurator, etc.) |
|
|
85
|
+
| `hazo_files/server` | Server-only: FileManager, TrackedFileManager, FileStorageProvider providers, schema exports |
|
|
86
|
+
| `hazo_files/background-upload` | Framework-agnostic UploadManager pipeline engine |
|
|
87
|
+
| `hazo_files/background-upload/react` | React bindings for background uploads |
|
|
88
|
+
| `hazo_files/testing` | `InMemoryProvider` — import in test suites, safe to use without `server-only` |
|
|
89
|
+
|
|
75
90
|
### Tailwind CSS v4 Setup (Required for UI Components)
|
|
76
91
|
|
|
77
92
|
If you're using Tailwind CSS v4 with the UI components, you must add a `@source` directive to your CSS file to ensure Tailwind scans the package's files for utility classes.
|
|
@@ -1549,6 +1564,117 @@ import type {
|
|
|
1549
1564
|
} from 'hazo_files';
|
|
1550
1565
|
```
|
|
1551
1566
|
|
|
1567
|
+
### Pure Reference Helpers (v2.1.0)
|
|
1568
|
+
|
|
1569
|
+
Functional helpers for manipulating `{ file_refs, ref_count }` objects without a database. Useful for in-memory state management or building your own persistence layer.
|
|
1570
|
+
|
|
1571
|
+
```typescript
|
|
1572
|
+
import { addRef, removeRef, countRefs } from 'hazo_files';
|
|
1573
|
+
import type { FileWithRefs, FileRef } from 'hazo_files';
|
|
1574
|
+
|
|
1575
|
+
// addRef — idempotent on same ref_id; returns updated object
|
|
1576
|
+
const updated = addRef(file, {
|
|
1577
|
+
ref_id: 'ref-123',
|
|
1578
|
+
ref_type: 'form_field',
|
|
1579
|
+
ref_source: 'form-abc',
|
|
1580
|
+
created_at: new Date().toISOString(),
|
|
1581
|
+
});
|
|
1582
|
+
// updated.ref_count === countRefs(updated)
|
|
1583
|
+
|
|
1584
|
+
// removeRef — soft-tombstones by setting removed_at; returns updated object
|
|
1585
|
+
const released = removeRef(file, 'ref-123', new Date().toISOString());
|
|
1586
|
+
// released.ref_count decremented; original ref_id still present with removed_at set
|
|
1587
|
+
|
|
1588
|
+
// countRefs — count active (non-tombstoned) references
|
|
1589
|
+
const active = countRefs(file); // number
|
|
1590
|
+
```
|
|
1591
|
+
|
|
1592
|
+
These helpers are **pure functions** — they never write to a database. Pair them with `TrackedFileManager.addRef` / `removeRef` when you need DB persistence.
|
|
1593
|
+
|
|
1594
|
+
## Naming Templates (v2.1.0)
|
|
1595
|
+
|
|
1596
|
+
`NamingTemplate` is a lightweight DSL for generating filenames and folder paths from string templates with variable substitution and pipe formatters.
|
|
1597
|
+
|
|
1598
|
+
### Basic Usage
|
|
1599
|
+
|
|
1600
|
+
```typescript
|
|
1601
|
+
import { NamingTemplate } from 'hazo_files';
|
|
1602
|
+
|
|
1603
|
+
const name = NamingTemplate.render(
|
|
1604
|
+
'{client_name|slug}_{date|date:YYYY-MM-DD}_{index|pad:3}.pdf',
|
|
1605
|
+
{ client_name: 'Acme Corp', date: '2026-05-24T00:00:00Z', index: 7 }
|
|
1606
|
+
);
|
|
1607
|
+
// → "acme-corp_2026-05-24_007.pdf"
|
|
1608
|
+
```
|
|
1609
|
+
|
|
1610
|
+
### Template Syntax
|
|
1611
|
+
|
|
1612
|
+
- **`{variable}`** — substitutes `variable` from the vars object
|
|
1613
|
+
- **`{variable|formatter}`** — applies a formatter after substitution
|
|
1614
|
+
- **`{variable|formatter:arg}`** — formatter with argument
|
|
1615
|
+
- **`{obj.nested.key}`** — dotted-path resolution into nested objects
|
|
1616
|
+
- Output is capped at **200 characters**; `/\:*?"<>|` are sanitised to `_`
|
|
1617
|
+
|
|
1618
|
+
### Formatters
|
|
1619
|
+
|
|
1620
|
+
| Formatter | Example | Description |
|
|
1621
|
+
|---|---|---|
|
|
1622
|
+
| `slug` | `{name\|slug}` | Lowercase, replace non-alphanumeric with `-`, strip leading/trailing `-` |
|
|
1623
|
+
| `upper` | `{code\|upper}` | Uppercase |
|
|
1624
|
+
| `lower` | `{code\|lower}` | Lowercase |
|
|
1625
|
+
| `pad:N` | `{index\|pad:3}` | Zero-pad to N digits (7 → `007`) |
|
|
1626
|
+
| `truncate:N` | `{title\|truncate:20}` | Trim to N characters |
|
|
1627
|
+
| `date:FMT` | `{ts\|date:YYYY-MM-DD}` | Format ISO date string; tokens: `YYYY`, `MM`, `DD`, `HH`, `mm`, `ss` |
|
|
1628
|
+
|
|
1629
|
+
### Error Conditions
|
|
1630
|
+
|
|
1631
|
+
- Missing variable → throws `Error: missing variable: <name>`
|
|
1632
|
+
- `pad` / `truncate` without numeric arg → throws
|
|
1633
|
+
- `date` formatter on empty/invalid value → throws
|
|
1634
|
+
- Unknown formatter → throws
|
|
1635
|
+
|
|
1636
|
+
## Collision-Safe Writes (v2.1.0)
|
|
1637
|
+
|
|
1638
|
+
`writeWithCollisionRetry` writes a file to any `FileStorageProvider`, automatically incrementing an `{index}` variable in the template until a free path is found.
|
|
1639
|
+
|
|
1640
|
+
### Usage
|
|
1641
|
+
|
|
1642
|
+
```typescript
|
|
1643
|
+
import { writeWithCollisionRetry, StorageCollisionExhausted } from 'hazo_files';
|
|
1644
|
+
|
|
1645
|
+
try {
|
|
1646
|
+
const result = await writeWithCollisionRetry({
|
|
1647
|
+
provider, // FileStorageProvider
|
|
1648
|
+
template: '{client|slug}_{date|date:YYYY-MM-DD}_{index|pad:3}.pdf',
|
|
1649
|
+
vars: { client: 'Acme Corp', date: new Date().toISOString() },
|
|
1650
|
+
body: pdfBuffer,
|
|
1651
|
+
path_prefix: 'uploads/forms', // optional — prepended to rendered name
|
|
1652
|
+
max_attempts: 999, // optional, default 999
|
|
1653
|
+
put_opts: { metadata: { ... } } // optional extra PutOpts (ifNotExists always set)
|
|
1654
|
+
});
|
|
1655
|
+
|
|
1656
|
+
console.log(result.logical_path);
|
|
1657
|
+
// → "uploads/forms/acme-corp_2026-05-24_001.pdf"
|
|
1658
|
+
// (or _002, _003, … if earlier slots were taken)
|
|
1659
|
+
} catch (err) {
|
|
1660
|
+
if (err instanceof StorageCollisionExhausted) {
|
|
1661
|
+
// All 999 attempts collided
|
|
1662
|
+
console.error(`Could not write after ${err.attempts} attempts`);
|
|
1663
|
+
}
|
|
1664
|
+
throw err;
|
|
1665
|
+
}
|
|
1666
|
+
```
|
|
1667
|
+
|
|
1668
|
+
### How It Works
|
|
1669
|
+
|
|
1670
|
+
1. Renders the template with `{ ...vars, index: 1 }` for the first attempt
|
|
1671
|
+
2. Calls `provider.put(path, body, { ifNotExists: true })`
|
|
1672
|
+
3. If the file exists, increments `index` and retries
|
|
1673
|
+
4. Returns `PutResult & { logical_path: string }` on the first successful write
|
|
1674
|
+
5. Throws `StorageCollisionExhausted` if all attempts are exhausted
|
|
1675
|
+
|
|
1676
|
+
The `{index}` variable is **automatically injected** — you don't need to supply it in `vars`.
|
|
1677
|
+
|
|
1552
1678
|
## File Change Detection
|
|
1553
1679
|
|
|
1554
1680
|
Detect file content changes using fast xxHash hashing.
|
|
@@ -1866,6 +1992,152 @@ try {
|
|
|
1866
1992
|
}
|
|
1867
1993
|
```
|
|
1868
1994
|
|
|
1995
|
+
## FileStorageProvider (v2 Provider API)
|
|
1996
|
+
|
|
1997
|
+
v2 introduces a slimmer storage abstraction — `FileStorageProvider` — that sits alongside (not replacing) the existing `FileManager`/`StorageModule` stack. Use it when you don't need folder trees, metadata tracking, or naming conventions, and just want put/get/signed-URL semantics.
|
|
1998
|
+
|
|
1999
|
+
### Interface
|
|
2000
|
+
|
|
2001
|
+
```typescript
|
|
2002
|
+
import type { FileStorageProvider } from 'hazo_files/server';
|
|
2003
|
+
|
|
2004
|
+
interface FileStorageProvider {
|
|
2005
|
+
put(path: string, body: Buffer | Readable, opts?: PutOpts): Promise<PutResult>;
|
|
2006
|
+
get(path: string): Promise<Buffer | Readable>;
|
|
2007
|
+
delete(path: string): Promise<void>;
|
|
2008
|
+
exists(path: string): Promise<boolean>;
|
|
2009
|
+
getSignedUrl(path: string, opts?: SignedUrlOpts): Promise<string>;
|
|
2010
|
+
probe(): Promise<ProbeResult>;
|
|
2011
|
+
}
|
|
2012
|
+
```
|
|
2013
|
+
|
|
2014
|
+
### AppFileServerProvider
|
|
2015
|
+
|
|
2016
|
+
Local filesystem with HMAC-signed download URLs. Ideal for self-hosted apps where files are served through an API route.
|
|
2017
|
+
|
|
2018
|
+
```typescript
|
|
2019
|
+
import { AppFileServerProvider } from 'hazo_files/server';
|
|
2020
|
+
|
|
2021
|
+
const provider = new AppFileServerProvider({
|
|
2022
|
+
root: './storage', // filesystem root
|
|
2023
|
+
hmac_secret: process.env.FILE_HMAC_SECRET!,
|
|
2024
|
+
default_ttl_seconds: 300, // default 5 min
|
|
2025
|
+
});
|
|
2026
|
+
|
|
2027
|
+
// Store a file
|
|
2028
|
+
const result = await provider.put('uploads/report.pdf', buffer, {
|
|
2029
|
+
contentType: 'application/pdf',
|
|
2030
|
+
});
|
|
2031
|
+
// { provider: 'app_file_server', native_id: 'uploads/report.pdf', size: 12345 }
|
|
2032
|
+
|
|
2033
|
+
// Generate a signed URL (expires in 5 min)
|
|
2034
|
+
const url = await provider.getSignedUrl('uploads/report.pdf');
|
|
2035
|
+
// '/api/files/serve/1748080800.ABC123.../uploads/report.pdf'
|
|
2036
|
+
```
|
|
2037
|
+
|
|
2038
|
+
#### Serving signed URLs (Next.js API route)
|
|
2039
|
+
|
|
2040
|
+
```typescript
|
|
2041
|
+
// app/api/files/serve/[...token]/route.ts
|
|
2042
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2043
|
+
import { AppFileServerProvider } from 'hazo_files/server';
|
|
2044
|
+
|
|
2045
|
+
const provider = new AppFileServerProvider({
|
|
2046
|
+
root: './storage',
|
|
2047
|
+
hmac_secret: process.env.FILE_HMAC_SECRET!,
|
|
2048
|
+
});
|
|
2049
|
+
|
|
2050
|
+
export async function GET(
|
|
2051
|
+
_req: NextRequest,
|
|
2052
|
+
{ params }: { params: { token: string[] } }
|
|
2053
|
+
) {
|
|
2054
|
+
// token = ['<exp>.<sig>', 'uploads', 'report.pdf']
|
|
2055
|
+
const [token, ...pathParts] = params.token;
|
|
2056
|
+
const filePath = pathParts.join('/');
|
|
2057
|
+
|
|
2058
|
+
if (!provider.verifySignedUrl(token, filePath)) {
|
|
2059
|
+
return new NextResponse('Forbidden', { status: 403 });
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
const buf = await provider.get(filePath) as Buffer;
|
|
2063
|
+
return new NextResponse(buf, {
|
|
2064
|
+
headers: { 'Content-Type': 'application/octet-stream' },
|
|
2065
|
+
});
|
|
2066
|
+
}
|
|
2067
|
+
```
|
|
2068
|
+
|
|
2069
|
+
### GoogleDriveProvider
|
|
2070
|
+
|
|
2071
|
+
Service-account Google Drive for Shared Drives. No OAuth flow — configure a GCP service account with Shared Drive access.
|
|
2072
|
+
|
|
2073
|
+
```typescript
|
|
2074
|
+
import { GoogleDriveProvider } from 'hazo_files/server';
|
|
2075
|
+
import type { DrivePathCache } from 'hazo_files/server';
|
|
2076
|
+
|
|
2077
|
+
// Implement the path cache (e.g., backed by your hazo_connect adapter)
|
|
2078
|
+
const pathCache: DrivePathCache = {
|
|
2079
|
+
lookup: async (key) => redis.get(`gdrive:${key}`),
|
|
2080
|
+
write: async (key, id) => redis.set(`gdrive:${key}`, id),
|
|
2081
|
+
invalidate: async (key) => redis.del(`gdrive:${key}`),
|
|
2082
|
+
};
|
|
2083
|
+
|
|
2084
|
+
const provider = new GoogleDriveProvider({
|
|
2085
|
+
service_account_json: process.env.GOOGLE_SERVICE_ACCOUNT_JSON!,
|
|
2086
|
+
shared_drive_id: 'your-shared-drive-id',
|
|
2087
|
+
path_cache: pathCache,
|
|
2088
|
+
});
|
|
2089
|
+
|
|
2090
|
+
// Upload a file (folders are created lazily)
|
|
2091
|
+
const result = await provider.put('2026/ACME/invoice.pdf', buffer, {
|
|
2092
|
+
contentType: 'application/pdf',
|
|
2093
|
+
});
|
|
2094
|
+
// { provider: 'gdrive', native_id: '<drive-file-id>', size: 12345 }
|
|
2095
|
+
|
|
2096
|
+
// Check connectivity
|
|
2097
|
+
const health = await provider.probe();
|
|
2098
|
+
// { ok: true } or { ok: false, error: 'drive_not_shared', message: '...' }
|
|
2099
|
+
```
|
|
2100
|
+
|
|
2101
|
+
**Note**: `getSignedUrl` for Google Drive returns a native `drive.google.com/uc?id=...` link, which requires the viewer to have Drive access. For public unauthenticated downloads, use `AppFileServerProvider` instead or implement your own proxy.
|
|
2102
|
+
|
|
2103
|
+
### InMemoryProvider (testing)
|
|
2104
|
+
|
|
2105
|
+
Import from the dedicated `hazo_files/testing` subpath — keeps server-only modules out of your test bundle.
|
|
2106
|
+
|
|
2107
|
+
```typescript
|
|
2108
|
+
import { InMemoryProvider } from 'hazo_files/testing';
|
|
2109
|
+
|
|
2110
|
+
const store = new InMemoryProvider();
|
|
2111
|
+
|
|
2112
|
+
await store.put('docs/readme.txt', Buffer.from('hello'));
|
|
2113
|
+
const buf = await store.get('docs/readme.txt');
|
|
2114
|
+
console.log(buf.toString()); // 'hello'
|
|
2115
|
+
|
|
2116
|
+
// Test helper: inspect internal state
|
|
2117
|
+
const snap = store.snapshot();
|
|
2118
|
+
console.log([...snap.keys()]); // ['docs/readme.txt']
|
|
2119
|
+
|
|
2120
|
+
// getSignedUrl returns a data: URL — no HTTP server needed
|
|
2121
|
+
const url = await store.getSignedUrl('docs/readme.txt');
|
|
2122
|
+
// 'data:application/octet-stream;base64,aGVsbG8='
|
|
2123
|
+
```
|
|
2124
|
+
|
|
2125
|
+
### Provider Errors
|
|
2126
|
+
|
|
2127
|
+
```typescript
|
|
2128
|
+
import {
|
|
2129
|
+
StorageCollisionExhausted,
|
|
2130
|
+
StorageNotConfigured,
|
|
2131
|
+
StorageUnavailable,
|
|
2132
|
+
} from 'hazo_files/server';
|
|
2133
|
+
|
|
2134
|
+
// StorageCollisionExhausted — thrown when put with ifNotExists keeps colliding
|
|
2135
|
+
// StorageNotConfigured — thrown when provider config is absent
|
|
2136
|
+
// StorageUnavailable — wraps a ProbeResult error for runtime checks
|
|
2137
|
+
```
|
|
2138
|
+
|
|
2139
|
+
---
|
|
2140
|
+
|
|
1869
2141
|
## Extending with Custom Storage Providers
|
|
1870
2142
|
|
|
1871
2143
|
See [docs/ADDING_MODULES.md](docs/ADDING_MODULES.md) for a complete guide on creating custom storage modules.
|
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
1
2
|
import { OAuth2Client } from 'google-auth-library';
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -396,7 +397,7 @@ type FileRefVisibility = 'public' | 'private' | 'internal';
|
|
|
396
397
|
* A reference from an entity to a file.
|
|
397
398
|
* Multiple entities can reference the same file.
|
|
398
399
|
*/
|
|
399
|
-
interface FileRef {
|
|
400
|
+
interface FileRef$1 {
|
|
400
401
|
/** Unique ID for this reference */
|
|
401
402
|
ref_id: string;
|
|
402
403
|
/** Type of entity referencing the file (e.g., 'form_field', 'chat_message') */
|
|
@@ -480,7 +481,7 @@ interface FileWithStatus {
|
|
|
480
481
|
/** Full metadata record (V2) */
|
|
481
482
|
record: FileMetadataRecordV2;
|
|
482
483
|
/** Parsed file references */
|
|
483
|
-
refs: FileRef[];
|
|
484
|
+
refs: FileRef$1[];
|
|
484
485
|
/** Whether the file has zero references */
|
|
485
486
|
is_orphaned: boolean;
|
|
486
487
|
}
|
|
@@ -878,7 +879,7 @@ declare class FileMetadataService {
|
|
|
878
879
|
/**
|
|
879
880
|
* Get all references for a file
|
|
880
881
|
*/
|
|
881
|
-
getRefs(fileId: string): Promise<FileRef[] | null>;
|
|
882
|
+
getRefs(fileId: string): Promise<FileRef$1[] | null>;
|
|
882
883
|
/**
|
|
883
884
|
* Get a file with its status and parsed refs
|
|
884
885
|
*/
|
|
@@ -3256,24 +3257,24 @@ declare function generateRefId(): string;
|
|
|
3256
3257
|
* Parse a JSON string into FileRef array.
|
|
3257
3258
|
* Returns empty array on invalid input.
|
|
3258
3259
|
*/
|
|
3259
|
-
declare function parseFileRefs(json: string | null | undefined): FileRef[];
|
|
3260
|
+
declare function parseFileRefs(json: string | null | undefined): FileRef$1[];
|
|
3260
3261
|
/**
|
|
3261
3262
|
* Serialize FileRef array to JSON string
|
|
3262
3263
|
*/
|
|
3263
|
-
declare function stringifyFileRefs(refs: FileRef[]): string;
|
|
3264
|
+
declare function stringifyFileRefs(refs: FileRef$1[]): string;
|
|
3264
3265
|
/**
|
|
3265
3266
|
* Create a FileRef from AddRefOptions
|
|
3266
3267
|
*/
|
|
3267
|
-
declare function createFileRef(options: AddRefOptions): FileRef;
|
|
3268
|
+
declare function createFileRef(options: AddRefOptions): FileRef$1;
|
|
3268
3269
|
/**
|
|
3269
3270
|
* Remove a ref by ref_id (immutable)
|
|
3270
3271
|
*/
|
|
3271
|
-
declare function removeRefFromArray(refs: FileRef[], refId: string): FileRef[];
|
|
3272
|
+
declare function removeRefFromArray(refs: FileRef$1[], refId: string): FileRef$1[];
|
|
3272
3273
|
/**
|
|
3273
3274
|
* Remove refs matching criteria from array (immutable).
|
|
3274
3275
|
* All specified criteria fields must match (AND semantics).
|
|
3275
3276
|
*/
|
|
3276
|
-
declare function removeRefsByCriteriaFromArray(refs: FileRef[], criteria: Omit<RemoveRefsCriteria, 'file_id' | 'scope_id'>): FileRef[];
|
|
3277
|
+
declare function removeRefsByCriteriaFromArray(refs: FileRef$1[], criteria: Omit<RemoveRefsCriteria, 'file_id' | 'scope_id'>): FileRef$1[];
|
|
3277
3278
|
/**
|
|
3278
3279
|
* Safely cast a FileMetadataRecord to FileMetadataRecordV2.
|
|
3279
3280
|
* Missing V2 fields are defaulted.
|
|
@@ -3284,4 +3285,90 @@ declare function toV2Record(record: FileMetadataRecord): FileMetadataRecordV2;
|
|
|
3284
3285
|
*/
|
|
3285
3286
|
declare function buildFileWithStatus(record: FileMetadataRecord): FileWithStatus;
|
|
3286
3287
|
|
|
3287
|
-
|
|
3288
|
+
declare class NamingTemplate {
|
|
3289
|
+
static render(template: string, vars: Record<string, unknown>): string;
|
|
3290
|
+
}
|
|
3291
|
+
|
|
3292
|
+
type StoragePath = string;
|
|
3293
|
+
interface PutOpts {
|
|
3294
|
+
/** Reject if the target path already exists (atomic). */
|
|
3295
|
+
ifNotExists?: boolean;
|
|
3296
|
+
/** Hint for content-type; provider may sniff if absent. */
|
|
3297
|
+
contentType?: string;
|
|
3298
|
+
/** Free-form key/value metadata persisted with the file when supported. */
|
|
3299
|
+
metadata?: Record<string, string>;
|
|
3300
|
+
}
|
|
3301
|
+
interface PutResult {
|
|
3302
|
+
/** Provider tag — `"app_file_server"`, `"gdrive"`, `"in_memory"`. */
|
|
3303
|
+
provider: string;
|
|
3304
|
+
/** Provider-native identifier (path for app-server; file ID for GDrive). */
|
|
3305
|
+
native_id: string;
|
|
3306
|
+
/** Size in bytes of the persisted body. */
|
|
3307
|
+
size: number;
|
|
3308
|
+
}
|
|
3309
|
+
interface SignedUrlOpts {
|
|
3310
|
+
/** Seconds the URL is valid for. */
|
|
3311
|
+
ttl_seconds?: number;
|
|
3312
|
+
/** Suggested download filename (Content-Disposition). */
|
|
3313
|
+
filename_hint?: string;
|
|
3314
|
+
}
|
|
3315
|
+
interface ProbeResult {
|
|
3316
|
+
ok: boolean;
|
|
3317
|
+
/** Machine-readable error tag when ok=false. */
|
|
3318
|
+
error?: "drive_not_shared" | "write_denied" | "invalid_id" | "transient" | "config_missing";
|
|
3319
|
+
/** Free-form detail for logging. */
|
|
3320
|
+
message?: string;
|
|
3321
|
+
}
|
|
3322
|
+
/**
|
|
3323
|
+
* Storage provider abstraction. Every method MUST be idempotent at the
|
|
3324
|
+
* data-content level — re-invoking put with identical body is allowed.
|
|
3325
|
+
*
|
|
3326
|
+
* Paths are logical; providers translate to native identifiers internally.
|
|
3327
|
+
*/
|
|
3328
|
+
interface FileStorageProvider {
|
|
3329
|
+
put(path: StoragePath, body: Buffer | Readable, opts?: PutOpts): Promise<PutResult>;
|
|
3330
|
+
get(path: StoragePath): Promise<Buffer | Readable>;
|
|
3331
|
+
delete(path: StoragePath): Promise<void>;
|
|
3332
|
+
exists(path: StoragePath): Promise<boolean>;
|
|
3333
|
+
getSignedUrl(path: StoragePath, opts?: SignedUrlOpts): Promise<string>;
|
|
3334
|
+
/** Used by validation cron + onboarding step 2. */
|
|
3335
|
+
probe(): Promise<ProbeResult>;
|
|
3336
|
+
}
|
|
3337
|
+
|
|
3338
|
+
interface WriteWithCollisionOpts {
|
|
3339
|
+
provider: FileStorageProvider;
|
|
3340
|
+
template: string;
|
|
3341
|
+
vars: Record<string, unknown>;
|
|
3342
|
+
body: Buffer;
|
|
3343
|
+
/** Prefix to mount the rendered filename under, e.g. `"folders/abc123"`. */
|
|
3344
|
+
path_prefix?: string;
|
|
3345
|
+
/** Max attempts. Default 999. */
|
|
3346
|
+
max_attempts?: number;
|
|
3347
|
+
/** Extra opts passed to provider.put (ifNotExists is always forced true). */
|
|
3348
|
+
put_opts?: Omit<PutOpts, "ifNotExists">;
|
|
3349
|
+
}
|
|
3350
|
+
declare function writeWithCollisionRetry(opts: WriteWithCollisionOpts): Promise<PutResult & {
|
|
3351
|
+
logical_path: string;
|
|
3352
|
+
}>;
|
|
3353
|
+
|
|
3354
|
+
interface FileRef {
|
|
3355
|
+
ref_id: string;
|
|
3356
|
+
ref_type: string;
|
|
3357
|
+
ref_source: string;
|
|
3358
|
+
form_id?: string;
|
|
3359
|
+
field_id?: string;
|
|
3360
|
+
message_id?: string;
|
|
3361
|
+
document_id?: string;
|
|
3362
|
+
created_at: string;
|
|
3363
|
+
removed_at?: string;
|
|
3364
|
+
}
|
|
3365
|
+
interface FileWithRefs {
|
|
3366
|
+
file_refs: FileRef[];
|
|
3367
|
+
ref_count: number;
|
|
3368
|
+
}
|
|
3369
|
+
|
|
3370
|
+
declare function addRef<T extends FileWithRefs>(file: T, ref: FileRef): T;
|
|
3371
|
+
declare function removeRef<T extends FileWithRefs>(file: T, ref_id: string, removed_at: string): T;
|
|
3372
|
+
declare function countRefs(file: Pick<FileWithRefs, "file_refs">): number;
|
|
3373
|
+
|
|
3374
|
+
export { ALL_SYSTEM_VARIABLES, type AddExtractionOptions, type AddRefOptions, type AuthCallbacks, AuthenticationError, type CleanupOrphanedOptions, ConfigurationError, type ContentTagConfig, type CreateFolderOptions, type CrudServiceLike, DEFAULT_DATE_FORMATS, type DatabaseSchemaDefinition, type DatabaseTrackingConfig, DirectoryExistsError, DirectoryNotEmptyError, DirectoryNotFoundError, type DownloadOptions, DropboxAuth, type DropboxAuthCallbacks, type DropboxAuthConfig, type DropboxConfig, DropboxModule, type DropboxTokenData, type ExtractionData, type ExtractionOptions, type ExtractionResult, type FileBrowserState, type FileDataStructure, FileExistsError, type FileInfo, type FileItem, FileManager, type FileManagerOptions, type FileMetadataInput, type FileMetadataRecord, type FileMetadataRecordV2, FileMetadataService, type FileMetadataServiceOptions, type FileMetadataUpdate, FileNotFoundError, type FileRef$1 as FileRef, type FileRefVisibility, type FileStatus, type FileSystemItem, FileTooLargeError, type FileWithRefs, type FileWithStatus, type FindOrphanedOptions, type FolderItem, type GeneratedNameResult, type GoogleAuthConfig, GoogleDriveAuth, type GoogleDriveConfig, GoogleDriveModule, HAZO_FILES_DEFAULT_TABLE_NAME, HAZO_FILES_MIGRATION_V2, HAZO_FILES_MIGRATION_V3, HAZO_FILES_NAMING_DEFAULT_TABLE_NAME, HAZO_FILES_NAMING_TABLE_SCHEMA, HAZO_FILES_TABLE_SCHEMA, type HazoFilesColumnDefinitions, type HazoFilesConfig, HazoFilesError, type HazoFilesMigrationV2, type HazoFilesMigrationV3, type HazoFilesNamingColumnDefinitions, type HazoFilesNamingTableSchema, type HazoFilesTableSchema, type HazoLLMInstance, InvalidExtensionError, InvalidPathError, LLMExtractionService, type LLMFactory, type LLMFactoryConfig, type LLMProvider, type ListNamingConventionsOptions, type ListOptions, type LocalStorageConfig, LocalStorageModule, type MetadataLogger, type MigrationExecutor, type MigrationSchemaDefinition, type MoveOptions, type NameGenerationOptions, type NamingConventionInput, type NamingConventionRecord, NamingConventionService, type NamingConventionServiceOptions, type NamingConventionType, type NamingConventionUpdate, type NamingRuleConfiguratorProps, type NamingRuleHistoryEntry, type NamingRuleSchema, NamingTemplate, type NamingVariable, OperationError, type OperationResult, type ParsedNamingConvention, type PatternSegment, PermissionDeniedError, type ProgressCallback, type RemoveExtractionOptions, type RemoveRefsCriteria, type RenameOptions, SYSTEM_COUNTER_VARIABLES, SYSTEM_DATE_VARIABLES, SYSTEM_FILE_VARIABLES, type StorageModule, type StorageProvider, type TokenData, TrackedFileManager, type TrackedFileManagerFullOptions, type TrackedFileManagerOptions, type TrackedUploadOptions, type TreeNode, type UploadExtractOptions, type UploadExtractResult, UploadExtractService, type UploadOptions, type UploadWithRefOptions, type UseNamingRuleActions, type UseNamingRuleReturn, type UseNamingRuleState, type VariableCategory, type WriteWithCollisionOpts, addExtractionToFileData, addRef, backfillV2Defaults, buildFileWithStatus, clearExtractions, clonePattern, computeFileHash, computeFileHashFromStream, computeFileHashSync, computeFileInfo, countRefs, createAndInitializeModule, createDropboxAuth, createDropboxModule, createEmptyFileDataStructure, createEmptyNamingRuleSchema, createFileItem, createFileManager, createFileMetadataService, createFileRef, createFolderItem, createGoogleDriveAuth, createGoogleDriveModule, createInitializedFileManager, createInitializedTrackedFileManager, createLLMExtractionService, createLiteralSegment, createLocalModule, createModule, createNamingConventionService, createTrackedFileManager, createUploadExtractService, createVariableSegment, deepMerge, errorResult, filterItems, formatBytes, formatCounter, formatDateToken, generateExtractionId, generateId, generatePreviewName, generateRefId, generateSampleConfig, generateSegmentId, getBaseName, getBreadcrumbs, getDirName, getExtension, getExtensionFromMime, getExtractionById, getExtractionCount, getExtractions, getFileCategory, getFileMetadataValues, getMergedData, getMigrationForTable, getMigrationV3ForTable, getMimeType, getNameWithoutExtension, getNamingSchemaForTable, getParentPath, getPathSegments, getRegisteredProviders, getRelativePath, getSchemaForTable, getSystemVariablePreviewValues, hasExtension, hasExtractionStructure, hasFileContentChanged, hashesEqual, hazo_files_generate_file_name, hazo_files_generate_folder_name, isAudio, isChildPath, isCounterVariable, isDateVariable, isDocument, isFile, isFileMetadataVariable, isFolder, isImage, isPreviewable, isProviderRegistered, isText, isVideo, joinPath, loadConfig, loadConfigAsync, migrateToV2, migrateToV3, normalizePath, parseConfig, parseFileData, parseFileRefs, parsePatternString, patternToString, recalculateMergedData, registerModule, removeExtractionById, removeExtractionByIndex, removeRef, removeRefFromArray, removeRefsByCriteriaFromArray, sanitizeFilename, saveConfig, sortItems, stringifyFileData, stringifyFileRefs, successResult, toV2Record, updateExtractionById, validateExtractionData, validateFileDataStructure, validateNamingRuleSchema, validatePath, writeWithCollisionRetry };
|