--- url: https://ficsysfr.github.io/nestjs_module_factorydrive/en/guide/ai.md description: >- Connect coding agents to Factorydrive llms.txt files and the documentation MCP server. --- # AI agents Factorydrive publishes English machine-readable documentation and a documentation-only MCP server. The MCP never receives disk configuration or storage credentials. ## `llms.txt` files | File | Role | | --- | --- | | [llms.txt](https://ficsysfr.github.io/nestjs_module_factorydrive/llms.txt) | Short index with links to Markdown pages | | [llms-full.txt](https://ficsysfr.github.io/nestjs_module_factorydrive/llms-full.txt) | Full English documentation bundle | Each English guide also has a `.md` route for targeted retrieval. ## MCP server Run the stdio server with: ```bash npx -y @ficsysfr/nestjs_module_factorydrive-mcp ``` It exposes: | Tool | Role | | --- | --- | | `list_doc_sources` | Read and parse the `llms.txt` index | | `search_docs` | Rank relevant pages from the index and full bundle | | `fetch_docs` | Fetch one approved documentation URL | ## Client configuration ```json { "mcpServers": { "factorydrive": { "command": "npx", "args": ["-y", "@ficsysfr/nestjs_module_factorydrive-mcp"] } } } ``` For a local documentation preview, set `DOCS_BASE_URL` to `http://127.0.0.1:4173`. Only `ficsysfr.github.io`, `127.0.0.1`, and `localhost` are accepted. ## Suggested workflow 1. Call `list_doc_sources` to discover available pages. 2. Call `search_docs` with the actual Factorydrive question. 3. Call `fetch_docs` on the most relevant result. 4. Answer from the retrieved API contract rather than inventing driver capabilities. --- --- url: >- https://ficsysfr.github.io/nestjs_module_factorydrive/en/guide/configuration.md description: >- Configure default and named Factorydrive disks synchronously or asynchronously. --- # Configuration Factorydrive accepts a default disk, a map of named disks, and an optional switch for the built-in local driver. ## Configuration contract ```ts interface StorageManagerConfig { default?: string disks?: Record registerLocalDriver?: boolean } ``` Every disk name is application-defined. Its `driver` value must match a registered driver key. Local storage is registered as `local` unless `registerLocalDriver: false` is set. ## Multiple local disks ```ts FactorydriveModule.forRoot({ default: 'documents', disks: { documents: { driver: 'local', config: { root: `${process.cwd()}/storage/documents` }, }, exports: { driver: 'local', config: { root: `${process.cwd()}/storage/exports` }, }, }, }) ``` Use `getDisk()` for `documents` and `getDisk('exports')` only for the explicit export storage use case. ## Asynchronous configuration Keep environment access at the module boundary: ```ts import { ConfigModule, ConfigService } from '@nestjs/config' import { FactorydriveModule } from '@ficsysfr/nestjs_module_factorydrive' FactorydriveModule.forRootAsync({ imports: [ConfigModule], inject: [ConfigService], useFactory: (config: ConfigService) => ({ default: config.get('FACTORYDRIVE_DEFAULT', 'local'), disks: { local: { driver: 'local', config: { root: config.get('FACTORYDRIVE_LOCAL_ROOT', `${process.cwd()}/storage`), }, }, }, }), }) ``` `forRootAsync()` also accepts `useClass` and `useExisting` through `FactorydriveModuleAsyncOptions`. ## Register external drivers Register each driver during application bootstrap before disks are initialized: ```ts import { FactorydriveService } from '@ficsysfr/nestjs_module_factorydrive' import { AwsS3Storage } from '@ficsysfr/nestjs_module_factorydrive-s3' export class AppModule { public constructor(factorydrive: FactorydriveService) { factorydrive.registerDriver('s3', AwsS3Storage) } } ``` The registration key (`s3`) must equal the configured `disks.*.driver` value. --- --- url: >- https://ficsysfr.github.io/nestjs_module_factorydrive/en/guide/custom-drivers.md description: Implement and register a custom Factorydrive AbstractStorage driver. --- # Custom drivers External drivers extend `AbstractStorage`. Override supported methods and let inherited methods throw `MethodNotSupportedException` for unsupported capabilities. ## Implement a driver ```ts import { AbstractStorage, type DeleteResponse, type Response, } from '@ficsysfr/nestjs_module_factorydrive' export class ExampleStorage extends AbstractStorage { public constructor(private readonly config: { namespace: string }) { super() } public async put(location: string, content: Buffer | NodeJS.ReadableStream | string): Promise { return { raw: { namespace: this.config.namespace, location, content } } } public async delete(location: string): Promise { return { raw: { location }, wasDeleted: true } } } ``` Keep provider clients, credentials, endpoints, and error translation inside the driver. Return portable fields such as `content`, `exists`, `path`, or `wasDeleted`; expose provider-specific results only under `raw`. ## Register the driver ```ts export class AppModule { public constructor(factorydrive: FactorydriveService) { factorydrive.registerDriver('example', ExampleStorage) } } ``` The `example` key must match the disk configuration. Registration must happen before Factorydrive initializes configured disks. ## Package a satellite driver * Use a separate `@ficsysfr/nestjs_module_factorydrive-*` package. * Peer-depend on `@ficsysfr/nestjs_module_factorydrive@^2.0.0`. * Keep the core free of provider SDK dependencies. * Test supported operations, provider error mapping, streams, pagination, and cleanup. --- --- url: https://ficsysfr.github.io/nestjs_module_factorydrive/en/guide/installation.md description: Install Factorydrive 2.0 and understand its portable storage architecture. --- # Installation and architecture Factorydrive is a NestJS storage abstraction. Applications configure named disks and use one common API whether data lives on a local filesystem, S3-compatible object storage, SFTP, or a custom backend. ## Requirements * Node.js 22 or newer * NestJS 6 through 11 * TypeScript 5 ## Install the core ```bash npm install @ficsysfr/nestjs_module_factorydrive ``` Install a satellite driver only when the application needs it: ```bash npm install @ficsysfr/nestjs_module_factorydrive-s3 npm install @ficsysfr/nestjs_module_factorydrive-sftp ``` ## Dependency direction Application services should inject `FactorydriveService`. They should not import `node:fs`, `S3Client`, or an SFTP client for persistent application files. ```text Business service | FactorydriveService | AbstractStorage / | \ local S3 SFTP ``` `FactorydriveService.getDisk()` resolves the configured default disk. Select a named disk only when the use case intentionally targets a specific storage destination. ## Minimal application module ```ts import { Module } from '@nestjs/common' import { FactorydriveModule } from '@ficsysfr/nestjs_module_factorydrive' @Module({ imports: [ FactorydriveModule.forRoot({ default: 'files', disks: { files: { driver: 'local', config: { root: `${process.cwd()}/storage` }, }, }, }), ], }) export class AppModule {} ``` Continue with [configuration](./configuration.md) or review the [2.0 scope migration](./migration.md). --- --- url: https://ficsysfr.github.io/nestjs_module_factorydrive/en/guide/drivers.md description: Compare the maintained local, S3, and SFTP Factorydrive drivers. --- # Local, S3, and SFTP drivers Use the common storage contract where capabilities overlap. Keep provider classes and configuration at the application bootstrap boundary. ## Capability matrix | Capability | Local | S3 | SFTP | | --- | --- | --- | --- | | `put`, `get`, `getBuffer` | Yes | Yes | Yes | | `copy`, `move`, `delete`, `exists` | Yes | Yes | Yes | | `getStat`, `getStream`, `flatList` | Yes | Yes | Yes | | `append`, `prepend` | Yes | No | No | | `getUrl` | Yes | No | No | | `getSignedUrl` | Yes | Yes | No | | `verifySignedUrl` | Yes | No | No | ## Local filesystem * Package: `@ficsysfr/nestjs_module_factorydrive` * Class: `LocalFileSystemStorage`, registered automatically as `local` * Required configuration: `root` * Optional URL configuration: `baseUrl`, `signatureSecret` Local storage is appropriate for a single host or an externally managed mounted filesystem. It does not expose an HTTP server. ## S3 * Package: `@ficsysfr/nestjs_module_factorydrive-s3` * Class: `AwsS3Storage` * Required configuration: `bucket` * Other options: AWS SDK v3 `S3ClientConfig`, including region, credentials, endpoint, path style, and provider compatibility settings S3 supports Amazon S3 and compatible providers. Its signed URL is validated by the provider, not by `verifySignedUrl()`. Source: ## SFTP * Package: `@ficsysfr/nestjs_module_factorydrive-sftp` * Class: `SFTPStorage` * Required configuration: remote `root` and `ssh2-sftp-client` connection `options` SFTP connects during `onStorageInit()`. Register it before module initialization. Prefer key-based authentication where the deployment environment supports it. Source: ## Selection guidance * Choose local storage for host-local or mounted persistence. * Choose S3 for object storage and provider-signed downloads. * Choose SFTP when an external system requires file exchange over SFTP. * Hide the selected provider behind the default disk whenever business behavior does not depend on it. --- --- url: https://ficsysfr.github.io/nestjs_module_factorydrive/en/guide/migration.md description: >- Migrate Factorydrive applications from the deprecated @tacxou packages to @ficsysfr 2.0.0. --- # Migration from `@tacxou` Factorydrive 2.0.0 moves every maintained package to the `@ficsysfr` npm scope. The TypeScript API and storage behavior are unchanged; package names and import specifiers are the breaking change. ## Package mapping | Deprecated package | Replacement | | --- | --- | | `@tacxou/nestjs_module_factorydrive` | `@ficsysfr/nestjs_module_factorydrive` | | `@tacxou/nestjs_module_factorydrive-s3` | `@ficsysfr/nestjs_module_factorydrive-s3` | | `@tacxou/nestjs_module_factorydrive-sftp` | `@ficsysfr/nestjs_module_factorydrive-sftp` | ## Migration steps 1. Remove every installed package in the deprecated scope. 2. Install core 2.0.0 and each required driver at 2.0.0 under `@ficsysfr`. 3. Replace package import specifiers in source, tests, mocks, and configuration. 4. Refresh the lockfile with the application's existing package manager. 5. Run the application's complete test and build suites. ```ts // Before import { FactorydriveService } from '@tacxou/nestjs_module_factorydrive' // After import { FactorydriveService } from '@ficsysfr/nestjs_module_factorydrive' ``` No compatibility shim is published. Deprecated packages remain installable for legacy applications but receive no 2.x updates. ## Maintainer deprecation step Run these commands only after all replacements are publicly installable and verified: ```bash npm deprecate "@tacxou/nestjs_module_factorydrive@*" "Moved to @ficsysfr/nestjs_module_factorydrive" npm deprecate "@tacxou/nestjs_module_factorydrive-s3@*" "Moved to @ficsysfr/nestjs_module_factorydrive-s3" npm deprecate "@tacxou/nestjs_module_factorydrive-sftp@*" "Moved to @ficsysfr/nestjs_module_factorydrive-sftp" ``` Deprecation is a manual rollout operation, not part of any release workflow. --- --- url: https://ficsysfr.github.io/nestjs_module_factorydrive/en/guide/operations.md description: >- Use the Factorydrive storage contract, response types, streams, listings, and errors. --- # Operations and errors Resolve a disk through `FactorydriveService` and consume the documented response fields. Treat `raw` as provider-specific diagnostic data. ## Application service pattern ```ts import { Injectable } from '@nestjs/common' import { FactorydriveService } from '@ficsysfr/nestjs_module_factorydrive' @Injectable() export class DocumentStorageService { public constructor(private readonly factorydrive: FactorydriveService) {} public async save(path: string, content: Buffer): Promise { await this.factorydrive.getDisk().put(path, content) } public async read(path: string): Promise { const { content } = await this.factorydrive.getDisk().getBuffer(path) return content } public async remove(path: string): Promise { const { wasDeleted } = await this.factorydrive.getDisk().delete(path) return wasDeleted } } ``` ## Common contract | Operation | Result | | --- | --- | | `put(location, content)` | `Promise` | | `get(location, encoding?)` | `Promise>` | | `getBuffer(location)` | `Promise>` | | `getStream(location)` | `Promise` | | `exists(location)` | `Promise` | | `delete(location)` | `Promise` | | `copy(src, dest)` / `move(src, dest)` | `Promise` | | `append(location, content)` / `prepend(location, content)` | `Promise` | | `getStat(location)` | `Promise` | | `flatList(prefix?)` | `AsyncIterable` | | `getUrl(location)` | `string` | | `getSignedUrl(location, options?)` | `Promise` | | `verifySignedUrl(location, params)` | `boolean` | Concrete drivers may inherit an unsupported method that throws `MethodNotSupportedException`. Check the [driver matrix](./drivers.md) first. ## Response fields * `Response`: `{ raw: unknown }` * `ContentResponse`: `{ content: T, raw: unknown }` * `ExistsResponse`: `{ exists: boolean, raw: unknown }` * `DeleteResponse`: `{ wasDeleted: boolean | null, raw: unknown }` * `StatResponse`: `{ size: number, modified: Date, raw: unknown }` * `FileListResponse`: `{ path: string, raw: unknown }` * `SignedUrlResponse`: `{ signedUrl: string, raw: unknown }` Do not treat `wasDeleted: null` as `false`; some providers cannot confirm deletion. ## Listings and streams ```ts for await (const { path } of this.factorydrive.getDisk().flatList('documents/')) { // Process one logical storage key. } const source = await sourceDisk.getStream('incoming/report.pdf') await destinationDisk.put('archive/report.pdf', source) ``` Validate logical storage keys at the application boundary. Do not pass unchecked absolute paths supplied by clients. ## Errors Factorydrive exports `InvalidConfigException`, `DriverNotSupportedException`, `FileNotFoundException`, `PermissionMissingException`, `MethodNotSupportedException`, `NoSuchBucketException`, and `UnknownException`. Map them at an HTTP, job, or application boundary without leaking credentials or raw provider failures. --- --- url: https://ficsysfr.github.io/nestjs_module_factorydrive/en/guide/signed-urls.md description: Generate and verify local or S3 signed URLs safely with Factorydrive. --- # Signed URLs Local and S3 signed URLs have different trust and serving models. ## Local signed URLs Configure both `baseUrl` and `signatureSecret`: ```ts { driver: 'local', config: { root: '/var/data', baseUrl: 'https://api.example.com/files', signatureSecret: process.env.STORAGE_URL_SECRET!, }, } ``` ```ts const { signedUrl } = await this.factorydrive .getDisk() .getSignedUrl('documents/report.pdf', { expiresIn: 3600 }) ``` `expiresIn` is expressed in seconds and defaults to 900. The URL contains an absolute expiry timestamp and an HMAC-SHA256 signature. Factorydrive does not serve the file. The application route must recover the exact storage key, parse `expires`, call `verifySignedUrl()`, and only then open the stream: ```ts const expires = Number(expiresValue) const disk = this.factorydrive.getDisk() if (!disk.verifySignedUrl(location, { expires, signature })) { throw new ForbiddenException('Invalid or expired file URL') } return disk.getStream(location) ``` Preserve application authorization checks when the signed URL is not intended to be a standalone bearer capability. Never recreate the HMAC in application code. ## S3 signed URLs The S3 driver delegates signing to AWS SDK v3: ```ts const { signedUrl } = await this.factorydrive .getDisk() .getSignedUrl('documents/report.pdf', { expiresIn: 60 }) ``` The storage provider validates the request. S3 does not implement `verifySignedUrl()`. The current SFTP driver implements neither signed URL operation.