hazo_files 1.5.2 → 1.6.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.
@@ -1,3 +1,4 @@
1
+ import { JobHandler } from 'hazo_jobs';
1
2
  import { OAuth2Client } from 'google-auth-library';
2
3
 
3
4
  /**
@@ -187,6 +188,10 @@ interface FileMetadataInput {
187
188
  original_filename?: string;
188
189
  /** Content tag classifying the document type (V3) */
189
190
  content_tag?: string;
191
+ /** Actor UUID who performed this mutation (V4) */
192
+ changed_by?: string;
193
+ /** Source URL when file was imported via importFromUrl (V4) */
194
+ source_url?: string;
190
195
  }
191
196
  /**
192
197
  * Input for updating an existing metadata record
@@ -433,6 +438,10 @@ interface FileMetadataRecordV2 extends FileMetadataRecord {
433
438
  deleted_at?: string | null;
434
439
  /** Content tag classifying the document type (V3) */
435
440
  content_tag?: string | null;
441
+ /** Actor UUID who last mutated this record (V4 — migration 004) */
442
+ changed_by?: string | null;
443
+ /** Source URL when file was imported via importFromUrl (V4 — migration 005) */
444
+ source_url?: string | null;
436
445
  }
437
446
  /**
438
447
  * Options for adding a reference to a file
@@ -770,8 +779,9 @@ declare class FileMetadataService {
770
779
  recordAccess(path: string, storageType: StorageProvider): Promise<boolean>;
771
780
  /**
772
781
  * Record a file deletion
782
+ * changedBy is accepted for API consistency but not written (record is deleted)
773
783
  */
774
- recordDelete(path: string, storageType: StorageProvider): Promise<boolean>;
784
+ recordDelete(path: string, storageType: StorageProvider, _changedBy?: string): Promise<boolean>;
775
785
  /**
776
786
  * Record a directory deletion (recursive)
777
787
  */
