@fgv/ts-http-storage 5.1.0-2 → 5.1.0-20
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/dist/tsdoc-metadata.json +1 -1
- package/package.json +21 -21
- package/.rush/temp/2a9b81b5aa00c0f868f72c1458e40e12863c5047.tar.log +0 -61
- package/.rush/temp/2a9b81b5aa00c0f868f72c1458e40e12863c5047.untar.log +0 -10
- package/.rush/temp/5f9717376d57cea06ce184ce1ad4be928dc96116.tar.log +0 -61
- package/.rush/temp/6dc57b26d5a4e0d9901b7ec5f070db373bfdde8d.tar.log +0 -63
- package/.rush/temp/chunked-rush-logs/ts-http-storage.build.chunks.jsonl +0 -10
- package/.rush/temp/chunked-rush-logs/ts-http-storage.test.chunks.jsonl +0 -26
- package/.rush/temp/fbfcc9487d290993ba47f1d36e9383196e7b12ac.tar.log +0 -61
- package/.rush/temp/operation/build/all.log +0 -10
- package/.rush/temp/operation/build/log-chunks.jsonl +0 -10
- package/.rush/temp/operation/build/state.json +0 -3
- package/.rush/temp/operation/test/all.log +0 -26
- package/.rush/temp/operation/test/log-chunks.jsonl +0 -26
- package/.rush/temp/operation/test/state.json +0 -3
- package/.rush/temp/shrinkwrap-deps.json +0 -647
- package/config/api-extractor.json +0 -36
- package/config/rig.json +0 -10
- package/config/typedoc.json +0 -7
- package/docs/README.md +0 -298
- package/docs/classes/FsStorageProvider.createDirectory.md +0 -20
- package/docs/classes/FsStorageProvider.getChildren.md +0 -20
- package/docs/classes/FsStorageProvider.getFile.md +0 -20
- package/docs/classes/FsStorageProvider.getItem.md +0 -20
- package/docs/classes/FsStorageProvider.md +0 -134
- package/docs/classes/FsStorageProvider.saveFile.md +0 -22
- package/docs/classes/FsStorageProvider.sync.md +0 -13
- package/docs/classes/FsStorageProviderFactory.forNamespace.md +0 -20
- package/docs/classes/FsStorageProviderFactory.md +0 -69
- package/docs/classes/HttpStorageService.createDirectory.md +0 -20
- package/docs/classes/HttpStorageService.getChildren.md +0 -20
- package/docs/classes/HttpStorageService.getFile.md +0 -20
- package/docs/classes/HttpStorageService.getItem.md +0 -20
- package/docs/classes/HttpStorageService.md +0 -132
- package/docs/classes/HttpStorageService.saveFile.md +0 -20
- package/docs/classes/HttpStorageService.sync.md +0 -20
- package/docs/functions/createStorageRoutes.md +0 -11
- package/docs/functions/normalizeRequestPath.md +0 -11
- package/docs/functions/sanitizeNamespace.md +0 -11
- package/docs/interfaces/ICreateStorageRoutesOptions.logger.md +0 -9
- package/docs/interfaces/ICreateStorageRoutesOptions.md +0 -61
- package/docs/interfaces/ICreateStorageRoutesOptions.providers.md +0 -9
- package/docs/interfaces/IFsStorageProviderFactoryOptions.md +0 -44
- package/docs/interfaces/IFsStorageProviderFactoryOptions.rootPath.md +0 -9
- package/docs/interfaces/IHttpStorageProvider.createDirectory.md +0 -20
- package/docs/interfaces/IHttpStorageProvider.getChildren.md +0 -20
- package/docs/interfaces/IHttpStorageProvider.getFile.md +0 -20
- package/docs/interfaces/IHttpStorageProvider.getItem.md +0 -20
- package/docs/interfaces/IHttpStorageProvider.md +0 -102
- package/docs/interfaces/IHttpStorageProvider.saveFile.md +0 -22
- package/docs/interfaces/IHttpStorageProvider.sync.md +0 -13
- package/docs/interfaces/IHttpStorageProviderFactory.forNamespace.md +0 -20
- package/docs/interfaces/IHttpStorageProviderFactory.md +0 -36
- package/docs/interfaces/IStorageFileResponse.contentType.md +0 -9
- package/docs/interfaces/IStorageFileResponse.contents.md +0 -9
- package/docs/interfaces/IStorageFileResponse.md +0 -78
- package/docs/interfaces/IStorageFileResponse.path.md +0 -9
- package/docs/interfaces/IStoragePathRequest.md +0 -61
- package/docs/interfaces/IStoragePathRequest.namespace.md +0 -9
- package/docs/interfaces/IStoragePathRequest.path.md +0 -9
- package/docs/interfaces/IStorageSyncRequest.md +0 -44
- package/docs/interfaces/IStorageSyncRequest.namespace.md +0 -9
- package/docs/interfaces/IStorageSyncResponse.md +0 -44
- package/docs/interfaces/IStorageSyncResponse.synced.md +0 -9
- package/docs/interfaces/IStorageTreeChildrenResponse.children.md +0 -9
- package/docs/interfaces/IStorageTreeChildrenResponse.md +0 -61
- package/docs/interfaces/IStorageTreeChildrenResponse.path.md +0 -9
- package/docs/interfaces/IStorageTreeItem.md +0 -78
- package/docs/interfaces/IStorageTreeItem.name.md +0 -9
- package/docs/interfaces/IStorageTreeItem.path.md +0 -9
- package/docs/interfaces/IStorageTreeItem.type.md +0 -9
- package/docs/interfaces/IStorageWriteFileRequest.contentType.md +0 -9
- package/docs/interfaces/IStorageWriteFileRequest.contents.md +0 -9
- package/docs/interfaces/IStorageWriteFileRequest.md +0 -97
- package/docs/type-aliases/StorageItemType.md +0 -11
- package/docs/type-aliases/StorageNamespace.md +0 -11
- package/docs/variables/storageFileResponse.md +0 -9
- package/docs/variables/storagePathRequest.md +0 -9
- package/docs/variables/storageSyncRequest.md +0 -9
- package/docs/variables/storageTreeChildrenResponse.md +0 -9
- package/docs/variables/storageTreeItem.md +0 -9
- package/docs/variables/storageWriteFileRequest.md +0 -9
- package/etc/ts-http-storage.api.md +0 -184
- package/lib/test/unit/storage/fsProvider.test.d.ts +0 -2
- package/lib/test/unit/storage/fsProvider.test.d.ts.map +0 -1
- package/lib/test/unit/storage/fsProvider.test.js +0 -106
- package/lib/test/unit/storage/fsProvider.test.js.map +0 -1
- package/lib/test/unit/storage/routes.test.d.ts +0 -2
- package/lib/test/unit/storage/routes.test.d.ts.map +0 -1
- package/lib/test/unit/storage/routes.test.js +0 -124
- package/lib/test/unit/storage/routes.test.js.map +0 -1
- package/lib/test/unit/storage/service.test.d.ts +0 -2
- package/lib/test/unit/storage/service.test.d.ts.map +0 -1
- package/lib/test/unit/storage/service.test.js +0 -67
- package/lib/test/unit/storage/service.test.js.map +0 -1
- package/rush-logs/ts-http-storage.build.cache.log +0 -3
- package/rush-logs/ts-http-storage.build.log +0 -10
- package/rush-logs/ts-http-storage.test.cache.log +0 -1
- package/rush-logs/ts-http-storage.test.log +0 -26
- package/src/index.ts +0 -28
- package/src/packlets/storage/converters.ts +0 -100
- package/src/packlets/storage/fsProvider.ts +0 -263
- package/src/packlets/storage/index.ts +0 -33
- package/src/packlets/storage/model.ts +0 -113
- package/src/packlets/storage/provider.ts +0 -33
- package/src/packlets/storage/routes.ts +0 -215
- package/src/packlets/storage/service.ts +0 -114
- package/src/test/unit/storage/fsProvider.test.ts +0 -129
- package/src/test/unit/storage/routes.test.ts +0 -165
- package/src/test/unit/storage/service.test.ts +0 -95
- package/temp/build/typescript/ts_l9Fw4VUO.json +0 -1
- package/temp/test/jest/haste-map-8c99f7c94db8a3bcd2cd59242978c7c2-2b6695b6f6f938dfd4bbd5c487f7012f-ba9beffc5d763ea8f7407ec52e16ae8f +0 -0
- package/temp/test/jest/perf-cache-8c99f7c94db8a3bcd2cd59242978c7c2-da39a3ee5e6b4b0d3255bfef95601890 +0 -1
- package/temp/ts-http-storage.api.json +0 -3103
- package/temp/ts-http-storage.api.md +0 -184
- package/tsconfig.json +0 -7
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
Invoking: heft test --clean
|
|
2
|
-
---- build started ----
|
|
3
|
-
[build:clean] Deleted 0 files and 3 folders
|
|
4
|
-
[build:typescript] The TypeScript compiler version 5.9.3 is newer than the latest version that was tested with Heft (5.8); it may not work correctly.
|
|
5
|
-
[build:typescript] Using TypeScript version 5.9.3
|
|
6
|
-
[build:api-extractor] Using API Extractor version 7.57.6
|
|
7
|
-
[build:api-extractor] Analysis will use the bundled TypeScript version 5.8.2
|
|
8
|
-
[build:api-extractor] *** The target project appears to use TypeScript 5.9.3 which is newer than the bundled compiler engine; consider upgrading API Extractor.
|
|
9
|
-
---- build finished (2.589s) ----
|
|
10
|
-
---- test started ----
|
|
11
|
-
[test:jest] Using Jest version 29.5.0
|
|
12
|
-
[test:jest]
|
|
13
|
-
[test:jest] Run start. 3 test suites
|
|
14
|
-
[test:jest] START lib/test/unit/storage/routes.test.js
|
|
15
|
-
[test:jest] START lib/test/unit/storage/fsProvider.test.js
|
|
16
|
-
[test:jest] START lib/test/unit/storage/service.test.js
|
|
17
|
-
[test:jest] PASS lib/test/unit/storage/service.test.js (duration: 0.151s, 3 passed, 0 failed)
|
|
18
|
-
[test:jest] PASS lib/test/unit/storage/routes.test.js (duration: 0.160s, 4 passed, 0 failed)
|
|
19
|
-
[test:jest] PASS lib/test/unit/storage/fsProvider.test.js (duration: 0.160s, 10 passed, 0 failed)
|
|
20
|
-
[test:jest]
|
|
21
|
-
[test:jest] Tests finished:
|
|
22
|
-
[test:jest] Successes: 17
|
|
23
|
-
[test:jest] Failures: 0
|
|
24
|
-
[test:jest] Total: 17
|
|
25
|
-
---- test finished (0.531s) ----
|
|
26
|
-
-------------------- Finished (3.123s) --------------------
|
package/src/index.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) 2026 Erik Fortune
|
|
3
|
-
*
|
|
4
|
-
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
-
* of this software and associated documentation files (the "Software"), to deal
|
|
6
|
-
* in the Software without restriction, including without limitation the rights
|
|
7
|
-
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
-
* copies of the Software, and to permit persons to whom the Software is
|
|
9
|
-
* furnished to do so, subject to the following conditions:
|
|
10
|
-
*
|
|
11
|
-
* The above copyright notice and this permission notice shall be included in all
|
|
12
|
-
* copies or substantial portions of the Software.
|
|
13
|
-
*
|
|
14
|
-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
-
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
-
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
-
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
-
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
-
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
-
* SOFTWARE.
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Reusable HTTP storage services for FileTree-style backends.
|
|
25
|
-
* @packageDocumentation
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
export * from './packlets/storage';
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) 2026 Erik Fortune
|
|
3
|
-
*
|
|
4
|
-
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
-
* of this software and associated documentation files (the "Software"), to deal
|
|
6
|
-
* in the Software without restriction, including without limitation the rights
|
|
7
|
-
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
-
* copies of the Software, and to permit persons to whom the Software is
|
|
9
|
-
* furnished to do so, subject to the following conditions:
|
|
10
|
-
*
|
|
11
|
-
* The above copyright notice and this permission notice shall be included in all
|
|
12
|
-
* copies or substantial portions of the Software.
|
|
13
|
-
*
|
|
14
|
-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
-
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
-
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
-
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
-
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
-
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
-
* SOFTWARE.
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import { type Converter, Converters } from '@fgv/ts-utils';
|
|
24
|
-
|
|
25
|
-
import type {
|
|
26
|
-
IStorageFileResponse,
|
|
27
|
-
IStoragePathRequest,
|
|
28
|
-
IStorageSyncRequest,
|
|
29
|
-
IStorageTreeChildrenResponse,
|
|
30
|
-
IStorageTreeItem,
|
|
31
|
-
IStorageWriteFileRequest,
|
|
32
|
-
StorageItemType
|
|
33
|
-
} from './model';
|
|
34
|
-
|
|
35
|
-
const storageItemType: Converter<StorageItemType> = Converters.enumeratedValue<StorageItemType>([
|
|
36
|
-
'file',
|
|
37
|
-
'directory'
|
|
38
|
-
]);
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Converter for {@link IStorageTreeItem}.
|
|
42
|
-
* @public
|
|
43
|
-
*/
|
|
44
|
-
export const storageTreeItem: Converter<IStorageTreeItem> = Converters.strictObject<IStorageTreeItem>({
|
|
45
|
-
path: Converters.string,
|
|
46
|
-
name: Converters.string,
|
|
47
|
-
type: storageItemType
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Converter for path-based requests.
|
|
52
|
-
* @public
|
|
53
|
-
*/
|
|
54
|
-
export const storagePathRequest: Converter<IStoragePathRequest> =
|
|
55
|
-
Converters.strictObject<IStoragePathRequest>({
|
|
56
|
-
path: Converters.string,
|
|
57
|
-
namespace: Converters.string.optional()
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Converter for write-file requests.
|
|
62
|
-
* @public
|
|
63
|
-
*/
|
|
64
|
-
export const storageWriteFileRequest: Converter<IStorageWriteFileRequest> =
|
|
65
|
-
Converters.strictObject<IStorageWriteFileRequest>({
|
|
66
|
-
path: Converters.string,
|
|
67
|
-
contents: Converters.string,
|
|
68
|
-
contentType: Converters.string.optional(),
|
|
69
|
-
namespace: Converters.string.optional()
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Converter for sync requests.
|
|
74
|
-
* @public
|
|
75
|
-
*/
|
|
76
|
-
export const storageSyncRequest: Converter<IStorageSyncRequest> =
|
|
77
|
-
Converters.strictObject<IStorageSyncRequest>({
|
|
78
|
-
namespace: Converters.string.optional()
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Converter for file responses.
|
|
83
|
-
* @public
|
|
84
|
-
*/
|
|
85
|
-
export const storageFileResponse: Converter<IStorageFileResponse> =
|
|
86
|
-
Converters.strictObject<IStorageFileResponse>({
|
|
87
|
-
path: Converters.string,
|
|
88
|
-
contents: Converters.string,
|
|
89
|
-
contentType: Converters.string.optional()
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Converter for children list responses.
|
|
94
|
-
* @public
|
|
95
|
-
*/
|
|
96
|
-
export const storageTreeChildrenResponse: Converter<IStorageTreeChildrenResponse> =
|
|
97
|
-
Converters.strictObject<IStorageTreeChildrenResponse>({
|
|
98
|
-
path: Converters.string,
|
|
99
|
-
children: Converters.arrayOf(storageTreeItem)
|
|
100
|
-
});
|
|
@@ -1,263 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) 2026 Erik Fortune
|
|
3
|
-
*
|
|
4
|
-
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
-
* of this software and associated documentation files (the "Software"), to deal
|
|
6
|
-
* in the Software without restriction, including without limitation the rights
|
|
7
|
-
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
-
* copies of the Software, and to permit persons to whom the Software is
|
|
9
|
-
* furnished to do so, subject to the following conditions:
|
|
10
|
-
*
|
|
11
|
-
* The above copyright notice and this permission notice shall be included in all
|
|
12
|
-
* copies or substantial portions of the Software.
|
|
13
|
-
*
|
|
14
|
-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
-
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
-
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
-
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
-
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
-
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
-
* SOFTWARE.
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import * as fs from 'fs';
|
|
24
|
-
import * as fsp from 'fs/promises';
|
|
25
|
-
import * as path from 'path';
|
|
26
|
-
|
|
27
|
-
import { captureResult, fail, type Result, succeed } from '@fgv/ts-utils';
|
|
28
|
-
|
|
29
|
-
import type {
|
|
30
|
-
IHttpStorageProvider,
|
|
31
|
-
IStorageFileResponse,
|
|
32
|
-
IStorageSyncResponse,
|
|
33
|
-
IStorageTreeItem,
|
|
34
|
-
StorageItemType
|
|
35
|
-
} from './model';
|
|
36
|
-
import type { IHttpStorageProviderFactory } from './provider';
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Options for creating filesystem-backed storage providers.
|
|
40
|
-
* @public
|
|
41
|
-
*/
|
|
42
|
-
export interface IFsStorageProviderFactoryOptions {
|
|
43
|
-
readonly rootPath: string;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function toItemType(stats: fs.Stats): StorageItemType {
|
|
47
|
-
return stats.isDirectory() ? 'directory' : 'file';
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Filesystem-backed implementation of {@link IHttpStorageProvider}.
|
|
52
|
-
* @public
|
|
53
|
-
*/
|
|
54
|
-
export class FsStorageProvider implements IHttpStorageProvider {
|
|
55
|
-
private readonly _rootPath: string;
|
|
56
|
-
|
|
57
|
-
public constructor(rootPath: string) {
|
|
58
|
-
this._rootPath = rootPath;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
public async getItem(itemPath: string): Promise<Result<IStorageTreeItem>> {
|
|
62
|
-
const resolved = this._resolveAbsolutePath(itemPath);
|
|
63
|
-
if (resolved.isFailure()) {
|
|
64
|
-
return fail(resolved.message);
|
|
65
|
-
}
|
|
66
|
-
try {
|
|
67
|
-
const stats = await fsp.stat(resolved.value);
|
|
68
|
-
return succeed(this._toTreeItem(itemPath, toItemType(stats)));
|
|
69
|
-
} catch (err: unknown) {
|
|
70
|
-
return fail(`${itemPath}: ${_toMessage(err)}`);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
public async getChildren(itemPath: string): Promise<Result<ReadonlyArray<IStorageTreeItem>>> {
|
|
75
|
-
const resolved = this._resolveAbsolutePath(itemPath);
|
|
76
|
-
if (resolved.isFailure()) {
|
|
77
|
-
return fail(resolved.message);
|
|
78
|
-
}
|
|
79
|
-
try {
|
|
80
|
-
const entries = await fsp.readdir(resolved.value, { withFileTypes: true });
|
|
81
|
-
return _mapEntries(entries, itemPath);
|
|
82
|
-
} catch (err: unknown) {
|
|
83
|
-
return fail(`${itemPath}: ${_toMessage(err)}`);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
public async getFile(itemPath: string): Promise<Result<IStorageFileResponse>> {
|
|
88
|
-
const resolved = this._resolveAbsolutePath(itemPath);
|
|
89
|
-
if (resolved.isFailure()) {
|
|
90
|
-
return fail(resolved.message);
|
|
91
|
-
}
|
|
92
|
-
try {
|
|
93
|
-
const stats = await fsp.stat(resolved.value);
|
|
94
|
-
if (!stats.isFile()) {
|
|
95
|
-
return fail(`${itemPath}: not a file`);
|
|
96
|
-
}
|
|
97
|
-
const contents = await fsp.readFile(resolved.value, 'utf8');
|
|
98
|
-
return succeed({
|
|
99
|
-
path: normalizeRequestPath(itemPath),
|
|
100
|
-
contents
|
|
101
|
-
});
|
|
102
|
-
} catch (err: unknown) {
|
|
103
|
-
return fail(`${itemPath}: ${_toMessage(err)}`);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
public async saveFile(
|
|
108
|
-
itemPath: string,
|
|
109
|
-
contents: string,
|
|
110
|
-
contentType?: string
|
|
111
|
-
): Promise<Result<IStorageFileResponse>> {
|
|
112
|
-
const resolved = this._resolveAbsolutePath(itemPath);
|
|
113
|
-
if (resolved.isFailure()) {
|
|
114
|
-
return fail(resolved.message);
|
|
115
|
-
}
|
|
116
|
-
try {
|
|
117
|
-
const parentDir = path.dirname(resolved.value);
|
|
118
|
-
await fsp.mkdir(parentDir, { recursive: true });
|
|
119
|
-
await fsp.writeFile(resolved.value, contents, 'utf8');
|
|
120
|
-
return succeed({
|
|
121
|
-
path: normalizeRequestPath(itemPath),
|
|
122
|
-
contents,
|
|
123
|
-
contentType
|
|
124
|
-
});
|
|
125
|
-
} catch (err: unknown) {
|
|
126
|
-
return fail(`${itemPath}: ${_toMessage(err)}`);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
public async deleteFile(itemPath: string): Promise<Result<boolean>> {
|
|
131
|
-
const resolved = this._resolveAbsolutePath(itemPath);
|
|
132
|
-
if (resolved.isFailure()) {
|
|
133
|
-
return fail(resolved.message);
|
|
134
|
-
}
|
|
135
|
-
try {
|
|
136
|
-
const stats = await fsp.stat(resolved.value);
|
|
137
|
-
if (!stats.isFile()) {
|
|
138
|
-
return fail(`${itemPath}: not a file`);
|
|
139
|
-
}
|
|
140
|
-
await fsp.unlink(resolved.value);
|
|
141
|
-
return succeed(true);
|
|
142
|
-
} catch (err: unknown) {
|
|
143
|
-
return fail(`${itemPath}: ${_toMessage(err)}`);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
public async createDirectory(itemPath: string): Promise<Result<IStorageTreeItem>> {
|
|
148
|
-
const resolved = this._resolveAbsolutePath(itemPath);
|
|
149
|
-
if (resolved.isFailure()) {
|
|
150
|
-
return fail(resolved.message);
|
|
151
|
-
}
|
|
152
|
-
try {
|
|
153
|
-
await fsp.mkdir(resolved.value, { recursive: true });
|
|
154
|
-
return succeed(this._toTreeItem(itemPath, 'directory'));
|
|
155
|
-
} catch (err: unknown) {
|
|
156
|
-
return fail(`${itemPath}: ${_toMessage(err)}`);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
public async sync(): Promise<Result<IStorageSyncResponse>> {
|
|
161
|
-
return succeed({ synced: 0 });
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
private _resolveAbsolutePath(requestPath: string): Result<string> {
|
|
165
|
-
const normalized = normalizeRequestPath(requestPath);
|
|
166
|
-
const candidate = path.resolve(this._rootPath, `.${normalized}`);
|
|
167
|
-
const relative = path.relative(this._rootPath, candidate);
|
|
168
|
-
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
169
|
-
return fail(`${requestPath}: path is outside storage root`);
|
|
170
|
-
}
|
|
171
|
-
return succeed(candidate);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
private _toTreeItem(itemPath: string, type: StorageItemType): IStorageTreeItem {
|
|
175
|
-
const normalizedPath = normalizeRequestPath(itemPath);
|
|
176
|
-
const name = normalizedPath === '/' ? '/' : path.posix.basename(normalizedPath);
|
|
177
|
-
return {
|
|
178
|
-
path: normalizedPath,
|
|
179
|
-
name,
|
|
180
|
-
type
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function _mapEntries(
|
|
186
|
-
entries: ReadonlyArray<fs.Dirent>,
|
|
187
|
-
parentPath: string
|
|
188
|
-
): Result<ReadonlyArray<IStorageTreeItem>> {
|
|
189
|
-
const normalizedParent = normalizeRequestPath(parentPath);
|
|
190
|
-
const items: IStorageTreeItem[] = entries.map((entry) => {
|
|
191
|
-
const isDirectory = entry.isDirectory();
|
|
192
|
-
const childPath = normalizedParent === '/' ? `/${entry.name}` : `${normalizedParent}/${entry.name}`;
|
|
193
|
-
return {
|
|
194
|
-
path: childPath,
|
|
195
|
-
name: entry.name,
|
|
196
|
-
type: isDirectory ? 'directory' : 'file'
|
|
197
|
-
};
|
|
198
|
-
});
|
|
199
|
-
return succeed(items);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function _toMessage(err: unknown): string {
|
|
203
|
-
return err instanceof Error ? err.message : String(err);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Normalizes a request path to a consistent POSIX format.
|
|
208
|
-
* @public
|
|
209
|
-
*/
|
|
210
|
-
export function normalizeRequestPath(requestPath: string): string {
|
|
211
|
-
if (requestPath.length === 0) {
|
|
212
|
-
return '/';
|
|
213
|
-
}
|
|
214
|
-
const normalized = path.posix.normalize(requestPath.startsWith('/') ? requestPath : `/${requestPath}`);
|
|
215
|
-
return normalized.startsWith('/') ? normalized : `/${normalized}`;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Namespace-aware provider factory backed by filesystem directories.
|
|
220
|
-
* @public
|
|
221
|
-
*/
|
|
222
|
-
export class FsStorageProviderFactory implements IHttpStorageProviderFactory {
|
|
223
|
-
private readonly _rootPath: string;
|
|
224
|
-
|
|
225
|
-
public constructor(options: IFsStorageProviderFactoryOptions) {
|
|
226
|
-
this._rootPath = path.resolve(options.rootPath);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
public forNamespace(namespace?: string): Result<IHttpStorageProvider> {
|
|
230
|
-
const safeNamespace = sanitizeNamespace(namespace);
|
|
231
|
-
if (safeNamespace.isFailure()) {
|
|
232
|
-
return fail(safeNamespace.message);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
const namespaceRoot = path.resolve(this._rootPath, safeNamespace.value);
|
|
236
|
-
const relative = path.relative(this._rootPath, namespaceRoot);
|
|
237
|
-
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
238
|
-
return fail(`namespace '${safeNamespace.value}' is outside root`);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
const ensureResult = captureResult(() => fs.mkdirSync(namespaceRoot, { recursive: true }));
|
|
242
|
-
if (ensureResult.isFailure()) {
|
|
243
|
-
return fail(`namespace '${safeNamespace.value}': ${ensureResult.message}`);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
return succeed(new FsStorageProvider(namespaceRoot));
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Sanitize namespace path segment.
|
|
252
|
-
* @public
|
|
253
|
-
*/
|
|
254
|
-
export function sanitizeNamespace(namespace?: string): Result<string> {
|
|
255
|
-
if (!namespace || namespace.trim().length === 0) {
|
|
256
|
-
return succeed('default');
|
|
257
|
-
}
|
|
258
|
-
const trimmed = namespace.trim();
|
|
259
|
-
if (!/^[a-zA-Z0-9._-]+$/.test(trimmed)) {
|
|
260
|
-
return fail(`namespace '${namespace}': only [a-zA-Z0-9._-] allowed`);
|
|
261
|
-
}
|
|
262
|
-
return succeed(trimmed);
|
|
263
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) 2026 Erik Fortune
|
|
3
|
-
*
|
|
4
|
-
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
-
* of this software and associated documentation files (the "Software"), to deal
|
|
6
|
-
* in the Software without restriction, including without limitation the rights
|
|
7
|
-
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
-
* copies of the Software, and to permit persons to whom the Software is
|
|
9
|
-
* furnished to do so, subject to the following conditions:
|
|
10
|
-
*
|
|
11
|
-
* The above copyright notice and this permission notice shall be included in all
|
|
12
|
-
* copies or substantial portions of the Software.
|
|
13
|
-
*
|
|
14
|
-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
-
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
-
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
-
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
-
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
-
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
-
* SOFTWARE.
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Reusable HTTP storage services and route helpers.
|
|
25
|
-
* @packageDocumentation
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
export * from './model';
|
|
29
|
-
export * from './converters';
|
|
30
|
-
export * from './provider';
|
|
31
|
-
export * from './fsProvider';
|
|
32
|
-
export * from './service';
|
|
33
|
-
export * from './routes';
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) 2026 Erik Fortune
|
|
3
|
-
*
|
|
4
|
-
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
-
* of this software and associated documentation files (the "Software"), to deal
|
|
6
|
-
* in the Software without restriction, including without limitation the rights
|
|
7
|
-
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
-
* copies of the Software, and to permit persons to whom the Software is
|
|
9
|
-
* furnished to do so, subject to the following conditions:
|
|
10
|
-
*
|
|
11
|
-
* The above copyright notice and this permission notice shall be included in all
|
|
12
|
-
* copies or substantial portions of the Software.
|
|
13
|
-
*
|
|
14
|
-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
-
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
-
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
-
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
-
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
-
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
-
* SOFTWARE.
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import type { Result } from '@fgv/ts-utils';
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Namespace identifier for scoped storage.
|
|
27
|
-
* @public
|
|
28
|
-
*/
|
|
29
|
-
export type StorageNamespace = string;
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Storage item type.
|
|
33
|
-
* @public
|
|
34
|
-
*/
|
|
35
|
-
export type StorageItemType = 'file' | 'directory';
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Storage tree item metadata.
|
|
39
|
-
* @public
|
|
40
|
-
*/
|
|
41
|
-
export interface IStorageTreeItem {
|
|
42
|
-
readonly path: string;
|
|
43
|
-
readonly name: string;
|
|
44
|
-
readonly type: StorageItemType;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Response for listing children.
|
|
49
|
-
* @public
|
|
50
|
-
*/
|
|
51
|
-
export interface IStorageTreeChildrenResponse {
|
|
52
|
-
readonly path: string;
|
|
53
|
-
readonly children: ReadonlyArray<IStorageTreeItem>;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Response for reading a file.
|
|
58
|
-
* @public
|
|
59
|
-
*/
|
|
60
|
-
export interface IStorageFileResponse {
|
|
61
|
-
readonly path: string;
|
|
62
|
-
readonly contents: string;
|
|
63
|
-
readonly contentType?: string;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Request for path-based operations.
|
|
68
|
-
* @public
|
|
69
|
-
*/
|
|
70
|
-
export interface IStoragePathRequest {
|
|
71
|
-
readonly path: string;
|
|
72
|
-
readonly namespace?: StorageNamespace;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Request for writing file contents.
|
|
77
|
-
* @public
|
|
78
|
-
*/
|
|
79
|
-
export interface IStorageWriteFileRequest extends IStoragePathRequest {
|
|
80
|
-
readonly contents: string;
|
|
81
|
-
readonly contentType?: string;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Request for sync operation.
|
|
86
|
-
* @public
|
|
87
|
-
*/
|
|
88
|
-
export interface IStorageSyncRequest {
|
|
89
|
-
readonly namespace?: StorageNamespace;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Sync response metadata.
|
|
94
|
-
* @public
|
|
95
|
-
*/
|
|
96
|
-
export interface IStorageSyncResponse {
|
|
97
|
-
readonly synced: number;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Provider contract for storage backends.
|
|
102
|
-
* All methods are async to support both filesystem and database backends.
|
|
103
|
-
* @public
|
|
104
|
-
*/
|
|
105
|
-
export interface IHttpStorageProvider {
|
|
106
|
-
getItem(path: string): Promise<Result<IStorageTreeItem>>;
|
|
107
|
-
getChildren(path: string): Promise<Result<ReadonlyArray<IStorageTreeItem>>>;
|
|
108
|
-
getFile(path: string): Promise<Result<IStorageFileResponse>>;
|
|
109
|
-
saveFile(path: string, contents: string, contentType?: string): Promise<Result<IStorageFileResponse>>;
|
|
110
|
-
deleteFile(path: string): Promise<Result<boolean>>;
|
|
111
|
-
createDirectory(path: string): Promise<Result<IStorageTreeItem>>;
|
|
112
|
-
sync(): Promise<Result<IStorageSyncResponse>>;
|
|
113
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) 2026 Erik Fortune
|
|
3
|
-
*
|
|
4
|
-
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
-
* of this software and associated documentation files (the "Software"), to deal
|
|
6
|
-
* in the Software without restriction, including without limitation the rights
|
|
7
|
-
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
-
* copies of the Software, and to permit persons to whom the Software is
|
|
9
|
-
* furnished to do so, subject to the following conditions:
|
|
10
|
-
*
|
|
11
|
-
* The above copyright notice and this permission notice shall be included in all
|
|
12
|
-
* copies or substantial portions of the Software.
|
|
13
|
-
*
|
|
14
|
-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
-
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
-
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
-
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
-
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
-
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
-
* SOFTWARE.
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import type { Result } from '@fgv/ts-utils';
|
|
24
|
-
|
|
25
|
-
import type { IHttpStorageProvider, StorageNamespace } from './model';
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Factory for creating namespace-scoped storage providers.
|
|
29
|
-
* @public
|
|
30
|
-
*/
|
|
31
|
-
export interface IHttpStorageProviderFactory {
|
|
32
|
-
forNamespace(namespace?: StorageNamespace): Result<IHttpStorageProvider>;
|
|
33
|
-
}
|