@mantiq/filesystem 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,19 @@
1
+ # @mantiq/filesystem
2
+
3
+ Filesystem abstraction for MantiqJS — Local, S3, GCS, Azure Blob, FTP, and SFTP drivers.
4
+
5
+ Part of [MantiqJS](https://github.com/abdullahkhan/mantiq) — a batteries-included TypeScript web framework for Bun.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ bun add @mantiq/filesystem
11
+ ```
12
+
13
+ ## Documentation
14
+
15
+ See the [MantiqJS repository](https://github.com/abdullahkhan/mantiq) for full documentation.
16
+
17
+ ## License
18
+
19
+ MIT
package/package.json ADDED
@@ -0,0 +1,83 @@
1
+ {
2
+ "name": "@mantiq/filesystem",
3
+ "version": "0.0.1",
4
+ "description": "Filesystem abstraction — local, S3, GCS, R2, Azure, FTP, SFTP drivers with uniform API",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "Abdullah Khan",
8
+ "homepage": "https://github.com/abdullahkhan/mantiq/tree/main/packages/filesystem",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/abdullahkhan/mantiq.git",
12
+ "directory": "packages/filesystem"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/abdullahkhan/mantiq/issues"
16
+ },
17
+ "keywords": [
18
+ "mantiq",
19
+ "mantiqjs",
20
+ "bun",
21
+ "typescript",
22
+ "framework",
23
+ "filesystem"
24
+ ],
25
+ "engines": {
26
+ "bun": ">=1.1.0"
27
+ },
28
+ "main": "./src/index.ts",
29
+ "types": "./src/index.ts",
30
+ "exports": {
31
+ ".": {
32
+ "bun": "./src/index.ts",
33
+ "default": "./src/index.ts"
34
+ }
35
+ },
36
+ "files": [
37
+ "src/",
38
+ "package.json",
39
+ "README.md",
40
+ "LICENSE"
41
+ ],
42
+ "scripts": {
43
+ "build": "bun build ./src/index.ts --outdir ./dist --target bun",
44
+ "test": "bun test",
45
+ "typecheck": "tsc --noEmit",
46
+ "clean": "rm -rf dist"
47
+ },
48
+ "dependencies": {
49
+ "@mantiq/core": "workspace:*"
50
+ },
51
+ "devDependencies": {
52
+ "bun-types": "latest",
53
+ "typescript": "^5.7.0"
54
+ },
55
+ "peerDependencies": {
56
+ "@aws-sdk/client-s3": "^3.0.0",
57
+ "@aws-sdk/s3-request-presigner": "^3.0.0",
58
+ "@google-cloud/storage": "^7.0.0",
59
+ "@azure/storage-blob": "^12.0.0",
60
+ "basic-ftp": "^5.0.0",
61
+ "ssh2-sftp-client": "^11.0.0"
62
+ },
63
+ "peerDependenciesMeta": {
64
+ "@aws-sdk/client-s3": {
65
+ "optional": true
66
+ },
67
+ "@aws-sdk/s3-request-presigner": {
68
+ "optional": true
69
+ },
70
+ "@google-cloud/storage": {
71
+ "optional": true
72
+ },
73
+ "@azure/storage-blob": {
74
+ "optional": true
75
+ },
76
+ "basic-ftp": {
77
+ "optional": true
78
+ },
79
+ "ssh2-sftp-client": {
80
+ "optional": true
81
+ }
82
+ }
83
+ }
@@ -0,0 +1,177 @@
1
+ import type { DriverManager } from '@mantiq/core'
2
+ import type { FilesystemDriver, PutOptions } from './contracts/FilesystemDriver.ts'
3
+ import type { FilesystemConfig, DiskConfig } from './contracts/FilesystemConfig.ts'
4
+ import { LocalDriver } from './drivers/LocalDriver.ts'
5
+ import { NullDriver } from './drivers/NullDriver.ts'
6
+ import { S3Driver } from './drivers/S3Driver.ts'
7
+ import { GCSDriver } from './drivers/GCSDriver.ts'
8
+ import { AzureBlobDriver } from './drivers/AzureBlobDriver.ts'
9
+ import { FTPDriver } from './drivers/FTPDriver.ts'
10
+ import { SFTPDriver } from './drivers/SFTPDriver.ts'
11
+
12
+ export class FilesystemManager implements DriverManager<FilesystemDriver>, FilesystemDriver {
13
+ private readonly config: FilesystemConfig
14
+ private readonly disks = new Map<string, FilesystemDriver>()
15
+ private readonly customCreators = new Map<string, (config: DiskConfig) => FilesystemDriver>()
16
+
17
+ constructor(config?: Partial<FilesystemConfig>) {
18
+ this.config = {
19
+ default: config?.default ?? 'local',
20
+ disks: config?.disks ?? {},
21
+ }
22
+ }
23
+
24
+ // ── DriverManager ─────────────────────────────────────────────────────────
25
+
26
+ driver(name?: string): FilesystemDriver {
27
+ const diskName = name ?? this.getDefaultDriver()
28
+
29
+ if (!this.disks.has(diskName)) {
30
+ this.disks.set(diskName, this.createDriver(diskName))
31
+ }
32
+
33
+ return this.disks.get(diskName)!
34
+ }
35
+
36
+ disk(name?: string): FilesystemDriver {
37
+ return this.driver(name)
38
+ }
39
+
40
+ extend(name: string, factory: (config: DiskConfig) => FilesystemDriver): void {
41
+ this.customCreators.set(name, factory)
42
+ }
43
+
44
+ getDefaultDriver(): string {
45
+ return this.config.default
46
+ }
47
+
48
+ forgetDisk(name: string): void {
49
+ this.disks.delete(name)
50
+ }
51
+
52
+ forgetDisks(): void {
53
+ this.disks.clear()
54
+ }
55
+
56
+ // ── FilesystemDriver (delegates to default disk) ──────────────────────────
57
+
58
+ exists(path: string) { return this.driver().exists(path) }
59
+ get(path: string) { return this.driver().get(path) }
60
+ getBytes(path: string) { return this.driver().getBytes(path) }
61
+ stream(path: string) { return this.driver().stream(path) }
62
+
63
+ put(path: string, contents: string | Uint8Array, options?: PutOptions) { return this.driver().put(path, contents, options) }
64
+ putStream(path: string, stream: ReadableStream, options?: PutOptions) { return this.driver().putStream(path, stream, options) }
65
+ append(path: string, contents: string) { return this.driver().append(path, contents) }
66
+ prepend(path: string, contents: string) { return this.driver().prepend(path, contents) }
67
+
68
+ delete(path: string | string[]) { return this.driver().delete(path) }
69
+ copy(from: string, to: string) { return this.driver().copy(from, to) }
70
+ move(from: string, to: string) { return this.driver().move(from, to) }
71
+
72
+ size(path: string) { return this.driver().size(path) }
73
+ lastModified(path: string) { return this.driver().lastModified(path) }
74
+ mimeType(path: string) { return this.driver().mimeType(path) }
75
+ path(filePath: string) { return this.driver().path(filePath) }
76
+
77
+ url(path: string) { return this.driver().url(path) }
78
+ temporaryUrl(path: string, expiration: number, options?: Record<string, any>) { return this.driver().temporaryUrl(path, expiration, options) }
79
+
80
+ files(directory?: string) { return this.driver().files(directory) }
81
+ allFiles(directory?: string) { return this.driver().allFiles(directory) }
82
+ directories(directory?: string) { return this.driver().directories(directory) }
83
+ allDirectories(directory?: string) { return this.driver().allDirectories(directory) }
84
+ makeDirectory(path: string) { return this.driver().makeDirectory(path) }
85
+ deleteDirectory(directory: string) { return this.driver().deleteDirectory(directory) }
86
+
87
+ setVisibility(path: string, visibility: 'public' | 'private') { return this.driver().setVisibility(path, visibility) }
88
+ getVisibility(path: string) { return this.driver().getVisibility(path) }
89
+
90
+ // ── Internal ──────────────────────────────────────────────────────────────
91
+
92
+ private createDriver(name: string): FilesystemDriver {
93
+ const diskConfig = this.config.disks[name]
94
+ const driverName = diskConfig?.driver ?? name
95
+
96
+ const custom = this.customCreators.get(driverName)
97
+ if (custom) return custom(diskConfig ?? { driver: driverName })
98
+
99
+ if (!diskConfig) {
100
+ throw new Error(`Disk "${name}" is not configured. Define it in config/filesystem.ts or use extend().`)
101
+ }
102
+
103
+ switch (driverName) {
104
+ case 'local':
105
+ return new LocalDriver(
106
+ diskConfig.root ?? '/tmp/mantiq-storage',
107
+ diskConfig.url as string | undefined,
108
+ (diskConfig.visibility as 'public' | 'private') ?? 'public',
109
+ )
110
+ case 'null':
111
+ return new NullDriver()
112
+ case 's3':
113
+ case 'r2':
114
+ case 'spaces':
115
+ case 'minio':
116
+ return new S3Driver({
117
+ bucket: diskConfig.bucket as string,
118
+ region: diskConfig.region as string | undefined,
119
+ key: diskConfig.key as string | undefined,
120
+ secret: diskConfig.secret as string | undefined,
121
+ token: diskConfig.token as string | undefined,
122
+ endpoint: diskConfig.endpoint as string | undefined,
123
+ forcePathStyle: diskConfig.forcePathStyle as boolean | undefined,
124
+ root: diskConfig.root,
125
+ url: diskConfig.url,
126
+ visibility: diskConfig.visibility,
127
+ })
128
+ case 'gcs':
129
+ return new GCSDriver({
130
+ bucket: diskConfig.bucket as string,
131
+ projectId: diskConfig.projectId as string | undefined,
132
+ keyFilename: diskConfig.keyFilename as string | undefined,
133
+ credentials: diskConfig.credentials as Record<string, any> | undefined,
134
+ root: diskConfig.root,
135
+ url: diskConfig.url,
136
+ visibility: diskConfig.visibility,
137
+ })
138
+ case 'azure':
139
+ return new AzureBlobDriver({
140
+ container: diskConfig.container as string,
141
+ connectionString: diskConfig.connectionString as string | undefined,
142
+ accountName: diskConfig.accountName as string | undefined,
143
+ accountKey: diskConfig.accountKey as string | undefined,
144
+ sasToken: diskConfig.sasToken as string | undefined,
145
+ root: diskConfig.root,
146
+ url: diskConfig.url,
147
+ visibility: diskConfig.visibility,
148
+ })
149
+ case 'ftp':
150
+ return new FTPDriver({
151
+ host: diskConfig.host as string,
152
+ port: diskConfig.port as number | undefined,
153
+ username: diskConfig.username as string | undefined,
154
+ password: diskConfig.password as string | undefined,
155
+ secure: diskConfig.secure as boolean | 'implicit' | undefined,
156
+ timeout: diskConfig.timeout as number | undefined,
157
+ root: diskConfig.root,
158
+ url: diskConfig.url,
159
+ visibility: diskConfig.visibility,
160
+ })
161
+ case 'sftp':
162
+ return new SFTPDriver({
163
+ host: diskConfig.host as string,
164
+ port: diskConfig.port as number | undefined,
165
+ username: diskConfig.username as string | undefined,
166
+ password: diskConfig.password as string | undefined,
167
+ privateKey: diskConfig.privateKey as string | undefined,
168
+ passphrase: diskConfig.passphrase as string | undefined,
169
+ root: diskConfig.root,
170
+ url: diskConfig.url,
171
+ visibility: diskConfig.visibility,
172
+ })
173
+ default:
174
+ throw new Error(`Unsupported filesystem driver: "${driverName}". Use extend() to register custom drivers.`)
175
+ }
176
+ }
177
+ }
@@ -0,0 +1,30 @@
1
+ import { ServiceProvider, ConfigRepository, Application } from '@mantiq/core'
2
+ import { FilesystemManager } from './FilesystemManager.ts'
3
+ import { FILESYSTEM_MANAGER } from './helpers/storage.ts'
4
+ import type { FilesystemConfig } from './contracts/FilesystemConfig.ts'
5
+
6
+ const DEFAULT_CONFIG: FilesystemConfig = {
7
+ default: 'local',
8
+ disks: {
9
+ local: { driver: 'local', root: 'storage/app' },
10
+ },
11
+ }
12
+
13
+ export class FilesystemServiceProvider extends ServiceProvider {
14
+ override register(): void {
15
+ this.app.singleton(FilesystemManager, (c) => {
16
+ const config = c.make(ConfigRepository).get<FilesystemConfig>('filesystem', DEFAULT_CONFIG)
17
+ const app = c.make(Application)
18
+
19
+ // Normalize relative root paths to absolute using the app base path
20
+ for (const disk of Object.values(config.disks)) {
21
+ if (disk.root && !disk.root.startsWith('/')) {
22
+ disk.root = app.basePath_(disk.root)
23
+ }
24
+ }
25
+
26
+ return new FilesystemManager(config)
27
+ })
28
+ this.app.alias(FilesystemManager, FILESYSTEM_MANAGER)
29
+ }
30
+ }
@@ -0,0 +1,60 @@
1
+ export interface DiskConfig {
2
+ driver: string
3
+ root?: string
4
+ url?: string
5
+ visibility?: 'public' | 'private'
6
+ [key: string]: unknown
7
+ }
8
+
9
+ export interface S3DiskConfig extends DiskConfig {
10
+ driver: 's3'
11
+ bucket: string
12
+ region?: string
13
+ key?: string
14
+ secret?: string
15
+ token?: string
16
+ endpoint?: string
17
+ forcePathStyle?: boolean
18
+ }
19
+
20
+ export interface GCSDiskConfig extends DiskConfig {
21
+ driver: 'gcs'
22
+ bucket: string
23
+ projectId?: string
24
+ keyFilename?: string
25
+ credentials?: Record<string, any>
26
+ }
27
+
28
+ export interface AzureDiskConfig extends DiskConfig {
29
+ driver: 'azure'
30
+ container: string
31
+ connectionString?: string
32
+ accountName?: string
33
+ accountKey?: string
34
+ sasToken?: string
35
+ }
36
+
37
+ export interface FTPDiskConfig extends DiskConfig {
38
+ driver: 'ftp'
39
+ host: string
40
+ port?: number
41
+ username?: string
42
+ password?: string
43
+ secure?: boolean | 'implicit'
44
+ timeout?: number
45
+ }
46
+
47
+ export interface SFTPDiskConfig extends DiskConfig {
48
+ driver: 'sftp'
49
+ host: string
50
+ port?: number
51
+ username?: string
52
+ password?: string
53
+ privateKey?: string
54
+ passphrase?: string
55
+ }
56
+
57
+ export interface FilesystemConfig {
58
+ default: string
59
+ disks: Record<string, DiskConfig>
60
+ }
@@ -0,0 +1,45 @@
1
+ export interface PutOptions {
2
+ visibility?: 'public' | 'private'
3
+ mimeType?: string
4
+ }
5
+
6
+ export interface FilesystemDriver {
7
+ // ── Reads ───────────────────────────────────────────────────────────────────
8
+ exists(path: string): Promise<boolean>
9
+ get(path: string): Promise<string | null>
10
+ getBytes(path: string): Promise<Uint8Array | null>
11
+ stream(path: string): Promise<ReadableStream | null>
12
+
13
+ // ── Writes ──────────────────────────────────────────────────────────────────
14
+ put(path: string, contents: string | Uint8Array, options?: PutOptions): Promise<void>
15
+ putStream(path: string, stream: ReadableStream, options?: PutOptions): Promise<void>
16
+ append(path: string, contents: string): Promise<void>
17
+ prepend(path: string, contents: string): Promise<void>
18
+
19
+ // ── Operations ──────────────────────────────────────────────────────────────
20
+ delete(path: string | string[]): Promise<boolean>
21
+ copy(from: string, to: string): Promise<void>
22
+ move(from: string, to: string): Promise<void>
23
+
24
+ // ── Metadata ────────────────────────────────────────────────────────────────
25
+ size(path: string): Promise<number>
26
+ lastModified(path: string): Promise<number>
27
+ mimeType(path: string): Promise<string | null>
28
+ path(filePath: string): string
29
+
30
+ // ── URLs ────────────────────────────────────────────────────────────────────
31
+ url(path: string): string
32
+ temporaryUrl(path: string, expiration: number, options?: Record<string, any>): Promise<string>
33
+
34
+ // ── Directories ─────────────────────────────────────────────────────────────
35
+ files(directory?: string): Promise<string[]>
36
+ allFiles(directory?: string): Promise<string[]>
37
+ directories(directory?: string): Promise<string[]>
38
+ allDirectories(directory?: string): Promise<string[]>
39
+ makeDirectory(path: string): Promise<void>
40
+ deleteDirectory(directory: string): Promise<boolean>
41
+
42
+ // ── Visibility ──────────────────────────────────────────────────────────────
43
+ setVisibility(path: string, visibility: 'public' | 'private'): Promise<void>
44
+ getVisibility(path: string): Promise<string>
45
+ }