@@ -779,11 +789,11 @@ declare class FileMetadataService {
779
789
  /**
780
790
  * Record a file or folder move
781
791
  */
782
- recordMove(sourcePath: string, destinationPath: string, storageType: StorageProvider): Promise<boolean>;
792
+ recordMove(sourcePath: string, destinationPath: string, storageType: StorageProvider, changedBy?: string): Promise<boolean>;
783
793
  /**
784
794
  * Record a file or folder rename
785
795
  */
786
- recordRename(path: string, newName: string, storageType: StorageProvider): Promise<boolean>;
796
+ recordRename(path: string, newName: string, storageType: StorageProvider, changedBy?: string): Promise<boolean>;
787
797
  /**
788
798
  * Find a record by path and storage type
789
799
  */
@@ -887,9 +897,9 @@ declare class FileMetadataService {
887
897
  */
888
898
  softDelete(fileId: string): Promise<boolean>;
889
899
  /**
890
- * Update specific V2 fields on a record
900
+ * Update specific V2/V4 fields on a record
891
901
  */
892
- updateFields(fileId: string, fields: Partial<Pick<FileMetadataRecordV2, 'scope_id' | 'uploaded_by' | 'original_filename' | 'storage_verified_at' | 'status' | 'content_tag'>>): Promise<boolean>;
902
+ updateFields(fileId: string, fields: Partial<Pick<FileMetadataRecordV2, 'scope_id' | 'uploaded_by' | 'original_filename' | 'storage_verified_at' | 'status' | 'content_tag' | 'changed_by' | 'source_url'>>): Promise<boolean>;
893
903
  /**
894
904
  * Find orphaned files (zero references)
895
905
  */
@@ -1053,6 +1063,142 @@ declare function createFileManager(options?: FileManagerOptions): FileManager;
1053
1063
  */
1054
1064
  declare function createInitializedFileManager(options?: FileManagerOptions): Promise<FileManager>;
1055
1065
 
1066
+ /**
1067
+ * Quota Service
1068
+ * Per-scope opt-in quota tracking with threshold callbacks.
1069
+ *
1070
+ * A scope without a hazo_file_quotas row has no quota and uploads succeed (fail-open).
1071
+ */
1072
+ /**
1073
+ * Quota status for a scope
1074
+ */
1075
+ interface QuotaStatus {
1076
+ scopeId: string;
1077
+ byteLimit: number;
1078
+ byteUsed: number;
1079
+ percentUsed: number;
1080
+ }
1081
+ /**
1082
+ * Event emitted when usage crosses a configured threshold band
1083
+ */
1084
+ interface QuotaThresholdEvent {
1085
+ scopeId: string;
1086
+ /** Fractional threshold crossed, e.g. 0.80 or 0.95 */
1087
+ percent: number;
1088
+ bytesUsed: number;
1089
+ byteLimit: number;
1090
+ }
1091
+ /**
1092
+ * Raw row shape from hazo_file_quotas table
1093
+ */
1094
+ interface QuotaRow {
1095
+ scope_id: string;
1096
+ byte_limit: number;
1097
+ byte_used: number;
1098
+ updated_at: string;
1099
+ [key: string]: unknown;
1100
+ }
1101
+ /**
1102
+ * Minimal interface for the quota CRUD service (hazo_connect compatible)
1103
+ */
1104
+ interface QuotaCrudServiceLike {
1105
+ findBy(criteria: Record<string, unknown>): Promise<QuotaRow[]>;
1106
+ findOneBy(criteria: Record<string, unknown>): Promise<QuotaRow | null>;
1107
+ insert(data: Partial<QuotaRow> | Partial<QuotaRow>[]): Promise<QuotaRow[]>;
1108
+ updateById(id: unknown, patch: Partial<QuotaRow>): Promise<QuotaRow[]>;
1109
+ list(configure?: (qb: unknown) => unknown): Promise<QuotaRow[]>;
1110
+ }
1111
+ /**
1112
+ * Options for QuotaService
1113
+ */
1114
+ interface QuotaServiceOptions {
1115
+ /** hazo_connect CRUD service pointed at hazo_file_quotas table */
1116
+ crudService: QuotaCrudServiceLike;
1117
+ /** Callback fired when usage crosses a threshold band */
1118
+ onThreshold?: (event: QuotaThresholdEvent) => void;
1119
+ /** Fractional threshold bands (default: [0.80, 0.95]) */
1120
+ bands?: number[];
1121
+ /** Logger for diagnostics */
1122
+ logger?: {
1123
+ debug?(message: string, data?: Record<string, unknown>): void;
1124
+ warn?(message: string, data?: Record<string, unknown>): void;
1125
+ error?(message: string, data?: Record<string, unknown>): void;
1126
+ };
1127
+ }
1128
+ /**
1129
+ * Per-scope opt-in quota tracking.
1130
+ *
1131
+ * Fail-open: a scope without a quota row has no quota and all uploads succeed.
1132
+ */
1133
+ declare class QuotaService {
1134
+ private crud;
1135
+ private onThreshold?;
1136
+ private bands;
1137
+ private logger?;
1138
+ constructor(opts: QuotaServiceOptions);
1139
+ /**
1140
+ * Get quota status for a scope.
1141
+ * Returns null if no quota row exists (fail-open = no quota set).
1142
+ */
1143
+ getQuota(scopeId: string): Promise<QuotaStatus | null>;
1144
+ /**
1145
+ * Set (or update) the byte limit for a scope.
1146
+ * Creates a quota row if one does not exist (with byte_used = 0).
1147
+ * Returns the current stored status after upsert.
1148
+ *
1149
+ * @note This method does NOT auto-reconcile byte_used via a SUM query —
1150
+ * it simply upserts the limit and returns the stored row. To reconcile
1151
+ * byte_used against actual file sizes, call recomputeQuota() separately
1152
+ * after a SUM(file_size) query on hazo_files for the scope.
1153
+ */
1154
+ setQuotaLimit(scopeId: string, bytes: number): Promise<QuotaStatus>;
1155
+ /**
1156
+ * Recompute byteUsed by reading the current row.
1157
+ * (Full reconciliation against actual file sizes should be done externally
1158
+ * via a SUM query on hazo_files; this method just returns the stored state.)
1159
+ */
1160
+ recomputeQuota(scopeId: string): Promise<QuotaStatus>;
1161
+ /**
1162
+ * Pre-upload check ONLY — does NOT increment.
1163
+ * Throws QuotaExceededError if the upload would exceed the limit.
1164
+ * If no quota row exists for the scope, succeeds silently (fail-open).
1165
+ *
1166
+ * Use this before the upload, then call incrementUsage after confirmed success.
1167
+ * This prevents quota inflation when an upload fails mid-stream.
1168
+ */
1169
+ checkQuota(scopeId: string, deltaBytes: number): Promise<void>;
1170
+ /**
1171
+ * Pre-upload check and increment (atomic). Throws QuotaExceededError if the upload
1172
+ * would exceed the limit. If no quota row exists, succeeds silently (fail-open).
1173
+ * Also fires threshold callbacks for any bands crossed by the new usage.
1174
+ *
1175
+ * @deprecated Prefer checkQuota() before upload + incrementUsage() after success.
1176
+ * checkAndIncrement() increments before the upload completes; if the upload
1177
+ * subsequently fails the quota is inflated with no rollback.
1178
+ */
1179
+ checkAndIncrement(scopeId: string, deltaBytes: number): Promise<void>;
1180
+ /**
1181
+ * Decrement usage (call after soft-delete or hard-delete).
1182
+ * Clamps to zero; no-ops if no quota row.
1183
+ */
1184
+ decrementUsage(scopeId: string, deltaBytes: number): Promise<void>;
1185
+ /**
1186
+ * Increment usage manually (admin override).
1187
+ * Does NOT throw on exceeded quota — admin is explicitly bypassing.
1188
+ */
1189
+ incrementUsage(scopeId: string, deltaBytes: number): Promise<void>;
1190
+ /**
1191
+ * Fire threshold callbacks for all bands crossed going from prevUsed → newUsed.
1192
+ * Bands are sorted ascending so callbacks fire in order (80% before 95%).
1193
+ */
1194
+ private fireThresholdCallbacks;
1195
+ private rowToStatus;
1196
+ }
1197
+ /**
1198
+ * Create a QuotaService instance
1199
+ */
1200
+ declare function createQuotaService(opts: QuotaServiceOptions): QuotaService;
1201
+
1056
1202
  /**
1057
1203
  * Tracked File Manager
1058
1204
  * Extends FileManager to add database tracking of file operations
@@ -1068,6 +1214,10 @@ interface TrackedFileManagerFullOptions extends FileManagerOptions {
1068
1214
  tracking?: DatabaseTrackingConfig;
1069
1215
  /** Logger for structured file operation logging */
1070
1216
  logger?: MetadataLogger;
1217
+ /** Optional quota service for per-scope upload limits */
1218
+ quotaService?: QuotaService;
1219
+ /** SSRF allowlist passed to importFromUrl (host strings) */
1220
+ ssrfAllowlist?: string[];
1071
1221
  }
1072
1222
  /**
1073
1223
  * Extended upload options with hash tracking
@@ -1081,6 +1231,10 @@ interface TrackedUploadOptions extends UploadOptions {
1081
1231
  * Set to true when you need to immediately query/update the file record.
1082
1232
  */
1083
1233
  awaitRecording?: boolean;
1234
+ /** Actor ID (UUID) to record in uploaded_by and changed_by columns */
1235
+ actor_id?: string;
1236
+ /** Scope ID for quota tracking and organizational grouping */
1237
+ scope_id?: string;
1084
1238
  }
