@warlock.js/core 5.21.0 → 5.22.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.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
7
  > ⚠ **Versioning: `@warlock.js/*` does not follow SemVer strictly — breaking changes may ship in a minor.** This is a deliberate decision, not an oversight: the framework is pre-adoption and the cost of a major per behaviour fix currently outweighs the benefit. **Pin an exact version or a tilde range (`~4.13.0`) if you need to opt into changes rather than receive them.** Every breaking change is marked **BREAKING** in its entry and summarised in an _Upgrading_ section at the top of the release. **This policy will change once the framework has consumers beyond its author.**
8
8
 
9
+ ## 5.22.0 - 2026-09-25
10
+
11
+ ### Fixed
12
+
13
+ - Local storage results return paths with the configured prefix for writes and moves.
14
+
9
15
  ## 5.21.0 - 2026-09-25
10
16
 
11
17
  ### Added
@@ -62,7 +62,7 @@ var LocalDriver = class {
62
62
  const stats = await stat(absolutePath);
63
63
  const mimeType = options?.mimeType || this.guessMimeType(location);
64
64
  return {
65
- path: location,
65
+ path: this.applyPrefix(location),
66
66
  url: this.url(location),
67
67
  size: stats.size,
68
68
  hash,
@@ -99,7 +99,7 @@ var LocalDriver = class {
99
99
  const stats = await stat(absolutePath);
100
100
  const mimeType = options?.mimeType || this.guessMimeType(location);
101
101
  return {
102
- path: location,
102
+ path: this.applyPrefix(location),
103
103
  url: this.url(location),
104
104
  size: stats.size,
105
105
  hash,
@@ -122,7 +122,7 @@ var LocalDriver = class {
122
122
  const stats = await stat(absolutePath);
123
123
  const mimeType = options?.mimeType || this.guessMimeType(location);
124
124
  return {
125
- path: location,
125
+ path: this.applyPrefix(location),
126
126
  url: this.url(location),
127
127
  size: stats.size,
128
128
  hash,
@@ -321,7 +321,7 @@ var LocalDriver = class {
321
321
  const hash = await this.hashFile(toPath);
322
322
  const stats = await stat(toPath);
323
323
  return {
324
- path: to,
324
+ path: this.applyPrefix(to),
325
325
  url: this.url(to),
326
326
  size: stats.size,
327
327
  hash,
@@ -341,7 +341,7 @@ var LocalDriver = class {
341
341
  const hash = await this.hashFile(toPath);
342
342
  const stats = await stat(toPath);
343
343
  return {
344
- path: to,
344
+ path: this.applyPrefix(to),
345
345
  url: this.url(to),
346
346
  size: stats.size,
347
347
  hash,
@@ -1 +1 @@
1
- {"version":3,"file":"local-driver.mjs","names":["fsPromises"],"sources":["../../../../../../../../core/src/storage/drivers/local-driver.ts"],"sourcesContent":["import {\n directoryExistsAsync,\n ensureDirectoryAsync,\n fileExistsAsync,\n removeDirectoryAsync,\n unlinkAsync,\n} from \"@warlock.js/fs\";\nimport { ltrim } from \"@mongez/reinforcements\";\nimport crypto from \"crypto\";\nimport { createReadStream, createWriteStream, promises as fsPromises } from \"fs\";\nimport { copyFile, readFile, readdir, rename, stat, unlink, writeFile } from \"fs/promises\";\nimport { basename, dirname, join, resolve } from \"path\";\nimport { Transform, type Readable } from \"stream\";\nimport { pipeline } from \"stream/promises\";\nimport { UploadedFile } from \"../../http\";\nimport { storagePath } from \"../../utils/paths\";\nimport { url } from \"../../utils/urls\";\nimport { storageDriverContext } from \"../context/storage-driver-context\";\nimport { resolveWithinRoot } from \"../utils/contain-path\";\nimport { StorageError } from \"../utils/storage-error\";\nimport type {\n DeleteManyResult,\n ListOptions,\n LocalStorageDriverOptions,\n PutOptions,\n StorageDriverContract,\n StorageDriverType,\n StorageFileData,\n StorageFileInfo,\n TemporaryTokenPayload,\n TemporaryTokenValidation,\n} from \"../types\";\nimport { getMimeType } from \"../utils/mime\";\n\n/**\n * Local filesystem storage driver\n *\n * Stores files on the local filesystem with support for:\n * - File operations (put, get, delete, copy, move)\n * - Stream operations for large files\n * - Batch operations\n * - Signed temporary URLs\n */\nexport class LocalDriver implements StorageDriverContract {\n /**\n * Driver name\n */\n public readonly name: StorageDriverType = \"local\";\n\n /**\n * Root path for storage\n */\n protected root: string;\n\n /**\n * URL prefix for file URLs\n */\n protected urlPrefix: string = \"\";\n\n /**\n * URL prefix for temporary file URLs\n */\n protected temporaryUrlPrefix: string;\n\n /**\n * Secret key for signing temporary URLs\n */\n protected signatureKey?: string;\n\n public constructor(public options: LocalStorageDriverOptions = {}) {\n // Resolve the root to an absolute, normalized path up front so the\n // path-traversal containment check (resolveWithinRoot) has a stable\n // anchor to compare resolved locations against.\n this.root = resolve(options.root ?? storagePath());\n this.urlPrefix = options.urlPrefix ?? \"\";\n this.temporaryUrlPrefix = options.temporaryUrlPrefix ?? \"/temp-files\";\n this.signatureKey = options.signatureKey;\n }\n\n // ============================================================\n // Prefix Operations\n // ============================================================\n\n /**\n * Apply prefix to location path\n *\n * Priority: context prefix > driver options prefix > no prefix\n * This allows multi-tenant scenarios where context overrides driver config.\n *\n * @param location - Original location path\n * @returns Location with prefix applied if one exists\n */\n public applyPrefix(location: string): string {\n // Check context prefix first (highest priority)\n const contextPrefix = storageDriverContext.getPrefix();\n const prefix = contextPrefix || this.options.prefix;\n\n if (!prefix) {\n return location;\n }\n\n const cleanPrefix = prefix.replace(/\\/+$/, \"\");\n const cleanLocation = location.replace(/^\\/+/, \"\");\n\n // Avoid double-prefixing\n if (cleanLocation.startsWith(cleanPrefix + \"/\") || cleanLocation === cleanPrefix) {\n return cleanLocation;\n }\n\n return `${cleanPrefix}/${cleanLocation}`;\n }\n\n // ============================================================\n // Core File Operations\n // ============================================================\n\n /**\n * Put file to local storage\n */\n public async put(\n file: Buffer | string | UploadedFile,\n location: string,\n options?: PutOptions,\n ): Promise<StorageFileData> {\n const absolutePath = this.getAbsolutePath(location);\n\n await ensureDirectoryAsync(dirname(absolutePath));\n\n const fileBuffer = await this.toBuffer(file);\n const hash = this.calculateHash(fileBuffer);\n\n await writeFile(absolutePath, new Uint8Array(fileBuffer));\n\n\n const stats = await stat(absolutePath);\n const mimeType = options?.mimeType || this.guessMimeType(location);\n\n return {\n path: location,\n url: this.url(location),\n size: stats.size,\n hash,\n mimeType,\n driver: this.name,\n };\n }\n\n /**\n * Atomically put a file only if nothing exists at `location`.\n *\n * Content is written to a temp file in the same directory, then hard-linked\n * to the target. `link()` fails with EEXIST if the target exists, so exactly\n * one concurrent caller wins, the object never appears half-written, and the\n * check is atomic on NFS too. Returns `null` only on EEXIST; other errors throw.\n */\n public async putIfAbsent(\n file: Buffer | string,\n location: string,\n options?: PutOptions,\n ): Promise<StorageFileData | null> {\n const absolutePath = this.getAbsolutePath(location);\n const directory = dirname(absolutePath);\n\n await ensureDirectoryAsync(directory);\n\n // Unlike put(), a string here is CONTENT, never a source path (matches\n // the cloud driver): the objects this protects are small JSON bodies.\n const fileBuffer = typeof file === \"string\" ? Buffer.from(file) : file;\n const hash = this.calculateHash(fileBuffer);\n\n const tempPath = join(\n directory,\n `.${basename(absolutePath)}.${crypto.randomBytes(8).toString(\"hex\")}.tmp`,\n );\n\n try {\n await writeFile(tempPath, new Uint8Array(fileBuffer));\n\n try {\n await fsPromises.link(tempPath, absolutePath);\n } catch (error: any) {\n if (error?.code === \"EEXIST\") {\n return null;\n }\n\n throw error;\n }\n } finally {\n await unlink(tempPath).catch(() => undefined);\n }\n\n\n const stats = await stat(absolutePath);\n const mimeType = options?.mimeType || this.guessMimeType(location);\n\n return {\n path: location,\n url: this.url(location),\n size: stats.size,\n hash,\n mimeType,\n driver: this.name,\n };\n }\n\n /**\n * Put file from a readable stream (for large files)\n */\n public async putStream(\n stream: Readable,\n location: string,\n options?: PutOptions,\n ): Promise<StorageFileData> {\n const absolutePath = this.getAbsolutePath(location);\n\n await ensureDirectoryAsync(dirname(absolutePath));\n\n // Hash while streaming so large files are never read back into memory\n const hasher = crypto.createHash(\"sha256\");\n const hashTransform = new Transform({\n transform(chunk, _encoding, callback) {\n hasher.update(chunk);\n callback(null, chunk);\n },\n });\n\n const writeStream = createWriteStream(absolutePath);\n await pipeline(stream, hashTransform, writeStream);\n\n\n const hash = hasher.digest(\"hex\");\n const stats = await stat(absolutePath);\n const mimeType = options?.mimeType || this.guessMimeType(location);\n\n return {\n path: location,\n url: this.url(location),\n size: stats.size,\n hash,\n mimeType,\n driver: this.name,\n };\n }\n\n /**\n * Get file contents as Buffer\n */\n public async get(location: string): Promise<Buffer> {\n const absolutePath = this.getAbsolutePath(location);\n\n if (!(await fileExistsAsync(absolutePath))) {\n throw new Error(`File not found: ${location}`);\n }\n\n return readFile(absolutePath);\n }\n\n /**\n * Get file as a readable stream (for large files)\n */\n public async getStream(location: string): Promise<Readable> {\n const absolutePath = this.getAbsolutePath(location);\n\n if (!(await fileExistsAsync(absolutePath))) {\n throw new Error(`File not found: ${location}`);\n }\n\n return createReadStream(absolutePath);\n }\n\n /**\n * Delete a file\n */\n public async delete(location: string): Promise<boolean> {\n const absolutePath = this.getAbsolutePath(location);\n\n if (!(await fileExistsAsync(absolutePath))) {\n return false;\n }\n\n await unlinkAsync(absolutePath);\n\n\n return true;\n }\n\n /**\n * Delete multiple files at once\n */\n public async deleteMany(locations: string[]): Promise<DeleteManyResult[]> {\n const results: DeleteManyResult[] = [];\n\n for (const location of locations) {\n try {\n const deleted = await this.delete(location);\n results.push({ location, deleted });\n } catch (error) {\n results.push({\n location,\n deleted: false,\n error: error instanceof Error ? error.message : \"Unknown error\",\n });\n }\n }\n\n return results;\n }\n\n /**\n * Delete directory\n *\n * The directory path is resolved through {@link getAbsolutePath} so it is\n * contained within the storage root — the raw, caller-supplied argument is\n * never handed to the filesystem (preventing `../` traversal deletes).\n */\n public async deleteDirectory(directoryPath: string, options?: { allowRoot?: boolean }) {\n const absolutePath = this.getAbsolutePath(directoryPath);\n\n if (!options?.allowRoot && resolve(absolutePath) === this.root) {\n throw new StorageError(\n `Refusing to delete the storage root (\"${directoryPath}\"). Pass { allowRoot: true } to confirm.`,\n { context: { directoryPath } },\n );\n }\n\n await removeDirectoryAsync(absolutePath);\n\n return true;\n }\n\n /**\n * Check if file exists\n */\n public async exists(location: string): Promise<boolean> {\n const absolutePath = this.getAbsolutePath(location);\n return Boolean(await fileExistsAsync(absolutePath));\n }\n\n // ============================================================\n // URL Operations\n // ============================================================\n\n /**\n * Get public URL for file\n */\n public url(location: string): string {\n return url(this.urlPrefix + \"/\" + ltrim(this.applyPrefix(location), \"/\"));\n }\n\n /**\n * Get a temporary signed URL that expires\n * Returns a clean URL with encoded token: {temporaryUrlPrefix}/{token}\n *\n * @param location - File path\n * @param expiresIn - Seconds until expiration (default: 3600)\n */\n public async temporaryUrl(location: string, expiresIn = 3600): Promise<string> {\n if (!this.signatureKey) {\n throw new Error(\n \"Temporary URLs require a signatureKey in LocalDriver options. \" +\n \"Configure storage.drivers.local.signatureKey in your config.\",\n );\n }\n\n const token = this.encodeTemporaryToken(location, expiresIn);\n return `${this.temporaryUrlPrefix}/${token}`;\n }\n\n /**\n * Encode a temporary token containing path, expiry, and signature\n *\n * @param location - File path\n * @param expiresIn - Seconds until expiration\n */\n public encodeTemporaryToken(location: string, expiresIn: number): string {\n if (!this.signatureKey) {\n throw new Error(\"Temporary tokens require a signatureKey\");\n }\n\n const exp = Math.floor(Date.now() / 1000) + expiresIn;\n const sig = crypto\n .createHmac(\"sha256\", this.signatureKey)\n .update(`${location}:${exp}`)\n .digest(\"hex\");\n\n const payload: TemporaryTokenPayload = { path: location, exp, sig };\n const json = JSON.stringify(payload);\n\n // Use base64url encoding (URL-safe base64)\n return Buffer.from(json).toString(\"base64url\");\n }\n\n /**\n * Validate a temporary URL token\n * Returns a result object with validation status, file info, and convenience methods\n *\n * @param token - The token from the URL\n */\n public async validateTemporaryToken(token: string): Promise<TemporaryTokenValidation> {\n // Check signature key\n if (!this.signatureKey) {\n return { valid: false, error: \"missing_key\" };\n }\n\n // Decode token\n let payload: TemporaryTokenPayload;\n try {\n const json = Buffer.from(token, \"base64url\").toString(\"utf-8\");\n payload = JSON.parse(json);\n } catch {\n return { valid: false, error: \"invalid_token\" };\n }\n\n // Validate payload structure\n if (!payload.path || !payload.exp || !payload.sig) {\n return { valid: false, error: \"invalid_token\" };\n }\n\n // Check expiration\n const now = Math.floor(Date.now() / 1000);\n if (payload.exp < now) {\n return { valid: false, error: \"expired\" };\n }\n\n // Verify signature\n const expectedSig = crypto\n .createHmac(\"sha256\", this.signatureKey)\n .update(`${payload.path}:${payload.exp}`)\n .digest(\"hex\");\n\n const sigBuffer = Buffer.from(payload.sig, \"hex\");\n const expectedBuffer = Buffer.from(expectedSig, \"hex\");\n\n if (sigBuffer.length !== expectedBuffer.length) {\n return { valid: false, error: \"invalid_signature\" };\n }\n\n const isValidSig = crypto.timingSafeEqual(\n new Uint8Array(sigBuffer),\n new Uint8Array(expectedBuffer),\n );\n\n if (!isValidSig) {\n return { valid: false, error: \"invalid_signature\" };\n }\n\n // Check file exists\n const absolutePath = this.getAbsolutePath(payload.path);\n if (!(await fileExistsAsync(absolutePath))) {\n return { valid: false, error: \"file_not_found\" };\n }\n\n // Build successful result with convenience methods\n const result: TemporaryTokenValidation = {\n valid: true,\n path: payload.path,\n absolutePath,\n expiresAt: new Date(payload.exp * 1000),\n mimeType: this.guessMimeType(payload.path),\n driver: this,\n getFile: () => this.get(payload.path),\n getStream: () => this.getStream(payload.path),\n };\n\n return result;\n }\n\n // ============================================================\n // Metadata Operations\n // ============================================================\n\n /**\n * Get file info/metadata without downloading\n */\n public async metadata(location: string): Promise<StorageFileInfo> {\n const absolutePath = this.getAbsolutePath(location);\n\n if (!(await fileExistsAsync(absolutePath))) {\n throw new Error(`File not found: ${absolutePath}`);\n }\n\n const stats = await stat(absolutePath);\n const name = location.split(\"/\").pop() || \"\";\n\n return {\n path: location,\n name,\n size: stats.size,\n isDirectory: stats.isDirectory(),\n lastModified: stats.mtime,\n mimeType: this.guessMimeType(location),\n };\n }\n\n /**\n * Get file size in bytes (shortcut for metadata().size)\n */\n public async size(location: string): Promise<number> {\n const metadata = await this.metadata(location);\n return metadata.size;\n }\n\n // ============================================================\n // File Operations\n // ============================================================\n\n /**\n * Copy file to a new location\n */\n public async copy(from: string, to: string): Promise<StorageFileData> {\n const fromPath = this.getAbsolutePath(from);\n const toPath = this.getAbsolutePath(to);\n\n if (!(await fileExistsAsync(fromPath))) {\n throw new Error(`Source file not found: ${from}`);\n }\n\n await ensureDirectoryAsync(dirname(toPath));\n await copyFile(fromPath, toPath);\n\n\n const hash = await this.hashFile(toPath);\n const stats = await stat(toPath);\n\n return {\n path: to,\n url: this.url(to),\n size: stats.size,\n hash,\n mimeType: this.guessMimeType(to),\n driver: this.name,\n };\n }\n\n /**\n * Move file to a new location\n */\n public async move(from: string, to: string): Promise<StorageFileData> {\n const fromPath = this.getAbsolutePath(from);\n const toPath = this.getAbsolutePath(to);\n\n if (!(await fileExistsAsync(fromPath))) {\n throw new Error(`Source file not found: ${from}`);\n }\n\n await ensureDirectoryAsync(dirname(toPath));\n await rename(fromPath, toPath);\n\n\n const hash = await this.hashFile(toPath);\n const stats = await stat(toPath);\n\n return {\n path: to,\n url: this.url(to),\n size: stats.size,\n hash,\n mimeType: this.guessMimeType(to),\n driver: this.name,\n };\n }\n\n /**\n * List files in a directory\n */\n public async list(directory: string, options?: ListOptions): Promise<StorageFileInfo[]> {\n const absolutePath = this.getAbsolutePath(directory);\n const files: StorageFileInfo[] = [];\n\n // A listing target is a DIRECTORY, so guard with `directoryExistsAsync`.\n // `fileExistsAsync` resolves `false` for a folder, which would make `list`\n // always return [] for a real, populated directory.\n if (!(await directoryExistsAsync(absolutePath))) {\n return files;\n }\n\n const entries = await readdir(absolutePath, { withFileTypes: true });\n\n for (const entry of entries) {\n const entryPath = join(directory, entry.name);\n const entryStats = await stat(this.getAbsolutePath(entryPath));\n\n files.push({\n path: entryPath,\n name: entry.name,\n size: entryStats.size,\n isDirectory: entry.isDirectory(),\n lastModified: entryStats.mtime,\n mimeType: entry.isFile() ? this.guessMimeType(entry.name) : undefined,\n });\n\n // Recursively list subdirectories if requested\n if (options?.recursive && entry.isDirectory()) {\n const subFiles = await this.list(entryPath, options);\n files.push(...subFiles);\n }\n\n // Respect limit\n if (options?.limit && files.length >= options.limit) {\n break;\n }\n }\n\n return files;\n }\n\n // ============================================================\n // Path Operations\n // ============================================================\n\n /**\n * Get absolute filesystem path for a location\n */\n public path(location: string): string {\n return this.getAbsolutePath(location);\n }\n\n /**\n * Get the storage root directory\n */\n public getRoot(): string {\n return this.root;\n }\n\n // ============================================================\n // Utilities\n // ============================================================\n\n /**\n * Get absolute file path\n *\n * Routes through {@link resolveWithinRoot} so a location can never escape\n * the configured storage root via `..` traversal, an absolute path, or a\n * prefix breakout.\n */\n protected getAbsolutePath(location: string): string {\n const prefixedLocation = this.applyPrefix(location);\n const resolved = resolveWithinRoot(this.root, prefixedLocation);\n\n // The prefix is a containment boundary too: \"../t2/x\" under prefix \"t1\"\n // must not land in a sibling tenant's directory.\n const prefix = storageDriverContext.getPrefix() || this.options.prefix;\n\n if (prefix) {\n const prefixRoot = resolveWithinRoot(this.root, prefix.replace(/^[\\\\/]+|[\\\\/]+$/g, \"\"));\n resolveWithinRoot(prefixRoot, resolved);\n }\n\n return resolved;\n }\n\n /**\n * Convert various input types to Buffer\n */\n protected async toBuffer(file: Buffer | string | UploadedFile): Promise<Buffer> {\n if (Buffer.isBuffer(file)) {\n return file;\n }\n\n // A string is always content, never a source path\n if (typeof file === \"string\") {\n return Buffer.from(file);\n }\n\n return file.buffer();\n }\n\n /**\n * Calculate SHA-256 hash\n */\n protected calculateHash(buffer: Buffer): string {\n return crypto.createHash(\"sha256\").update(new Uint8Array(buffer)).digest(\"hex\");\n }\n\n /**\n * Calculate SHA-256 hash of a file by streaming it (constant memory)\n */\n protected async hashFile(absolutePath: string): Promise<string> {\n const hasher = crypto.createHash(\"sha256\");\n\n for await (const chunk of createReadStream(absolutePath)) {\n hasher.update(chunk);\n }\n\n return hasher.digest(\"hex\");\n }\n\n /**\n * Guess MIME type from file extension\n */\n protected guessMimeType(location: string): string {\n return getMimeType(location);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"local-driver.mjs","names":["fsPromises"],"sources":["../../../../../../../../core/src/storage/drivers/local-driver.ts"],"sourcesContent":["import {\n directoryExistsAsync,\n ensureDirectoryAsync,\n fileExistsAsync,\n removeDirectoryAsync,\n unlinkAsync,\n} from \"@warlock.js/fs\";\nimport { ltrim } from \"@mongez/reinforcements\";\nimport crypto from \"crypto\";\nimport { createReadStream, createWriteStream, promises as fsPromises } from \"fs\";\nimport { copyFile, readFile, readdir, rename, stat, unlink, writeFile } from \"fs/promises\";\nimport { basename, dirname, join, resolve } from \"path\";\nimport { Transform, type Readable } from \"stream\";\nimport { pipeline } from \"stream/promises\";\nimport { UploadedFile } from \"../../http\";\nimport { storagePath } from \"../../utils/paths\";\nimport { url } from \"../../utils/urls\";\nimport { storageDriverContext } from \"../context/storage-driver-context\";\nimport { resolveWithinRoot } from \"../utils/contain-path\";\nimport { StorageError } from \"../utils/storage-error\";\nimport type {\n DeleteManyResult,\n ListOptions,\n LocalStorageDriverOptions,\n PutOptions,\n StorageDriverContract,\n StorageDriverType,\n StorageFileData,\n StorageFileInfo,\n TemporaryTokenPayload,\n TemporaryTokenValidation,\n} from \"../types\";\nimport { getMimeType } from \"../utils/mime\";\n\n/**\n * Local filesystem storage driver\n *\n * Stores files on the local filesystem with support for:\n * - File operations (put, get, delete, copy, move)\n * - Stream operations for large files\n * - Batch operations\n * - Signed temporary URLs\n */\nexport class LocalDriver implements StorageDriverContract {\n /**\n * Driver name\n */\n public readonly name: StorageDriverType = \"local\";\n\n /**\n * Root path for storage\n */\n protected root: string;\n\n /**\n * URL prefix for file URLs\n */\n protected urlPrefix: string = \"\";\n\n /**\n * URL prefix for temporary file URLs\n */\n protected temporaryUrlPrefix: string;\n\n /**\n * Secret key for signing temporary URLs\n */\n protected signatureKey?: string;\n\n public constructor(public options: LocalStorageDriverOptions = {}) {\n // Resolve the root to an absolute, normalized path up front so the\n // path-traversal containment check (resolveWithinRoot) has a stable\n // anchor to compare resolved locations against.\n this.root = resolve(options.root ?? storagePath());\n this.urlPrefix = options.urlPrefix ?? \"\";\n this.temporaryUrlPrefix = options.temporaryUrlPrefix ?? \"/temp-files\";\n this.signatureKey = options.signatureKey;\n }\n\n // ============================================================\n // Prefix Operations\n // ============================================================\n\n /**\n * Apply prefix to location path\n *\n * Priority: context prefix > driver options prefix > no prefix\n * This allows multi-tenant scenarios where context overrides driver config.\n *\n * @param location - Original location path\n * @returns Location with prefix applied if one exists\n */\n public applyPrefix(location: string): string {\n // Check context prefix first (highest priority)\n const contextPrefix = storageDriverContext.getPrefix();\n const prefix = contextPrefix || this.options.prefix;\n\n if (!prefix) {\n return location;\n }\n\n const cleanPrefix = prefix.replace(/\\/+$/, \"\");\n const cleanLocation = location.replace(/^\\/+/, \"\");\n\n // Avoid double-prefixing\n if (cleanLocation.startsWith(cleanPrefix + \"/\") || cleanLocation === cleanPrefix) {\n return cleanLocation;\n }\n\n return `${cleanPrefix}/${cleanLocation}`;\n }\n\n // ============================================================\n // Core File Operations\n // ============================================================\n\n /**\n * Put file to local storage\n */\n public async put(\n file: Buffer | string | UploadedFile,\n location: string,\n options?: PutOptions,\n ): Promise<StorageFileData> {\n const absolutePath = this.getAbsolutePath(location);\n\n await ensureDirectoryAsync(dirname(absolutePath));\n\n const fileBuffer = await this.toBuffer(file);\n const hash = this.calculateHash(fileBuffer);\n\n await writeFile(absolutePath, new Uint8Array(fileBuffer));\n\n\n const stats = await stat(absolutePath);\n const mimeType = options?.mimeType || this.guessMimeType(location);\n\n return {\n path: this.applyPrefix(location),\n url: this.url(location),\n size: stats.size,\n hash,\n mimeType,\n driver: this.name,\n };\n }\n\n /**\n * Atomically put a file only if nothing exists at `location`.\n *\n * Content is written to a temp file in the same directory, then hard-linked\n * to the target. `link()` fails with EEXIST if the target exists, so exactly\n * one concurrent caller wins, the object never appears half-written, and the\n * check is atomic on NFS too. Returns `null` only on EEXIST; other errors throw.\n */\n public async putIfAbsent(\n file: Buffer | string,\n location: string,\n options?: PutOptions,\n ): Promise<StorageFileData | null> {\n const absolutePath = this.getAbsolutePath(location);\n const directory = dirname(absolutePath);\n\n await ensureDirectoryAsync(directory);\n\n // Unlike put(), a string here is CONTENT, never a source path (matches\n // the cloud driver): the objects this protects are small JSON bodies.\n const fileBuffer = typeof file === \"string\" ? Buffer.from(file) : file;\n const hash = this.calculateHash(fileBuffer);\n\n const tempPath = join(\n directory,\n `.${basename(absolutePath)}.${crypto.randomBytes(8).toString(\"hex\")}.tmp`,\n );\n\n try {\n await writeFile(tempPath, new Uint8Array(fileBuffer));\n\n try {\n await fsPromises.link(tempPath, absolutePath);\n } catch (error: any) {\n if (error?.code === \"EEXIST\") {\n return null;\n }\n\n throw error;\n }\n } finally {\n await unlink(tempPath).catch(() => undefined);\n }\n\n\n const stats = await stat(absolutePath);\n const mimeType = options?.mimeType || this.guessMimeType(location);\n\n return {\n path: this.applyPrefix(location),\n url: this.url(location),\n size: stats.size,\n hash,\n mimeType,\n driver: this.name,\n };\n }\n\n /**\n * Put file from a readable stream (for large files)\n */\n public async putStream(\n stream: Readable,\n location: string,\n options?: PutOptions,\n ): Promise<StorageFileData> {\n const absolutePath = this.getAbsolutePath(location);\n\n await ensureDirectoryAsync(dirname(absolutePath));\n\n // Hash while streaming so large files are never read back into memory\n const hasher = crypto.createHash(\"sha256\");\n const hashTransform = new Transform({\n transform(chunk, _encoding, callback) {\n hasher.update(chunk);\n callback(null, chunk);\n },\n });\n\n const writeStream = createWriteStream(absolutePath);\n await pipeline(stream, hashTransform, writeStream);\n\n\n const hash = hasher.digest(\"hex\");\n const stats = await stat(absolutePath);\n const mimeType = options?.mimeType || this.guessMimeType(location);\n\n return {\n path: this.applyPrefix(location),\n url: this.url(location),\n size: stats.size,\n hash,\n mimeType,\n driver: this.name,\n };\n }\n\n /**\n * Get file contents as Buffer\n */\n public async get(location: string): Promise<Buffer> {\n const absolutePath = this.getAbsolutePath(location);\n\n if (!(await fileExistsAsync(absolutePath))) {\n throw new Error(`File not found: ${location}`);\n }\n\n return readFile(absolutePath);\n }\n\n /**\n * Get file as a readable stream (for large files)\n */\n public async getStream(location: string): Promise<Readable> {\n const absolutePath = this.getAbsolutePath(location);\n\n if (!(await fileExistsAsync(absolutePath))) {\n throw new Error(`File not found: ${location}`);\n }\n\n return createReadStream(absolutePath);\n }\n\n /**\n * Delete a file\n */\n public async delete(location: string): Promise<boolean> {\n const absolutePath = this.getAbsolutePath(location);\n\n if (!(await fileExistsAsync(absolutePath))) {\n return false;\n }\n\n await unlinkAsync(absolutePath);\n\n\n return true;\n }\n\n /**\n * Delete multiple files at once\n */\n public async deleteMany(locations: string[]): Promise<DeleteManyResult[]> {\n const results: DeleteManyResult[] = [];\n\n for (const location of locations) {\n try {\n const deleted = await this.delete(location);\n results.push({ location, deleted });\n } catch (error) {\n results.push({\n location,\n deleted: false,\n error: error instanceof Error ? error.message : \"Unknown error\",\n });\n }\n }\n\n return results;\n }\n\n /**\n * Delete directory\n *\n * The directory path is resolved through {@link getAbsolutePath} so it is\n * contained within the storage root — the raw, caller-supplied argument is\n * never handed to the filesystem (preventing `../` traversal deletes).\n */\n public async deleteDirectory(directoryPath: string, options?: { allowRoot?: boolean }) {\n const absolutePath = this.getAbsolutePath(directoryPath);\n\n if (!options?.allowRoot && resolve(absolutePath) === this.root) {\n throw new StorageError(\n `Refusing to delete the storage root (\"${directoryPath}\"). Pass { allowRoot: true } to confirm.`,\n { context: { directoryPath } },\n );\n }\n\n await removeDirectoryAsync(absolutePath);\n\n return true;\n }\n\n /**\n * Check if file exists\n */\n public async exists(location: string): Promise<boolean> {\n const absolutePath = this.getAbsolutePath(location);\n return Boolean(await fileExistsAsync(absolutePath));\n }\n\n // ============================================================\n // URL Operations\n // ============================================================\n\n /**\n * Get public URL for file\n */\n public url(location: string): string {\n return url(this.urlPrefix + \"/\" + ltrim(this.applyPrefix(location), \"/\"));\n }\n\n /**\n * Get a temporary signed URL that expires\n * Returns a clean URL with encoded token: {temporaryUrlPrefix}/{token}\n *\n * @param location - File path\n * @param expiresIn - Seconds until expiration (default: 3600)\n */\n public async temporaryUrl(location: string, expiresIn = 3600): Promise<string> {\n if (!this.signatureKey) {\n throw new Error(\n \"Temporary URLs require a signatureKey in LocalDriver options. \" +\n \"Configure storage.drivers.local.signatureKey in your config.\",\n );\n }\n\n const token = this.encodeTemporaryToken(location, expiresIn);\n return `${this.temporaryUrlPrefix}/${token}`;\n }\n\n /**\n * Encode a temporary token containing path, expiry, and signature\n *\n * @param location - File path\n * @param expiresIn - Seconds until expiration\n */\n public encodeTemporaryToken(location: string, expiresIn: number): string {\n if (!this.signatureKey) {\n throw new Error(\"Temporary tokens require a signatureKey\");\n }\n\n const exp = Math.floor(Date.now() / 1000) + expiresIn;\n const sig = crypto\n .createHmac(\"sha256\", this.signatureKey)\n .update(`${location}:${exp}`)\n .digest(\"hex\");\n\n const payload: TemporaryTokenPayload = { path: location, exp, sig };\n const json = JSON.stringify(payload);\n\n // Use base64url encoding (URL-safe base64)\n return Buffer.from(json).toString(\"base64url\");\n }\n\n /**\n * Validate a temporary URL token\n * Returns a result object with validation status, file info, and convenience methods\n *\n * @param token - The token from the URL\n */\n public async validateTemporaryToken(token: string): Promise<TemporaryTokenValidation> {\n // Check signature key\n if (!this.signatureKey) {\n return { valid: false, error: \"missing_key\" };\n }\n\n // Decode token\n let payload: TemporaryTokenPayload;\n try {\n const json = Buffer.from(token, \"base64url\").toString(\"utf-8\");\n payload = JSON.parse(json);\n } catch {\n return { valid: false, error: \"invalid_token\" };\n }\n\n // Validate payload structure\n if (!payload.path || !payload.exp || !payload.sig) {\n return { valid: false, error: \"invalid_token\" };\n }\n\n // Check expiration\n const now = Math.floor(Date.now() / 1000);\n if (payload.exp < now) {\n return { valid: false, error: \"expired\" };\n }\n\n // Verify signature\n const expectedSig = crypto\n .createHmac(\"sha256\", this.signatureKey)\n .update(`${payload.path}:${payload.exp}`)\n .digest(\"hex\");\n\n const sigBuffer = Buffer.from(payload.sig, \"hex\");\n const expectedBuffer = Buffer.from(expectedSig, \"hex\");\n\n if (sigBuffer.length !== expectedBuffer.length) {\n return { valid: false, error: \"invalid_signature\" };\n }\n\n const isValidSig = crypto.timingSafeEqual(\n new Uint8Array(sigBuffer),\n new Uint8Array(expectedBuffer),\n );\n\n if (!isValidSig) {\n return { valid: false, error: \"invalid_signature\" };\n }\n\n // Check file exists\n const absolutePath = this.getAbsolutePath(payload.path);\n if (!(await fileExistsAsync(absolutePath))) {\n return { valid: false, error: \"file_not_found\" };\n }\n\n // Build successful result with convenience methods\n const result: TemporaryTokenValidation = {\n valid: true,\n path: payload.path,\n absolutePath,\n expiresAt: new Date(payload.exp * 1000),\n mimeType: this.guessMimeType(payload.path),\n driver: this,\n getFile: () => this.get(payload.path),\n getStream: () => this.getStream(payload.path),\n };\n\n return result;\n }\n\n // ============================================================\n // Metadata Operations\n // ============================================================\n\n /**\n * Get file info/metadata without downloading\n */\n public async metadata(location: string): Promise<StorageFileInfo> {\n const absolutePath = this.getAbsolutePath(location);\n\n if (!(await fileExistsAsync(absolutePath))) {\n throw new Error(`File not found: ${absolutePath}`);\n }\n\n const stats = await stat(absolutePath);\n const name = location.split(\"/\").pop() || \"\";\n\n return {\n path: location,\n name,\n size: stats.size,\n isDirectory: stats.isDirectory(),\n lastModified: stats.mtime,\n mimeType: this.guessMimeType(location),\n };\n }\n\n /**\n * Get file size in bytes (shortcut for metadata().size)\n */\n public async size(location: string): Promise<number> {\n const metadata = await this.metadata(location);\n return metadata.size;\n }\n\n // ============================================================\n // File Operations\n // ============================================================\n\n /**\n * Copy file to a new location\n */\n public async copy(from: string, to: string): Promise<StorageFileData> {\n const fromPath = this.getAbsolutePath(from);\n const toPath = this.getAbsolutePath(to);\n\n if (!(await fileExistsAsync(fromPath))) {\n throw new Error(`Source file not found: ${from}`);\n }\n\n await ensureDirectoryAsync(dirname(toPath));\n await copyFile(fromPath, toPath);\n\n\n const hash = await this.hashFile(toPath);\n const stats = await stat(toPath);\n\n return {\n path: this.applyPrefix(to),\n url: this.url(to),\n size: stats.size,\n hash,\n mimeType: this.guessMimeType(to),\n driver: this.name,\n };\n }\n\n /**\n * Move file to a new location\n */\n public async move(from: string, to: string): Promise<StorageFileData> {\n const fromPath = this.getAbsolutePath(from);\n const toPath = this.getAbsolutePath(to);\n\n if (!(await fileExistsAsync(fromPath))) {\n throw new Error(`Source file not found: ${from}`);\n }\n\n await ensureDirectoryAsync(dirname(toPath));\n await rename(fromPath, toPath);\n\n\n const hash = await this.hashFile(toPath);\n const stats = await stat(toPath);\n\n return {\n path: this.applyPrefix(to),\n url: this.url(to),\n size: stats.size,\n hash,\n mimeType: this.guessMimeType(to),\n driver: this.name,\n };\n }\n\n /**\n * List files in a directory\n */\n public async list(directory: string, options?: ListOptions): Promise<StorageFileInfo[]> {\n const absolutePath = this.getAbsolutePath(directory);\n const files: StorageFileInfo[] = [];\n\n // A listing target is a DIRECTORY, so guard with `directoryExistsAsync`.\n // `fileExistsAsync` resolves `false` for a folder, which would make `list`\n // always return [] for a real, populated directory.\n if (!(await directoryExistsAsync(absolutePath))) {\n return files;\n }\n\n const entries = await readdir(absolutePath, { withFileTypes: true });\n\n for (const entry of entries) {\n const entryPath = join(directory, entry.name);\n const entryStats = await stat(this.getAbsolutePath(entryPath));\n\n files.push({\n path: entryPath,\n name: entry.name,\n size: entryStats.size,\n isDirectory: entry.isDirectory(),\n lastModified: entryStats.mtime,\n mimeType: entry.isFile() ? this.guessMimeType(entry.name) : undefined,\n });\n\n // Recursively list subdirectories if requested\n if (options?.recursive && entry.isDirectory()) {\n const subFiles = await this.list(entryPath, options);\n files.push(...subFiles);\n }\n\n // Respect limit\n if (options?.limit && files.length >= options.limit) {\n break;\n }\n }\n\n return files;\n }\n\n // ============================================================\n // Path Operations\n // ============================================================\n\n /**\n * Get absolute filesystem path for a location\n */\n public path(location: string): string {\n return this.getAbsolutePath(location);\n }\n\n /**\n * Get the storage root directory\n */\n public getRoot(): string {\n return this.root;\n }\n\n // ============================================================\n // Utilities\n // ============================================================\n\n /**\n * Get absolute file path\n *\n * Routes through {@link resolveWithinRoot} so a location can never escape\n * the configured storage root via `..` traversal, an absolute path, or a\n * prefix breakout.\n */\n protected getAbsolutePath(location: string): string {\n const prefixedLocation = this.applyPrefix(location);\n const resolved = resolveWithinRoot(this.root, prefixedLocation);\n\n // The prefix is a containment boundary too: \"../t2/x\" under prefix \"t1\"\n // must not land in a sibling tenant's directory.\n const prefix = storageDriverContext.getPrefix() || this.options.prefix;\n\n if (prefix) {\n const prefixRoot = resolveWithinRoot(this.root, prefix.replace(/^[\\\\/]+|[\\\\/]+$/g, \"\"));\n resolveWithinRoot(prefixRoot, resolved);\n }\n\n return resolved;\n }\n\n /**\n * Convert various input types to Buffer\n */\n protected async toBuffer(file: Buffer | string | UploadedFile): Promise<Buffer> {\n if (Buffer.isBuffer(file)) {\n return file;\n }\n\n // A string is always content, never a source path\n if (typeof file === \"string\") {\n return Buffer.from(file);\n }\n\n return file.buffer();\n }\n\n /**\n * Calculate SHA-256 hash\n */\n protected calculateHash(buffer: Buffer): string {\n return crypto.createHash(\"sha256\").update(new Uint8Array(buffer)).digest(\"hex\");\n }\n\n /**\n * Calculate SHA-256 hash of a file by streaming it (constant memory)\n */\n protected async hashFile(absolutePath: string): Promise<string> {\n const hasher = crypto.createHash(\"sha256\");\n\n for await (const chunk of createReadStream(absolutePath)) {\n hasher.update(chunk);\n }\n\n return hasher.digest(\"hex\");\n }\n\n /**\n * Guess MIME type from file extension\n */\n protected guessMimeType(location: string): string {\n return getMimeType(location);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AA2CA,IAAa,cAAb,MAA0D;CA0BxD,AAAO,YAAY,AAAO,UAAqC,CAAC,GAAG;EAAzC;cAtBgB;mBAUZ;EAgB5B,KAAK,OAAO,QAAQ,QAAQ,QAAQ,YAAY,CAAC;EACjD,KAAK,YAAY,QAAQ,aAAa;EACtC,KAAK,qBAAqB,QAAQ,sBAAsB;EACxD,KAAK,eAAe,QAAQ;CAC9B;;;;;;;;;;CAeA,AAAO,YAAY,UAA0B;EAG3C,MAAM,SADgB,qBAAqB,UAChB,KAAK,KAAK,QAAQ;EAE7C,IAAI,CAAC,QACH,OAAO;EAGT,MAAM,cAAc,OAAO,QAAQ,QAAQ,EAAE;EAC7C,MAAM,gBAAgB,SAAS,QAAQ,QAAQ,EAAE;EAGjD,IAAI,cAAc,WAAW,cAAc,GAAG,KAAK,kBAAkB,aACnE,OAAO;EAGT,OAAO,GAAG,YAAY,GAAG;CAC3B;;;;CASA,MAAa,IACX,MACA,UACA,SAC0B;EAC1B,MAAM,eAAe,KAAK,gBAAgB,QAAQ;EAElD,MAAM,qBAAqB,QAAQ,YAAY,CAAC;EAEhD,MAAM,aAAa,MAAM,KAAK,SAAS,IAAI;EAC3C,MAAM,OAAO,KAAK,cAAc,UAAU;EAE1C,MAAM,UAAU,cAAc,IAAI,WAAW,UAAU,CAAC;EAGxD,MAAM,QAAQ,MAAM,KAAK,YAAY;EACrC,MAAM,WAAW,SAAS,YAAY,KAAK,cAAc,QAAQ;EAEjE,OAAO;GACL,MAAM,KAAK,YAAY,QAAQ;GAC/B,KAAK,KAAK,IAAI,QAAQ;GACtB,MAAM,MAAM;GACZ;GACA;GACA,QAAQ,KAAK;EACf;CACF;;;;;;;;;CAUA,MAAa,YACX,MACA,UACA,SACiC;EACjC,MAAM,eAAe,KAAK,gBAAgB,QAAQ;EAClD,MAAM,YAAY,QAAQ,YAAY;EAEtC,MAAM,qBAAqB,SAAS;EAIpC,MAAM,aAAa,OAAO,SAAS,WAAW,OAAO,KAAK,IAAI,IAAI;EAClE,MAAM,OAAO,KAAK,cAAc,UAAU;EAE1C,MAAM,WAAW,KACf,WACA,IAAI,SAAS,YAAY,EAAE,GAAG,OAAO,YAAY,CAAC,CAAC,CAAC,SAAS,KAAK,EAAE,KACtE;EAEA,IAAI;GACF,MAAM,UAAU,UAAU,IAAI,WAAW,UAAU,CAAC;GAEpD,IAAI;IACF,MAAMA,SAAW,KAAK,UAAU,YAAY;GAC9C,SAAS,OAAY;IACnB,IAAI,OAAO,SAAS,UAClB,OAAO;IAGT,MAAM;GACR;EACF,UAAU;GACR,MAAM,OAAO,QAAQ,CAAC,CAAC,YAAY,MAAS;EAC9C;EAGA,MAAM,QAAQ,MAAM,KAAK,YAAY;EACrC,MAAM,WAAW,SAAS,YAAY,KAAK,cAAc,QAAQ;EAEjE,OAAO;GACL,MAAM,KAAK,YAAY,QAAQ;GAC/B,KAAK,KAAK,IAAI,QAAQ;GACtB,MAAM,MAAM;GACZ;GACA;GACA,QAAQ,KAAK;EACf;CACF;;;;CAKA,MAAa,UACX,QACA,UACA,SAC0B;EAC1B,MAAM,eAAe,KAAK,gBAAgB,QAAQ;EAElD,MAAM,qBAAqB,QAAQ,YAAY,CAAC;EAGhD,MAAM,SAAS,OAAO,WAAW,QAAQ;EASzC,MAAM,SAAS,QAAQ,IARG,UAAU,EAClC,UAAU,OAAO,WAAW,UAAU;GACpC,OAAO,OAAO,KAAK;GACnB,SAAS,MAAM,KAAK;EACtB,EACF,CAGmC,GADf,kBAAkB,YACU,CAAC;EAGjD,MAAM,OAAO,OAAO,OAAO,KAAK;EAChC,MAAM,QAAQ,MAAM,KAAK,YAAY;EACrC,MAAM,WAAW,SAAS,YAAY,KAAK,cAAc,QAAQ;EAEjE,OAAO;GACL,MAAM,KAAK,YAAY,QAAQ;GAC/B,KAAK,KAAK,IAAI,QAAQ;GACtB,MAAM,MAAM;GACZ;GACA;GACA,QAAQ,KAAK;EACf;CACF;;;;CAKA,MAAa,IAAI,UAAmC;EAClD,MAAM,eAAe,KAAK,gBAAgB,QAAQ;EAElD,IAAI,CAAE,MAAM,gBAAgB,YAAY,GACtC,MAAM,IAAI,MAAM,mBAAmB,UAAU;EAG/C,OAAO,SAAS,YAAY;CAC9B;;;;CAKA,MAAa,UAAU,UAAqC;EAC1D,MAAM,eAAe,KAAK,gBAAgB,QAAQ;EAElD,IAAI,CAAE,MAAM,gBAAgB,YAAY,GACtC,MAAM,IAAI,MAAM,mBAAmB,UAAU;EAG/C,OAAO,iBAAiB,YAAY;CACtC;;;;CAKA,MAAa,OAAO,UAAoC;EACtD,MAAM,eAAe,KAAK,gBAAgB,QAAQ;EAElD,IAAI,CAAE,MAAM,gBAAgB,YAAY,GACtC,OAAO;EAGT,MAAM,YAAY,YAAY;EAG9B,OAAO;CACT;;;;CAKA,MAAa,WAAW,WAAkD;EACxE,MAAM,UAA8B,CAAC;EAErC,KAAK,MAAM,YAAY,WACrB,IAAI;GACF,MAAM,UAAU,MAAM,KAAK,OAAO,QAAQ;GAC1C,QAAQ,KAAK;IAAE;IAAU;GAAQ,CAAC;EACpC,SAAS,OAAO;GACd,QAAQ,KAAK;IACX;IACA,SAAS;IACT,OAAO,iBAAiB,QAAQ,MAAM,UAAU;GAClD,CAAC;EACH;EAGF,OAAO;CACT;;;;;;;;CASA,MAAa,gBAAgB,eAAuB,SAAmC;EACrF,MAAM,eAAe,KAAK,gBAAgB,aAAa;EAEvD,IAAI,CAAC,SAAS,aAAa,QAAQ,YAAY,MAAM,KAAK,MACxD,MAAM,IAAI,aACR,yCAAyC,cAAc,2CACvD,EAAE,SAAS,EAAE,cAAc,EAAE,CAC/B;EAGF,MAAM,qBAAqB,YAAY;EAEvC,OAAO;CACT;;;;CAKA,MAAa,OAAO,UAAoC;EACtD,MAAM,eAAe,KAAK,gBAAgB,QAAQ;EAClD,OAAO,QAAQ,MAAM,gBAAgB,YAAY,CAAC;CACpD;;;;CASA,AAAO,IAAI,UAA0B;EACnC,OAAO,IAAI,KAAK,YAAY,MAAM,MAAM,KAAK,YAAY,QAAQ,GAAG,GAAG,CAAC;CAC1E;;;;;;;;CASA,MAAa,aAAa,UAAkB,YAAY,MAAuB;EAC7E,IAAI,CAAC,KAAK,cACR,MAAM,IAAI,MACR,4HAEF;EAGF,MAAM,QAAQ,KAAK,qBAAqB,UAAU,SAAS;EAC3D,OAAO,GAAG,KAAK,mBAAmB,GAAG;CACvC;;;;;;;CAQA,AAAO,qBAAqB,UAAkB,WAA2B;EACvE,IAAI,CAAC,KAAK,cACR,MAAM,IAAI,MAAM,yCAAyC;EAG3D,MAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,IAAI;EAM5C,MAAM,UAAiC;GAAE,MAAM;GAAU;GAAK,KALlD,OACT,WAAW,UAAU,KAAK,YAAY,CAAC,CACvC,OAAO,GAAG,SAAS,GAAG,KAAK,CAAC,CAC5B,OAAO,KAEsD;EAAE;EAClE,MAAM,OAAO,KAAK,UAAU,OAAO;EAGnC,OAAO,OAAO,KAAK,IAAI,CAAC,CAAC,SAAS,WAAW;CAC/C;;;;;;;CAQA,MAAa,uBAAuB,OAAkD;EAEpF,IAAI,CAAC,KAAK,cACR,OAAO;GAAE,OAAO;GAAO,OAAO;EAAc;EAI9C,IAAI;EACJ,IAAI;GACF,MAAM,OAAO,OAAO,KAAK,OAAO,WAAW,CAAC,CAAC,SAAS,OAAO;GAC7D,UAAU,KAAK,MAAM,IAAI;EAC3B,QAAQ;GACN,OAAO;IAAE,OAAO;IAAO,OAAO;GAAgB;EAChD;EAGA,IAAI,CAAC,QAAQ,QAAQ,CAAC,QAAQ,OAAO,CAAC,QAAQ,KAC5C,OAAO;GAAE,OAAO;GAAO,OAAO;EAAgB;EAIhD,MAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;EACxC,IAAI,QAAQ,MAAM,KAChB,OAAO;GAAE,OAAO;GAAO,OAAO;EAAU;EAI1C,MAAM,cAAc,OACjB,WAAW,UAAU,KAAK,YAAY,CAAC,CACvC,OAAO,GAAG,QAAQ,KAAK,GAAG,QAAQ,KAAK,CAAC,CACxC,OAAO,KAAK;EAEf,MAAM,YAAY,OAAO,KAAK,QAAQ,KAAK,KAAK;EAChD,MAAM,iBAAiB,OAAO,KAAK,aAAa,KAAK;EAErD,IAAI,UAAU,WAAW,eAAe,QACtC,OAAO;GAAE,OAAO;GAAO,OAAO;EAAoB;EAQpD,IAAI,CALe,OAAO,gBACxB,IAAI,WAAW,SAAS,GACxB,IAAI,WAAW,cAAc,CAGjB,GACZ,OAAO;GAAE,OAAO;GAAO,OAAO;EAAoB;EAIpD,MAAM,eAAe,KAAK,gBAAgB,QAAQ,IAAI;EACtD,IAAI,CAAE,MAAM,gBAAgB,YAAY,GACtC,OAAO;GAAE,OAAO;GAAO,OAAO;EAAiB;EAejD,OAAO;GAVL,OAAO;GACP,MAAM,QAAQ;GACd;GACA,2BAAW,IAAI,KAAK,QAAQ,MAAM,GAAI;GACtC,UAAU,KAAK,cAAc,QAAQ,IAAI;GACzC,QAAQ;GACR,eAAe,KAAK,IAAI,QAAQ,IAAI;GACpC,iBAAiB,KAAK,UAAU,QAAQ,IAAI;EAGlC;CACd;;;;CASA,MAAa,SAAS,UAA4C;EAChE,MAAM,eAAe,KAAK,gBAAgB,QAAQ;EAElD,IAAI,CAAE,MAAM,gBAAgB,YAAY,GACtC,MAAM,IAAI,MAAM,mBAAmB,cAAc;EAGnD,MAAM,QAAQ,MAAM,KAAK,YAAY;EAGrC,OAAO;GACL,MAAM;GACN,MAJW,SAAS,MAAM,GAAG,CAAC,CAAC,IAAI,KAAK;GAKxC,MAAM,MAAM;GACZ,aAAa,MAAM,YAAY;GAC/B,cAAc,MAAM;GACpB,UAAU,KAAK,cAAc,QAAQ;EACvC;CACF;;;;CAKA,MAAa,KAAK,UAAmC;EAEnD,QAAO,MADgB,KAAK,SAAS,QAAQ,EAC9B,CAAC;CAClB;;;;CASA,MAAa,KAAK,MAAc,IAAsC;EACpE,MAAM,WAAW,KAAK,gBAAgB,IAAI;EAC1C,MAAM,SAAS,KAAK,gBAAgB,EAAE;EAEtC,IAAI,CAAE,MAAM,gBAAgB,QAAQ,GAClC,MAAM,IAAI,MAAM,0BAA0B,MAAM;EAGlD,MAAM,qBAAqB,QAAQ,MAAM,CAAC;EAC1C,MAAM,SAAS,UAAU,MAAM;EAG/B,MAAM,OAAO,MAAM,KAAK,SAAS,MAAM;EACvC,MAAM,QAAQ,MAAM,KAAK,MAAM;EAE/B,OAAO;GACL,MAAM,KAAK,YAAY,EAAE;GACzB,KAAK,KAAK,IAAI,EAAE;GAChB,MAAM,MAAM;GACZ;GACA,UAAU,KAAK,cAAc,EAAE;GAC/B,QAAQ,KAAK;EACf;CACF;;;;CAKA,MAAa,KAAK,MAAc,IAAsC;EACpE,MAAM,WAAW,KAAK,gBAAgB,IAAI;EAC1C,MAAM,SAAS,KAAK,gBAAgB,EAAE;EAEtC,IAAI,CAAE,MAAM,gBAAgB,QAAQ,GAClC,MAAM,IAAI,MAAM,0BAA0B,MAAM;EAGlD,MAAM,qBAAqB,QAAQ,MAAM,CAAC;EAC1C,MAAM,OAAO,UAAU,MAAM;EAG7B,MAAM,OAAO,MAAM,KAAK,SAAS,MAAM;EACvC,MAAM,QAAQ,MAAM,KAAK,MAAM;EAE/B,OAAO;GACL,MAAM,KAAK,YAAY,EAAE;GACzB,KAAK,KAAK,IAAI,EAAE;GAChB,MAAM,MAAM;GACZ;GACA,UAAU,KAAK,cAAc,EAAE;GAC/B,QAAQ,KAAK;EACf;CACF;;;;CAKA,MAAa,KAAK,WAAmB,SAAmD;EACtF,MAAM,eAAe,KAAK,gBAAgB,SAAS;EACnD,MAAM,QAA2B,CAAC;EAKlC,IAAI,CAAE,MAAM,qBAAqB,YAAY,GAC3C,OAAO;EAGT,MAAM,UAAU,MAAM,QAAQ,cAAc,EAAE,eAAe,KAAK,CAAC;EAEnE,KAAK,MAAM,SAAS,SAAS;GAC3B,MAAM,YAAY,KAAK,WAAW,MAAM,IAAI;GAC5C,MAAM,aAAa,MAAM,KAAK,KAAK,gBAAgB,SAAS,CAAC;GAE7D,MAAM,KAAK;IACT,MAAM;IACN,MAAM,MAAM;IACZ,MAAM,WAAW;IACjB,aAAa,MAAM,YAAY;IAC/B,cAAc,WAAW;IACzB,UAAU,MAAM,OAAO,IAAI,KAAK,cAAc,MAAM,IAAI,IAAI;GAC9D,CAAC;GAGD,IAAI,SAAS,aAAa,MAAM,YAAY,GAAG;IAC7C,MAAM,WAAW,MAAM,KAAK,KAAK,WAAW,OAAO;IACnD,MAAM,KAAK,GAAG,QAAQ;GACxB;GAGA,IAAI,SAAS,SAAS,MAAM,UAAU,QAAQ,OAC5C;EAEJ;EAEA,OAAO;CACT;;;;CASA,AAAO,KAAK,UAA0B;EACpC,OAAO,KAAK,gBAAgB,QAAQ;CACtC;;;;CAKA,AAAO,UAAkB;EACvB,OAAO,KAAK;CACd;;;;;;;;CAaA,AAAU,gBAAgB,UAA0B;EAClD,MAAM,mBAAmB,KAAK,YAAY,QAAQ;EAClD,MAAM,WAAW,kBAAkB,KAAK,MAAM,gBAAgB;EAI9D,MAAM,SAAS,qBAAqB,UAAU,KAAK,KAAK,QAAQ;EAEhE,IAAI,QAEF,kBADmB,kBAAkB,KAAK,MAAM,OAAO,QAAQ,oBAAoB,EAAE,CAC1D,GAAG,QAAQ;EAGxC,OAAO;CACT;;;;CAKA,MAAgB,SAAS,MAAuD;EAC9E,IAAI,OAAO,SAAS,IAAI,GACtB,OAAO;EAIT,IAAI,OAAO,SAAS,UAClB,OAAO,OAAO,KAAK,IAAI;EAGzB,OAAO,KAAK,OAAO;CACrB;;;;CAKA,AAAU,cAAc,QAAwB;EAC9C,OAAO,OAAO,WAAW,QAAQ,CAAC,CAAC,OAAO,IAAI,WAAW,MAAM,CAAC,CAAC,CAAC,OAAO,KAAK;CAChF;;;;CAKA,MAAgB,SAAS,cAAuC;EAC9D,MAAM,SAAS,OAAO,WAAW,QAAQ;EAEzC,WAAW,MAAM,SAAS,iBAAiB,YAAY,GACrD,OAAO,OAAO,KAAK;EAGrB,OAAO,OAAO,OAAO,KAAK;CAC5B;;;;CAKA,AAAU,cAAc,UAA0B;EAChD,OAAO,YAAY,QAAQ;CAC7B;AACF"}
package/package.json CHANGED
@@ -14,23 +14,23 @@
14
14
  "@fastify/multipart": "^9.3.0",
15
15
  "@fastify/rate-limit": "^10.3.0",
16
16
  "@fastify/static": "^8.3.0",
17
- "@mongez/concat-route": "^1.2.0",
18
- "@mongez/config": "^1.2.1",
19
- "@mongez/copper": "^2.1.2",
20
- "@mongez/dotenv": "^1.3.2",
21
- "@mongez/events": "^2.2.7",
17
+ "@mongez/concat-route": "^1.2.1",
18
+ "@mongez/config": "^1.2.2",
19
+ "@mongez/copper": "^2.1.3",
20
+ "@mongez/dotenv": "^1.3.3",
21
+ "@mongez/events": "^2.2.8",
22
22
  "@mongez/http": "^3.5.1",
23
23
  "@mongez/localization": "^3.5.1",
24
24
  "@mongez/reinforcements": "^4.0.2",
25
25
  "@mongez/slug": "^1.0.7",
26
26
  "@mongez/supportive-is": "^2.1.5",
27
27
  "@mongez/time-wizard": "^1.0.6",
28
- "@warlock.js/cache": "5.21.0",
29
- "@warlock.js/cascade": "5.21.0",
30
- "@warlock.js/context": "5.21.0",
31
- "@warlock.js/fs": "5.21.0",
32
- "@warlock.js/logger": "5.21.0",
33
- "@warlock.js/seal": "5.21.0",
28
+ "@warlock.js/cache": "5.22.0",
29
+ "@warlock.js/cascade": "5.22.0",
30
+ "@warlock.js/context": "5.22.0",
31
+ "@warlock.js/fs": "5.22.0",
32
+ "@warlock.js/logger": "5.22.0",
33
+ "@warlock.js/seal": "5.22.0",
34
34
  "bcryptjs": "^3.0.3",
35
35
  "chokidar": "^5.0.0",
36
36
  "dayjs": "^1.11.19",
@@ -52,10 +52,10 @@
52
52
  "@aws-sdk/lib-storage": "^3.955.0",
53
53
  "@aws-sdk/s3-request-presigner": "^3.955.0",
54
54
  "@react-email/render": "^2.0.5",
55
- "@warlock.js/access": "5.21.0",
56
- "@warlock.js/ai": "5.21.0",
57
- "@warlock.js/herald": "5.21.0",
58
- "@warlock.js/notifications": "5.21.0",
55
+ "@warlock.js/access": "5.22.0",
56
+ "@warlock.js/ai": "5.22.0",
57
+ "@warlock.js/herald": "5.22.0",
58
+ "@warlock.js/notifications": "5.22.0",
59
59
  "nodemailer": "^8.0.5",
60
60
  "react": "^19.2.3",
61
61
  "react-dom": "^19.2.3",
@@ -123,7 +123,7 @@
123
123
  ],
124
124
  "author": "hassanzohdy",
125
125
  "license": "MIT",
126
- "version": "5.21.0",
126
+ "version": "5.22.0",
127
127
  "type": "module",
128
128
  "main": "./esm/index.mjs",
129
129
  "module": "./esm/index.mjs",