1085
1239
  /**
1086
1240
  * TrackedFileManager - File manager with database tracking
@@ -1091,6 +1245,8 @@ interface TrackedUploadOptions extends UploadOptions {
1091
1245
  declare class TrackedFileManager extends FileManager {
1092
1246
  private metadataService;
1093
1247
  private trackingConfig;
1248
+ private quotaService;
1249
+ private ssrfAllowlist;
1094
1250
  constructor(options?: TrackedFileManagerFullOptions);
1095
1251
  /**
1096
1252
  * Check if tracking is enabled and service is available
@@ -1120,19 +1276,27 @@ declare class TrackedFileManager extends FileManager {
1120
1276
  /**
1121
1277
  * Move a file or folder and update its path in the database
1122
1278
  */
1123
- moveItem(sourcePath: string, destinationPath: string, options?: MoveOptions): Promise<OperationResult<FileSystemItem>>;
1279
+ moveItem(sourcePath: string, destinationPath: string, options?: MoveOptions & {
1280
+ actor_id?: string;
1281
+ }): Promise<OperationResult<FileSystemItem>>;
1124
1282
  /**
1125
1283
  * Delete a file and remove its record from the database
1126
1284
  */
1127
- deleteFile(path: string): Promise<OperationResult>;
1285
+ deleteFile(path: string, opts?: {
1286
+ actor_id?: string;
1287
+ }): Promise<OperationResult>;
1128
1288
  /**
1129
1289
  * Rename a file and update its record in the database
1130
1290
  */
1131
- renameFile(path: string, newName: string, options?: RenameOptions): Promise<OperationResult<FileItem>>;
1291
+ renameFile(path: string, newName: string, options?: RenameOptions & {
1292
+ actor_id?: string;
1293
+ }): Promise<OperationResult<FileItem>>;
1132
1294
  /**
1133
1295
  * Rename a folder and update its record in the database
1134
1296
  */
1135
- renameFolder(path: string, newName: string, options?: RenameOptions): Promise<OperationResult<FolderItem>>;
1297
+ renameFolder(path: string, newName: string, options?: RenameOptions & {
1298
+ actor_id?: string;
1299
+ }): Promise<OperationResult<FolderItem>>;
1136
1300
  /**
1137
1301
  * Write a file with string content and track it
1138
1302
  */
@@ -1211,8 +1375,33 @@ declare class TrackedFileManager extends FileManager {
1211
1375
  getFilesById(fileIds: string[]): Promise<FileWithStatus[]>;
1212
1376
  /**
1213
1377
  * Soft-delete a file (marks as soft_deleted, does not remove physical file)
1378
+ * Also decrements quota usage if quotaService is configured.
1379
+ */
1380
+ softDeleteFile(fileId: string, opts?: {
1381
+ actor_id?: string;
1382
+ }): Promise<boolean>;
1383
+ /**
1384
+ * Get quota status for a scope.
1385
+ * Returns null if no quota is configured (fail-open).
1386
+ */
1387
+ getQuota(scopeId: string): Promise<QuotaStatus | null>;
1388
+ /**
1389
+ * Set or update the byte limit for a scope.
1390
+ * Creates a quota row if one does not exist.
1391
+ */
1392
+ setQuotaLimit(scopeId: string, bytes: number): Promise<QuotaStatus | null>;
1393
+ /**
1394
+ * Recompute and return the quota status for a scope.
1395
+ */
1396
+ recomputeQuota(scopeId: string): Promise<QuotaStatus | null>;
1397
+ /**
1398
+ * Increment usage for a scope (admin override — does not throw on exceeded quota).
1214
1399
  */
1215
- softDeleteFile(fileId: string): Promise<boolean>;
1400
+ incrementQuotaUsage(scopeId: string, deltaBytes: number): Promise<void>;
1401
+ /**
1402
+ * Decrement usage for a scope (e.g. after manual cleanup).
1403
+ */
1404
+ decrementQuotaUsage(scopeId: string, deltaBytes: number): Promise<void>;
1216
1405
  /**
1217
1406
  * Find orphaned files (files with zero references)
1218
1407
  */
@@ -1235,6 +1424,31 @@ declare class TrackedFileManager extends FileManager {
1235
1424
  file_id?: string;
1236
1425
  ref_id?: string;
1237
1426
  }>>;
1427
+ /**
1428
+ * Import a file from a URL into virtual storage.
1429
+ *
1430
+ * Uses hazo_secure/fetch for SSRF protection (optional peer dependency).
1431
+ * Streams the response to a temp file, counting bytes live.
1432
+ * On cap exceeded: aborts, deletes temp file, throws ImportSizeCapError.
1433
+ * On success: uploads to virtualPath, sets source_url in DB record.
1434
+ *
1435
+ * @param url - URL to fetch
1436
+ * @param virtualPath - Destination virtual path in storage
1437
+ * @param opts.referrer - Optional Referer header to send
1438
+ * @param opts.maxBytes - Maximum response size in bytes (default: 50MB)
1439
+ * @param opts.actor_id - Actor UUID to record in uploaded_by / changed_by
1440
+ */
1441
+ importFromUrl(url: string, virtualPath: string, opts?: {
1442
+ referrer?: string;
1443
+ maxBytes?: number;
1444
+ actor_id?: string;
1445
+ /** Scope ID for quota checking and tracking. If provided, quota is checked before upload. */
1446
+ scope_id?: string;
1447
+ }): Promise<OperationResult<{
1448
+ virtualPath: string;
1449
+ size: number;
1450
+ sourceUrl: string;
1451
+ }>>;
1238
1452
  }
1239
1453
  /**
1240
1454
  * Create a new TrackedFileManager instance
@@ -1804,6 +2018,29 @@ interface HazoFilesServerOptions {
1804
2018
  * When set, uploads can automatically classify document content.
1805
2019
  */
1806
2020
  defaultContentTagConfig?: ContentTagConfig;
2021
+ /**
2022
+ * SSRF protection configuration for importFromUrl.
2023
+ * When provided, only URLs matching the allowlist will be permitted.
2024
+ */
2025
+ ssrf?: {
2026
+ /** Allowed host strings (e.g. ['example.com', 'cdn.myapp.com']) */
2027
+ allowlist: string[];
2028
+ };
2029
+ /**
2030
+ * CRUD service for the hazo_file_quotas table.
2031
+ * Required if you want to use per-scope quota tracking.
2032
+ */
2033
+ quotaCrudService?: QuotaCrudServiceLike;
2034
+ /**
2035
+ * Callback fired when a quota threshold band is crossed.
2036
+ * Receives scopeId, percent crossed, bytesUsed, and byteLimit.
2037
+ */
2038
+ onQuotaThreshold?: (event: QuotaThresholdEvent) => void;
2039
+ /**
2040
+ * Fractional threshold bands that trigger onQuotaThreshold.
2041
+ * Default: [0.80, 0.95]
2042
+ */
2043
+ quotaBands?: number[];
1807
2044
  }
1808
2045
  /**
1809
2046
  * Result of createHazoFilesServer
@@ -1881,6 +2118,95 @@ declare function createHazoFilesServer(options?: HazoFilesServerOptions): Promis
1881
2118
  */
1882
2119
  declare function createBasicFileManager(config: HazoFilesConfig, crudService?: CrudServiceLike<FileMetadataRecord>): Promise<TrackedFileManager>;
1883
2120
 
2121
+ /**
2122
+ * Purge Job Handlers
2123
+ *
2124
+ * Factory for hazo_jobs-compatible job handlers that perform file lifecycle purge operations.
2125
+ * Uses types-only import from hazo_jobs — no runtime dependency.
2126
+ *
2127
+ * @example
2128
+ * ```typescript
2129
+ * import { createPurgeJobHandlers, HAZO_FILES_JOB_TYPES } from 'hazo_files/server';
2130
+ *
2131
+ * const handlers = createPurgeJobHandlers(fm, {
2132
+ * submitJob: (type, payload) => jobs.submit({ type, payload }),
2133
+ * });
2134
+ *
2135
+ * worker.register(HAZO_FILES_JOB_TYPES.PURGE_PLAN, handlers[HAZO_FILES_JOB_TYPES.PURGE_PLAN]);
2136
+ * worker.register(HAZO_FILES_JOB_TYPES.PURGE_ONE, handlers[HAZO_FILES_JOB_TYPES.PURGE_ONE]);
2137
+ * ```
2138
+ */
2139
+
2140
+ /**
2141
+ * Job type constants for hazo_files purge operations
2142
+ */
2143
+ declare const HAZO_FILES_JOB_TYPES: {
2144
+ readonly PURGE_PLAN: "hazo_files.purge_plan";
2145
+ readonly PURGE_ONE: "hazo_files.purge_one";
2146
+ };
2147
+ type HazoFilesJobType = typeof HAZO_FILES_JOB_TYPES[keyof typeof HAZO_FILES_JOB_TYPES];
2148
+ /**
2149
+ * Payload for the purge_plan job.
2150
+ * Finds soft-deleted files older than retentionDays and schedules purge_one jobs.
2151
+ */
2152
+ interface PurgePlanPayload {
2153
+ /** Number of days after soft-delete before a file is eligible for purge. Default: 30 */
2154
+ retentionDays?: number;
2155
+ /**
2156
+ * Dry-run mode: return wouldPurge list but do not submit any purge_one jobs
2157
+ * and do not delete anything.
2158
+ */
2159
+ dryRun?: boolean;
2160
+ }
2161
+ /**
2162
+ * Payload for the purge_one job.
2163
+ * Hard-deletes a single soft-deleted file record and its physical storage.
2164
+ */
2165
+ interface PurgeOnePayload {
2166
+ fileId: string;
2167
+ }
2168
+ /**
2169
+ * Result returned by the purge_plan handler
2170
+ */
2171
+ interface PurgePlanResult {
2172
+ /** Number of files purged (or queued for purge) */
2173
+ purgedCount: number;
2174
+ /**
2175
+ * Populated only when dryRun: true.
2176
+ * Array of fileIds that would be purged.
2177
+ */
2178
+ wouldPurge?: string[];
2179
+ }
2180
+ /**
2181
+ * Options for the purge job handler factory
2182
+ */
2183
+ interface PurgeJobHandlerOptions {
2184
+ /**
2185
+ * Function to submit a child job.
2186
+ * When provided, purge_plan submits individual purge_one jobs via this function.
2187
+ * When omitted, purge_plan returns the fileIds in the result and does not submit jobs.
2188
+ */
2189
+ submitJob?: (type: string, payload: unknown) => Promise<void>;
2190
+ /** Logger for diagnostics */
2191
+ logger?: {
2192
+ info?(message: string, data?: Record<string, unknown>): void;
2193
+ warn?(message: string, data?: Record<string, unknown>): void;
2194
+ error?(message: string, data?: Record<string, unknown>): void;
2195
+ };
2196
+ }
2197
+ /**
2198
+ * Create purge job handlers for hazo_files lifecycle management.
2199
+ *
2200
+ * The handlers are compatible with hazo_jobs JobHandler<TPayload, TResult>.
2201
+ *
2202
+ * @param fm - TrackedFileManager instance
2203
+ * @param opts - Optional submitJob function and logger
2204
+ */
2205
+ declare function createPurgeJobHandlers(fm: TrackedFileManager, opts?: PurgeJobHandlerOptions): {
2206
+ [HAZO_FILES_JOB_TYPES.PURGE_PLAN]: JobHandler<PurgePlanPayload, PurgePlanResult>;
2207
+ [HAZO_FILES_JOB_TYPES.PURGE_ONE]: JobHandler<PurgeOnePayload>;
2208
+ };
2209
+
1884
2210
  /**
1885
2211
  * Database Schema Exports for hazo_files
1886
2212
  *
@@ -1948,6 +2274,10 @@ interface HazoFilesColumnDefinitions {
1948
2274
  original_filename: 'TEXT';
1949
2275
  /** Content tag classifying the document type (V3) */
1950
2276
  content_tag: 'TEXT';
2277
+ /** UUID of the actor who last mutated this record (V4 — migration 004) */
2278
+ changed_by: 'TEXT' | 'UUID';
2279
+ /** Source URL when file was imported via importFromUrl (V4 — migration 005) */
2280
+ source_url: 'TEXT';
1951
2281
  }
1952
2282
  /**
1953
2283
  * Schema definition for a specific database type
@@ -2170,6 +2500,86 @@ declare const HAZO_FILES_MIGRATION_V3: HazoFilesMigrationV3;
2170
2500
  * Get V3 migration statements for a custom table name
2171
2501
  */
2172
2502
  declare function getMigrationV3ForTable(tableName: string, dbType: 'sqlite' | 'postgres'): MigrationSchemaDefinition;
2503
+ /**
2504
+ * Migration schema for adding V4 actor-tracking and source_url columns.
2505
+ *
2506
+ * @note PostgreSQL ALTER statements use `IF NOT EXISTS` and are idempotent.
2507
+ * SQLite `ALTER TABLE ADD COLUMN` does NOT support `IF NOT EXISTS` —
2508
+ * wrap each statement in a try/catch or check `PRAGMA table_info(hazo_files)`
2509
+ * before running on SQLite to avoid "duplicate column name" errors.
2510
+ *
2511
+ * @example
2512
+ * ```typescript
2513
+ * import { HAZO_FILES_MIGRATION_V4 } from 'hazo_files';
2514
+ *
2515
+ * // SQLite — wrap in try/catch (NOT idempotent)
2516
+ * for (const stmt of HAZO_FILES_MIGRATION_V4.sqlite.alterStatements) {
2517
+ * try { await db.run(stmt); } catch { /* column already exists *\/ }
2518
+ * }
2519
+ *
2520
+ * // PostgreSQL — idempotent (uses IF NOT EXISTS)
2521
+ * for (const stmt of HAZO_FILES_MIGRATION_V4.postgres.alterStatements) {
2522
+ * await client.query(stmt);
2523
+ * }
2524
+ * for (const idx of HAZO_FILES_MIGRATION_V4.postgres.indexes) {
2525
+ * await client.query(idx);
2526
+ * }
2527
+ * ```
2528
+ */
2529
+ interface HazoFilesMigrationV4 {
2530
+ /** Default table name */
2531
+ tableName: string;
2532
+ /** SQLite migration statements */
2533
+ sqlite: MigrationSchemaDefinition;
2534
+ /** PostgreSQL migration statements */
2535
+ postgres: MigrationSchemaDefinition;
2536
+ /** New column names added in V4 */
2537
+ newColumns: readonly string[];
2538
+ }
2539
+ declare const HAZO_FILES_MIGRATION_V4: HazoFilesMigrationV4;
2540
+ /**
2541
+ * Get V4 migration statements for a custom table name
2542
+ */
2543
+ declare function getMigrationV4ForTable(tableName: string, dbType: 'sqlite' | 'postgres'): MigrationSchemaDefinition;
2544
+ /**
2545
+ * Default table name for quota tracking
2546
+ */
2547
+ declare const HAZO_FILE_QUOTAS_DEFAULT_TABLE_NAME = "hazo_file_quotas";
2548
+ /**
2549
+ * Column definitions for the hazo_file_quotas table
2550
+ */
2551
+ interface HazoFileQuotasColumnDefinitions {
2552
+ /** Scope ID — primary key, links to organizational unit */
2553
+ scope_id: 'TEXT' | 'UUID';
2554
+ /** Maximum allowed bytes for this scope */
2555
+ byte_limit: 'INTEGER' | 'BIGINT';
2556
+ /** Current bytes used (reconciled against actual file records) */
2557
+ byte_used: 'INTEGER' | 'BIGINT';
2558
+ /** ISO timestamp of last update */
2559
+ updated_at: 'TEXT' | 'TIMESTAMP';
2560
+ }
2561
+ /**
2562
+ * Schema definition for hazo_file_quotas table.
2563
+ *
2564
+ * Opt-in per-scope quota. A scope without a row has no quota (fail-open).
2565
+ *
2566
+ * @example
2567
+ * ```typescript
2568
+ * import { HAZO_FILE_QUOTAS_TABLE_SCHEMA } from 'hazo_files';
2569
+ *
2570
+ * // PostgreSQL
2571
+ * await client.query(HAZO_FILE_QUOTAS_TABLE_SCHEMA.postgres.ddl);
2572
+ * for (const idx of HAZO_FILE_QUOTAS_TABLE_SCHEMA.postgres.indexes) {
2573
+ * await client.query(idx);
2574
+ * }
2575
+ * ```
2576
+ */
2577
+ declare const HAZO_FILE_QUOTAS_TABLE_SCHEMA: {
2578
+ tableName: string;
2579
+ sqlite: DatabaseSchemaDefinition;
2580
+ postgres: DatabaseSchemaDefinition;
2581
+ columns: readonly ["scope_id", "byte_limit", "byte_used", "updated_at"];
2582
+ };
2173
2583
 
2174
2584
  /**
2175
2585
  * Migration: Add Reference Tracking (V2)
@@ -2853,6 +3263,15 @@ declare class ConfigurationError extends HazoFilesError {
2853
3263
  declare class OperationError extends HazoFilesError {
2854
3264
  constructor(operation: string, message: string, details?: Record<string, unknown>);
2855
3265
  }
3266
+ declare class QuotaExceededError extends HazoFilesError {
3267
+ constructor(scopeId: string, byteUsed: number, byteLimit: number, deltaBytes: number);
3268
+ }
3269
+ declare class SSRFError extends HazoFilesError {
3270
+ constructor(url: string, reason: string);
3271
+ }
3272
+ declare class ImportSizeCapError extends HazoFilesError {
3273
+ constructor(url: string, capBytes: number);
3274
+ }
2856
3275
 
2857
3276
  /**
2858
3277
  * MIME type utilities
@@ -3225,4 +3644,4 @@ declare function toV2Record(record: FileMetadataRecord): FileMetadataRecordV2;
3225
3644
  */
3226
3645
  declare function buildFileWithStatus(record: FileMetadataRecord): FileWithStatus;
3227
3646
 
3228
- 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, type FileRefVisibility, type FileStatus, type FileSystemItem, FileTooLargeError, 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 HazoFilesServerInstance, type HazoFilesServerOptions, type HazoFilesTableSchema, type HazoLLMInstance, type HazoLogger, 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, 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, addExtractionToFileData, backfillV2Defaults, buildFileWithStatus, clearExtractions, clonePattern, computeFileHash, computeFileHashFromStream, computeFileHashSync, computeFileInfo, createAndInitializeModule, createBasicFileManager, createDropboxAuth, createDropboxModule, createEmptyFileDataStructure, createEmptyNamingRuleSchema, createFileItem, createFileManager, createFileMetadataService, createFileRef, createFolderItem, createGoogleDriveAuth, createGoogleDriveModule, createHazoFilesServer, 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, removeRefFromArray, removeRefsByCriteriaFromArray, sanitizeFilename, saveConfig, sortItems, stringifyFileData, stringifyFileRefs, successResult, toV2Record, updateExtractionById, validateExtractionData, validateFileDataStructure, validateNamingRuleSchema, validatePath };
3647
+ 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, type FileRefVisibility, type FileStatus, type FileSystemItem, FileTooLargeError, type FileWithStatus, type FindOrphanedOptions, type FolderItem, type GeneratedNameResult, type GoogleAuthConfig, GoogleDriveAuth, type GoogleDriveConfig, GoogleDriveModule, HAZO_FILES_DEFAULT_TABLE_NAME, HAZO_FILES_JOB_TYPES, HAZO_FILES_MIGRATION_V2, HAZO_FILES_MIGRATION_V3, HAZO_FILES_MIGRATION_V4, HAZO_FILES_NAMING_DEFAULT_TABLE_NAME, HAZO_FILES_NAMING_TABLE_SCHEMA, HAZO_FILES_TABLE_SCHEMA, HAZO_FILE_QUOTAS_DEFAULT_TABLE_NAME, HAZO_FILE_QUOTAS_TABLE_SCHEMA, type HazoFileQuotasColumnDefinitions, type HazoFilesColumnDefinitions, type HazoFilesConfig, HazoFilesError, type HazoFilesJobType, type HazoFilesMigrationV2, type HazoFilesMigrationV3, type HazoFilesMigrationV4, type HazoFilesNamingColumnDefinitions, type HazoFilesNamingTableSchema, type HazoFilesServerInstance, type HazoFilesServerOptions, type HazoFilesTableSchema, type HazoLLMInstance, type HazoLogger, ImportSizeCapError, 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, type NamingVariable, OperationError, type OperationResult, type ParsedNamingConvention, type PatternSegment, PermissionDeniedError, type ProgressCallback, type PurgeJobHandlerOptions, type PurgeOnePayload, type PurgePlanPayload, type PurgePlanResult, type QuotaCrudServiceLike, QuotaExceededError, QuotaService, type QuotaServiceOptions, type QuotaStatus, type QuotaThresholdEvent, type RemoveExtractionOptions, type RemoveRefsCriteria, type RenameOptions, SSRFError, 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, addExtractionToFileData, backfillV2Defaults, buildFileWithStatus, clearExtractions, clonePattern, computeFileHash, computeFileHashFromStream, computeFileHashSync, computeFileInfo, createAndInitializeModule, createBasicFileManager, createDropboxAuth, createDropboxModule, createEmptyFileDataStructure, createEmptyNamingRuleSchema, createFileItem, createFileManager, createFileMetadataService, createFileRef, createFolderItem, createGoogleDriveAuth, createGoogleDriveModule, createHazoFilesServer, createInitializedFileManager, createInitializedTrackedFileManager, createLLMExtractionService, createLiteralSegment, createLocalModule, createModule, createNamingConventionService, createPurgeJobHandlers, createQuotaService, 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, getMigrationV4ForTable, 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, removeRefFromArray, removeRefsByCriteriaFromArray, sanitizeFilename, saveConfig, sortItems, stringifyFileData, stringifyFileRefs, successResult, toV2Record, updateExtractionById, validateExtractionData, validateFileDataStructure, validateNamingRuleSchema, validatePath };