@fgv/ts-http-storage 5.1.0-5 → 5.1.0-50

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.
Files changed (128) hide show
  1. package/LICENSE +21 -0
  2. package/dist/ts-http-storage.d.ts +74 -3
  3. package/dist/tsdoc-metadata.json +1 -1
  4. package/eslint.config.js +16 -0
  5. package/lib/packlets/storage/converters.d.ts +11 -1
  6. package/lib/packlets/storage/converters.d.ts.map +1 -1
  7. package/lib/packlets/storage/converters.js +17 -2
  8. package/lib/packlets/storage/converters.js.map +1 -1
  9. package/lib/packlets/storage/fsProvider.d.ts +2 -2
  10. package/lib/packlets/storage/fsProvider.d.ts.map +1 -1
  11. package/lib/packlets/storage/fsProvider.js +17 -1
  12. package/lib/packlets/storage/fsProvider.js.map +1 -1
  13. package/lib/packlets/storage/model.d.ts +58 -1
  14. package/lib/packlets/storage/model.d.ts.map +1 -1
  15. package/lib/packlets/storage/model.js.map +1 -1
  16. package/lib/packlets/storage/routes.d.ts.map +1 -1
  17. package/lib/packlets/storage/routes.js +2 -1
  18. package/lib/packlets/storage/routes.js.map +1 -1
  19. package/lib/packlets/storage/service.d.ts +2 -2
  20. package/lib/packlets/storage/service.d.ts.map +1 -1
  21. package/lib/packlets/storage/service.js +1 -1
  22. package/lib/packlets/storage/service.js.map +1 -1
  23. package/package.json +5 -5
  24. package/.rush/temp/4f4782db16a13ce96c2b268916d3e8a461b96fcf.tar.log +0 -61
  25. package/.rush/temp/chunked-rush-logs/ts-http-storage.build.chunks.jsonl +0 -9
  26. package/.rush/temp/operation/build/all.log +0 -9
  27. package/.rush/temp/operation/build/log-chunks.jsonl +0 -9
  28. package/.rush/temp/operation/build/state.json +0 -3
  29. package/.rush/temp/shrinkwrap-deps.json +0 -656
  30. package/config/api-extractor.json +0 -36
  31. package/config/rig.json +0 -10
  32. package/config/typedoc.json +0 -7
  33. package/docs/README.md +0 -298
  34. package/docs/classes/FsStorageProvider.createDirectory.md +0 -20
  35. package/docs/classes/FsStorageProvider.deleteFile.md +0 -20
  36. package/docs/classes/FsStorageProvider.getChildren.md +0 -20
  37. package/docs/classes/FsStorageProvider.getFile.md +0 -20
  38. package/docs/classes/FsStorageProvider.getItem.md +0 -20
  39. package/docs/classes/FsStorageProvider.md +0 -147
  40. package/docs/classes/FsStorageProvider.saveFile.md +0 -22
  41. package/docs/classes/FsStorageProvider.sync.md +0 -13
  42. package/docs/classes/FsStorageProviderFactory.forNamespace.md +0 -20
  43. package/docs/classes/FsStorageProviderFactory.md +0 -69
  44. package/docs/classes/HttpStorageService.createDirectory.md +0 -20
  45. package/docs/classes/HttpStorageService.deleteFile.md +0 -20
  46. package/docs/classes/HttpStorageService.getChildren.md +0 -20
  47. package/docs/classes/HttpStorageService.getFile.md +0 -20
  48. package/docs/classes/HttpStorageService.getItem.md +0 -20
  49. package/docs/classes/HttpStorageService.md +0 -145
  50. package/docs/classes/HttpStorageService.saveFile.md +0 -20
  51. package/docs/classes/HttpStorageService.sync.md +0 -20
  52. package/docs/functions/createStorageRoutes.md +0 -11
  53. package/docs/functions/normalizeRequestPath.md +0 -11
  54. package/docs/functions/sanitizeNamespace.md +0 -11
  55. package/docs/interfaces/ICreateStorageRoutesOptions.logger.md +0 -9
  56. package/docs/interfaces/ICreateStorageRoutesOptions.md +0 -61
  57. package/docs/interfaces/ICreateStorageRoutesOptions.providers.md +0 -9
  58. package/docs/interfaces/IFsStorageProviderFactoryOptions.md +0 -44
  59. package/docs/interfaces/IFsStorageProviderFactoryOptions.rootPath.md +0 -9
  60. package/docs/interfaces/IHttpStorageProvider.createDirectory.md +0 -20
  61. package/docs/interfaces/IHttpStorageProvider.deleteFile.md +0 -20
  62. package/docs/interfaces/IHttpStorageProvider.getChildren.md +0 -20
  63. package/docs/interfaces/IHttpStorageProvider.getFile.md +0 -20
  64. package/docs/interfaces/IHttpStorageProvider.getItem.md +0 -20
  65. package/docs/interfaces/IHttpStorageProvider.md +0 -115
  66. package/docs/interfaces/IHttpStorageProvider.saveFile.md +0 -22
  67. package/docs/interfaces/IHttpStorageProvider.sync.md +0 -13
  68. package/docs/interfaces/IHttpStorageProviderFactory.forNamespace.md +0 -20
  69. package/docs/interfaces/IHttpStorageProviderFactory.md +0 -36
  70. package/docs/interfaces/IStorageFileResponse.contentType.md +0 -9
  71. package/docs/interfaces/IStorageFileResponse.contents.md +0 -9
  72. package/docs/interfaces/IStorageFileResponse.md +0 -78
  73. package/docs/interfaces/IStorageFileResponse.path.md +0 -9
  74. package/docs/interfaces/IStoragePathRequest.md +0 -61
  75. package/docs/interfaces/IStoragePathRequest.namespace.md +0 -9
  76. package/docs/interfaces/IStoragePathRequest.path.md +0 -9
  77. package/docs/interfaces/IStorageSyncRequest.md +0 -44
  78. package/docs/interfaces/IStorageSyncRequest.namespace.md +0 -9
  79. package/docs/interfaces/IStorageSyncResponse.md +0 -44
  80. package/docs/interfaces/IStorageSyncResponse.synced.md +0 -9
  81. package/docs/interfaces/IStorageTreeChildrenResponse.children.md +0 -9
  82. package/docs/interfaces/IStorageTreeChildrenResponse.md +0 -61
  83. package/docs/interfaces/IStorageTreeChildrenResponse.path.md +0 -9
  84. package/docs/interfaces/IStorageTreeItem.md +0 -78
  85. package/docs/interfaces/IStorageTreeItem.name.md +0 -9
  86. package/docs/interfaces/IStorageTreeItem.path.md +0 -9
  87. package/docs/interfaces/IStorageTreeItem.type.md +0 -9
  88. package/docs/interfaces/IStorageWriteFileRequest.contentType.md +0 -9
  89. package/docs/interfaces/IStorageWriteFileRequest.contents.md +0 -9
  90. package/docs/interfaces/IStorageWriteFileRequest.md +0 -97
  91. package/docs/type-aliases/StorageItemType.md +0 -11
  92. package/docs/type-aliases/StorageNamespace.md +0 -11
  93. package/docs/variables/storageFileResponse.md +0 -9
  94. package/docs/variables/storagePathRequest.md +0 -9
  95. package/docs/variables/storageSyncRequest.md +0 -9
  96. package/docs/variables/storageTreeChildrenResponse.md +0 -9
  97. package/docs/variables/storageTreeItem.md +0 -9
  98. package/docs/variables/storageWriteFileRequest.md +0 -9
  99. package/etc/ts-http-storage.api.md +0 -184
  100. package/lib/test/unit/storage/fsProvider.test.d.ts +0 -2
  101. package/lib/test/unit/storage/fsProvider.test.d.ts.map +0 -1
  102. package/lib/test/unit/storage/fsProvider.test.js +0 -106
  103. package/lib/test/unit/storage/fsProvider.test.js.map +0 -1
  104. package/lib/test/unit/storage/routes.test.d.ts +0 -2
  105. package/lib/test/unit/storage/routes.test.d.ts.map +0 -1
  106. package/lib/test/unit/storage/routes.test.js +0 -124
  107. package/lib/test/unit/storage/routes.test.js.map +0 -1
  108. package/lib/test/unit/storage/service.test.d.ts +0 -2
  109. package/lib/test/unit/storage/service.test.d.ts.map +0 -1
  110. package/lib/test/unit/storage/service.test.js +0 -67
  111. package/lib/test/unit/storage/service.test.js.map +0 -1
  112. package/rush-logs/ts-http-storage.build.cache.log +0 -3
  113. package/rush-logs/ts-http-storage.build.log +0 -9
  114. package/src/index.ts +0 -28
  115. package/src/packlets/storage/converters.ts +0 -100
  116. package/src/packlets/storage/fsProvider.ts +0 -263
  117. package/src/packlets/storage/index.ts +0 -33
  118. package/src/packlets/storage/model.ts +0 -113
  119. package/src/packlets/storage/provider.ts +0 -33
  120. package/src/packlets/storage/routes.ts +0 -215
  121. package/src/packlets/storage/service.ts +0 -114
  122. package/src/test/unit/storage/fsProvider.test.ts +0 -129
  123. package/src/test/unit/storage/routes.test.ts +0 -165
  124. package/src/test/unit/storage/service.test.ts +0 -95
  125. package/temp/build/typescript/ts_l9Fw4VUO.json +0 -1
  126. package/temp/ts-http-storage.api.json +0 -3103
  127. package/temp/ts-http-storage.api.md +0 -184
  128. package/tsconfig.json +0 -7
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Erik Fortune
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -23,7 +23,7 @@ export declare class FsStorageProvider implements IHttpStorageProvider {
23
23
  constructor(rootPath: string);
24
24
  getItem(itemPath: string): Promise<Result<IStorageTreeItem>>;
25
25
  getChildren(itemPath: string): Promise<Result<ReadonlyArray<IStorageTreeItem>>>;
26
- getFile(itemPath: string): Promise<Result<IStorageFileResponse>>;
26
+ getFile(itemPath: string, encoding?: StorageContentEncoding): Promise<Result<IStorageFileResponse>>;
27
27
  saveFile(itemPath: string, contents: string, contentType?: string): Promise<Result<IStorageFileResponse>>;
28
28
  deleteFile(itemPath: string): Promise<Result<boolean>>;
29
29
  createDirectory(itemPath: string): Promise<Result<IStorageTreeItem>>;
@@ -51,7 +51,7 @@ export declare class HttpStorageService {
51
51
  constructor(providers: IHttpStorageProviderFactory);
52
52
  getItem(request: IStoragePathRequest): Promise<Result<IStorageTreeItem>>;
53
53
  getChildren(request: IStoragePathRequest): Promise<Result<IStorageTreeChildrenResponse>>;
54
- getFile(request: IStoragePathRequest): Promise<Result<IStorageFileResponse>>;
54
+ getFile(request: IStorageReadFileRequest): Promise<Result<IStorageFileResponse>>;
55
55
  saveFile(request: IStorageWriteFileRequest): Promise<Result<IStorageFileResponse>>;
56
56
  deleteFile(request: IStoragePathRequest): Promise<Result<boolean>>;
57
57
  createDirectory(request: IStoragePathRequest): Promise<Result<IStorageTreeItem>>;
@@ -84,7 +84,15 @@ export declare interface IFsStorageProviderFactoryOptions {
84
84
  export declare interface IHttpStorageProvider {
85
85
  getItem(path: string): Promise<Result<IStorageTreeItem>>;
86
86
  getChildren(path: string): Promise<Result<ReadonlyArray<IStorageTreeItem>>>;
87
- getFile(path: string): Promise<Result<IStorageFileResponse>>;
87
+ /**
88
+ * Reads a file.
89
+ * @param path - Path of the file to read.
90
+ * @param encoding - Preferred wire encoding for the returned `contents`. A
91
+ * provider that does not implement the requested encoding MUST answer in
92
+ * `'utf8'` and report that on the response, rather than failing — the field is
93
+ * a preference and the response is the authority.
94
+ */
95
+ getFile(path: string, encoding?: StorageContentEncoding): Promise<Result<IStorageFileResponse>>;
88
96
  saveFile(path: string, contents: string, contentType?: string): Promise<Result<IStorageFileResponse>>;
89
97
  deleteFile(path: string): Promise<Result<boolean>>;
90
98
  createDirectory(path: string): Promise<Result<IStorageTreeItem>>;
@@ -107,6 +115,19 @@ export declare interface IStorageFileResponse {
107
115
  readonly path: string;
108
116
  readonly contents: string;
109
117
  readonly contentType?: string;
118
+ /**
119
+ * How `contents` is encoded. Absent means `'utf8'`.
120
+ *
121
+ * @remarks
122
+ * **This field describes what the server actually produced, not what the
123
+ * client asked for**, and a client must branch on it rather than on its own
124
+ * request. A provider that does not implement `'base64'` ignores the request
125
+ * and answers with UTF-8 text; a client that base64-decoded on the strength of
126
+ * having *asked* would corrupt every such response. Trusting the field instead
127
+ * makes an older or simpler provider degrade to today's behavior rather than
128
+ * to garbage.
129
+ */
130
+ readonly encoding?: StorageContentEncoding;
110
131
  }
111
132
 
112
133
  /**
@@ -118,6 +139,27 @@ export declare interface IStoragePathRequest {
118
139
  readonly namespace?: StorageNamespace;
119
140
  }
120
141
 
142
+ /**
143
+ * Request for reading a file, with an optional preferred content encoding.
144
+ *
145
+ * @remarks
146
+ * `encoding` is a *preference*, not a demand: a provider that cannot honour it
147
+ * answers in `'utf8'` and says so on {@link IStorageFileResponse.encoding}. The
148
+ * response is the authority.
149
+ * @public
150
+ */
151
+ export declare interface IStorageReadFileRequest extends IStoragePathRequest {
152
+ /**
153
+ * Preferred wire encoding for the response's `contents`. Absent means `'utf8'`.
154
+ *
155
+ * @remarks
156
+ * A preference, not a demand — see {@link IStorageFileResponse.encoding}, which
157
+ * reports what the server actually produced and is the field a client must
158
+ * branch on.
159
+ */
160
+ readonly encoding?: StorageContentEncoding;
161
+ }
162
+
121
163
  /**
122
164
  * Request for sync operation.
123
165
  * @public
@@ -174,6 +216,29 @@ export declare function normalizeRequestPath(requestPath: string): string;
174
216
  */
175
217
  export declare function sanitizeNamespace(namespace?: string): Result<string>;
176
218
 
219
+ /**
220
+ * How a file's bytes are represented in the `contents` field on the wire.
221
+ *
222
+ * @remarks
223
+ * - `'utf8'` — `contents` is the file decoded as UTF-8 text. This is the
224
+ * historical (and default) representation, and it is **lossy**: the decode is
225
+ * the lenient WHATWG one, so any byte sequence that is not valid UTF-8 has
226
+ * already been replaced by U+FFFD by the time it reaches the wire, and the
227
+ * substitution is not recoverable downstream.
228
+ * - `'base64'` — `contents` is the file's bytes, base64-encoded. Byte-faithful
229
+ * and therefore the only representation over which a consumer can decide
230
+ * whether the stored bytes were valid UTF-8, or read genuinely binary content.
231
+ * Costs roughly 33% more payload.
232
+ * @public
233
+ */
234
+ export declare type StorageContentEncoding = 'utf8' | 'base64';
235
+
236
+ /**
237
+ * Converter for the wire content encoding.
238
+ * @public
239
+ */
240
+ export declare const storageContentEncoding: Converter<StorageContentEncoding>;
241
+
177
242
  /**
178
243
  * Converter for file responses.
179
244
  * @public
@@ -198,6 +263,12 @@ export declare type StorageNamespace = string;
198
263
  */
199
264
  export declare const storagePathRequest: Converter<IStoragePathRequest>;
200
265
 
266
+ /**
267
+ * Converter for read-file requests.
268
+ * @public
269
+ */
270
+ export declare const storageReadFileRequest: Converter<IStorageReadFileRequest>;
271
+
201
272
  /**
202
273
  * Converter for sync requests.
203
274
  * @public
@@ -5,7 +5,7 @@
5
5
  "toolPackages": [
6
6
  {
7
7
  "packageName": "@microsoft/api-extractor",
8
- "packageVersion": "7.57.7"
8
+ "packageVersion": "7.58.9"
9
9
  }
10
10
  ]
11
11
  }
@@ -0,0 +1,16 @@
1
+ // ESLint 9 flat config
2
+ const nodeProfile = require('@rushstack/eslint-config/flat/profile/node');
3
+ const packletsPlugin = require('@rushstack/eslint-config/flat/mixins/packlets');
4
+ const tsdocPlugin = require('@rushstack/eslint-config/flat/mixins/tsdoc');
5
+
6
+ module.exports = [
7
+ ...nodeProfile,
8
+ packletsPlugin,
9
+ ...tsdocPlugin,
10
+ {
11
+ // Override specific rules if needed
12
+ rules: {
13
+ '@rushstack/packlets/mechanics': 'warn'
14
+ }
15
+ }
16
+ ];
@@ -1,5 +1,5 @@
1
1
  import { type Converter } from '@fgv/ts-utils';
2
- import type { IStorageFileResponse, IStoragePathRequest, IStorageSyncRequest, IStorageTreeChildrenResponse, IStorageTreeItem, IStorageWriteFileRequest } from './model';
2
+ import type { IStorageFileResponse, IStoragePathRequest, IStorageReadFileRequest, IStorageSyncRequest, IStorageTreeChildrenResponse, IStorageTreeItem, IStorageWriteFileRequest, StorageContentEncoding } from './model';
3
3
  /**
4
4
  * Converter for {@link IStorageTreeItem}.
5
5
  * @public
@@ -10,6 +10,16 @@ export declare const storageTreeItem: Converter<IStorageTreeItem>;
10
10
  * @public
11
11
  */
12
12
  export declare const storagePathRequest: Converter<IStoragePathRequest>;
13
+ /**
14
+ * Converter for the wire content encoding.
15
+ * @public
16
+ */
17
+ export declare const storageContentEncoding: Converter<StorageContentEncoding>;
18
+ /**
19
+ * Converter for read-file requests.
20
+ * @public
21
+ */
22
+ export declare const storageReadFileRequest: Converter<IStorageReadFileRequest>;
13
23
  /**
14
24
  * Converter for write-file requests.
15
25
  * @public
@@ -1 +1 @@
1
- {"version":3,"file":"converters.d.ts","sourceRoot":"","sources":["../../../src/packlets/storage/converters.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAE,KAAK,SAAS,EAAc,MAAM,eAAe,CAAC;AAE3D,OAAO,KAAK,EACV,oBAAoB,EACpB,mBAAmB,EACnB,mBAAmB,EACnB,4BAA4B,EAC5B,gBAAgB,EAChB,wBAAwB,EAEzB,MAAM,SAAS,CAAC;AAOjB;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,SAAS,CAAC,gBAAgB,CAItD,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAAS,CAAC,mBAAmB,CAI1D,CAAC;AAEL;;;GAGG;AACH,eAAO,MAAM,uBAAuB,EAAE,SAAS,CAAC,wBAAwB,CAMpE,CAAC;AAEL;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAAS,CAAC,mBAAmB,CAG1D,CAAC;AAEL;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,SAAS,CAAC,oBAAoB,CAK5D,CAAC;AAEL;;;GAGG;AACH,eAAO,MAAM,2BAA2B,EAAE,SAAS,CAAC,4BAA4B,CAI5E,CAAC"}
1
+ {"version":3,"file":"converters.d.ts","sourceRoot":"","sources":["../../../src/packlets/storage/converters.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAE,KAAK,SAAS,EAAc,MAAM,eAAe,CAAC;AAE3D,OAAO,KAAK,EACV,oBAAoB,EACpB,mBAAmB,EACnB,uBAAuB,EACvB,mBAAmB,EACnB,4BAA4B,EAC5B,gBAAgB,EAChB,wBAAwB,EACxB,sBAAsB,EAEvB,MAAM,SAAS,CAAC;AAOjB;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,SAAS,CAAC,gBAAgB,CAItD,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAAS,CAAC,mBAAmB,CAI1D,CAAC;AAEL;;;GAGG;AACH,eAAO,MAAM,sBAAsB,EAAE,SAAS,CAAC,sBAAsB,CACG,CAAC;AAEzE;;;GAGG;AACH,eAAO,MAAM,sBAAsB,EAAE,SAAS,CAAC,uBAAuB,CAKlE,CAAC;AAEL;;;GAGG;AACH,eAAO,MAAM,uBAAuB,EAAE,SAAS,CAAC,wBAAwB,CAMpE,CAAC;AAEL;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAAS,CAAC,mBAAmB,CAG1D,CAAC;AAEL;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,SAAS,CAAC,oBAAoB,CAM5D,CAAC;AAEL;;;GAGG;AACH,eAAO,MAAM,2BAA2B,EAAE,SAAS,CAAC,4BAA4B,CAI5E,CAAC"}
@@ -21,7 +21,7 @@
21
21
  * SOFTWARE.
22
22
  */
23
23
  Object.defineProperty(exports, "__esModule", { value: true });
24
- exports.storageTreeChildrenResponse = exports.storageFileResponse = exports.storageSyncRequest = exports.storageWriteFileRequest = exports.storagePathRequest = exports.storageTreeItem = void 0;
24
+ exports.storageTreeChildrenResponse = exports.storageFileResponse = exports.storageSyncRequest = exports.storageWriteFileRequest = exports.storageReadFileRequest = exports.storageContentEncoding = exports.storagePathRequest = exports.storageTreeItem = void 0;
25
25
  const ts_utils_1 = require("@fgv/ts-utils");
26
26
  const storageItemType = ts_utils_1.Converters.enumeratedValue([
27
27
  'file',
@@ -44,6 +44,20 @@ exports.storagePathRequest = ts_utils_1.Converters.strictObject({
44
44
  path: ts_utils_1.Converters.string,
45
45
  namespace: ts_utils_1.Converters.string.optional()
46
46
  });
47
+ /**
48
+ * Converter for the wire content encoding.
49
+ * @public
50
+ */
51
+ exports.storageContentEncoding = ts_utils_1.Converters.enumeratedValue(['utf8', 'base64']);
52
+ /**
53
+ * Converter for read-file requests.
54
+ * @public
55
+ */
56
+ exports.storageReadFileRequest = ts_utils_1.Converters.strictObject({
57
+ path: ts_utils_1.Converters.string,
58
+ namespace: ts_utils_1.Converters.string.optional(),
59
+ encoding: exports.storageContentEncoding.optional()
60
+ });
47
61
  /**
48
62
  * Converter for write-file requests.
49
63
  * @public
@@ -68,7 +82,8 @@ exports.storageSyncRequest = ts_utils_1.Converters.strictObject({
68
82
  exports.storageFileResponse = ts_utils_1.Converters.strictObject({
69
83
  path: ts_utils_1.Converters.string,
70
84
  contents: ts_utils_1.Converters.string,
71
- contentType: ts_utils_1.Converters.string.optional()
85
+ contentType: ts_utils_1.Converters.string.optional(),
86
+ encoding: exports.storageContentEncoding.optional()
72
87
  });
73
88
  /**
74
89
  * Converter for children list responses.
@@ -1 +1 @@
1
- {"version":3,"file":"converters.js","sourceRoot":"","sources":["../../../src/packlets/storage/converters.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;;;AAEH,4CAA2D;AAY3D,MAAM,eAAe,GAA+B,qBAAU,CAAC,eAAe,CAAkB;IAC9F,MAAM;IACN,WAAW;CACZ,CAAC,CAAC;AAEH;;;GAGG;AACU,QAAA,eAAe,GAAgC,qBAAU,CAAC,YAAY,CAAmB;IACpG,IAAI,EAAE,qBAAU,CAAC,MAAM;IACvB,IAAI,EAAE,qBAAU,CAAC,MAAM;IACvB,IAAI,EAAE,eAAe;CACtB,CAAC,CAAC;AAEH;;;GAGG;AACU,QAAA,kBAAkB,GAC7B,qBAAU,CAAC,YAAY,CAAsB;IAC3C,IAAI,EAAE,qBAAU,CAAC,MAAM;IACvB,SAAS,EAAE,qBAAU,CAAC,MAAM,CAAC,QAAQ,EAAE;CACxC,CAAC,CAAC;AAEL;;;GAGG;AACU,QAAA,uBAAuB,GAClC,qBAAU,CAAC,YAAY,CAA2B;IAChD,IAAI,EAAE,qBAAU,CAAC,MAAM;IACvB,QAAQ,EAAE,qBAAU,CAAC,MAAM;IAC3B,WAAW,EAAE,qBAAU,CAAC,MAAM,CAAC,QAAQ,EAAE;IACzC,SAAS,EAAE,qBAAU,CAAC,MAAM,CAAC,QAAQ,EAAE;CACxC,CAAC,CAAC;AAEL;;;GAGG;AACU,QAAA,kBAAkB,GAC7B,qBAAU,CAAC,YAAY,CAAsB;IAC3C,SAAS,EAAE,qBAAU,CAAC,MAAM,CAAC,QAAQ,EAAE;CACxC,CAAC,CAAC;AAEL;;;GAGG;AACU,QAAA,mBAAmB,GAC9B,qBAAU,CAAC,YAAY,CAAuB;IAC5C,IAAI,EAAE,qBAAU,CAAC,MAAM;IACvB,QAAQ,EAAE,qBAAU,CAAC,MAAM;IAC3B,WAAW,EAAE,qBAAU,CAAC,MAAM,CAAC,QAAQ,EAAE;CAC1C,CAAC,CAAC;AAEL;;;GAGG;AACU,QAAA,2BAA2B,GACtC,qBAAU,CAAC,YAAY,CAA+B;IACpD,IAAI,EAAE,qBAAU,CAAC,MAAM;IACvB,QAAQ,EAAE,qBAAU,CAAC,OAAO,CAAC,uBAAe,CAAC;CAC9C,CAAC,CAAC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport { type Converter, Converters } from '@fgv/ts-utils';\n\nimport type {\n IStorageFileResponse,\n IStoragePathRequest,\n IStorageSyncRequest,\n IStorageTreeChildrenResponse,\n IStorageTreeItem,\n IStorageWriteFileRequest,\n StorageItemType\n} from './model';\n\nconst storageItemType: Converter<StorageItemType> = Converters.enumeratedValue<StorageItemType>([\n 'file',\n 'directory'\n]);\n\n/**\n * Converter for {@link IStorageTreeItem}.\n * @public\n */\nexport const storageTreeItem: Converter<IStorageTreeItem> = Converters.strictObject<IStorageTreeItem>({\n path: Converters.string,\n name: Converters.string,\n type: storageItemType\n});\n\n/**\n * Converter for path-based requests.\n * @public\n */\nexport const storagePathRequest: Converter<IStoragePathRequest> =\n Converters.strictObject<IStoragePathRequest>({\n path: Converters.string,\n namespace: Converters.string.optional()\n });\n\n/**\n * Converter for write-file requests.\n * @public\n */\nexport const storageWriteFileRequest: Converter<IStorageWriteFileRequest> =\n Converters.strictObject<IStorageWriteFileRequest>({\n path: Converters.string,\n contents: Converters.string,\n contentType: Converters.string.optional(),\n namespace: Converters.string.optional()\n });\n\n/**\n * Converter for sync requests.\n * @public\n */\nexport const storageSyncRequest: Converter<IStorageSyncRequest> =\n Converters.strictObject<IStorageSyncRequest>({\n namespace: Converters.string.optional()\n });\n\n/**\n * Converter for file responses.\n * @public\n */\nexport const storageFileResponse: Converter<IStorageFileResponse> =\n Converters.strictObject<IStorageFileResponse>({\n path: Converters.string,\n contents: Converters.string,\n contentType: Converters.string.optional()\n });\n\n/**\n * Converter for children list responses.\n * @public\n */\nexport const storageTreeChildrenResponse: Converter<IStorageTreeChildrenResponse> =\n Converters.strictObject<IStorageTreeChildrenResponse>({\n path: Converters.string,\n children: Converters.arrayOf(storageTreeItem)\n });\n"]}
1
+ {"version":3,"file":"converters.js","sourceRoot":"","sources":["../../../src/packlets/storage/converters.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;;;AAEH,4CAA2D;AAc3D,MAAM,eAAe,GAA+B,qBAAU,CAAC,eAAe,CAAkB;IAC9F,MAAM;IACN,WAAW;CACZ,CAAC,CAAC;AAEH;;;GAGG;AACU,QAAA,eAAe,GAAgC,qBAAU,CAAC,YAAY,CAAmB;IACpG,IAAI,EAAE,qBAAU,CAAC,MAAM;IACvB,IAAI,EAAE,qBAAU,CAAC,MAAM;IACvB,IAAI,EAAE,eAAe;CACtB,CAAC,CAAC;AAEH;;;GAGG;AACU,QAAA,kBAAkB,GAC7B,qBAAU,CAAC,YAAY,CAAsB;IAC3C,IAAI,EAAE,qBAAU,CAAC,MAAM;IACvB,SAAS,EAAE,qBAAU,CAAC,MAAM,CAAC,QAAQ,EAAE;CACxC,CAAC,CAAC;AAEL;;;GAGG;AACU,QAAA,sBAAsB,GACjC,qBAAU,CAAC,eAAe,CAAyB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;AAEzE;;;GAGG;AACU,QAAA,sBAAsB,GACjC,qBAAU,CAAC,YAAY,CAA0B;IAC/C,IAAI,EAAE,qBAAU,CAAC,MAAM;IACvB,SAAS,EAAE,qBAAU,CAAC,MAAM,CAAC,QAAQ,EAAE;IACvC,QAAQ,EAAE,8BAAsB,CAAC,QAAQ,EAAE;CAC5C,CAAC,CAAC;AAEL;;;GAGG;AACU,QAAA,uBAAuB,GAClC,qBAAU,CAAC,YAAY,CAA2B;IAChD,IAAI,EAAE,qBAAU,CAAC,MAAM;IACvB,QAAQ,EAAE,qBAAU,CAAC,MAAM;IAC3B,WAAW,EAAE,qBAAU,CAAC,MAAM,CAAC,QAAQ,EAAE;IACzC,SAAS,EAAE,qBAAU,CAAC,MAAM,CAAC,QAAQ,EAAE;CACxC,CAAC,CAAC;AAEL;;;GAGG;AACU,QAAA,kBAAkB,GAC7B,qBAAU,CAAC,YAAY,CAAsB;IAC3C,SAAS,EAAE,qBAAU,CAAC,MAAM,CAAC,QAAQ,EAAE;CACxC,CAAC,CAAC;AAEL;;;GAGG;AACU,QAAA,mBAAmB,GAC9B,qBAAU,CAAC,YAAY,CAAuB;IAC5C,IAAI,EAAE,qBAAU,CAAC,MAAM;IACvB,QAAQ,EAAE,qBAAU,CAAC,MAAM;IAC3B,WAAW,EAAE,qBAAU,CAAC,MAAM,CAAC,QAAQ,EAAE;IACzC,QAAQ,EAAE,8BAAsB,CAAC,QAAQ,EAAE;CAC5C,CAAC,CAAC;AAEL;;;GAGG;AACU,QAAA,2BAA2B,GACtC,qBAAU,CAAC,YAAY,CAA+B;IACpD,IAAI,EAAE,qBAAU,CAAC,MAAM;IACvB,QAAQ,EAAE,qBAAU,CAAC,OAAO,CAAC,uBAAe,CAAC;CAC9C,CAAC,CAAC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport { type Converter, Converters } from '@fgv/ts-utils';\n\nimport type {\n IStorageFileResponse,\n IStoragePathRequest,\n IStorageReadFileRequest,\n IStorageSyncRequest,\n IStorageTreeChildrenResponse,\n IStorageTreeItem,\n IStorageWriteFileRequest,\n StorageContentEncoding,\n StorageItemType\n} from './model';\n\nconst storageItemType: Converter<StorageItemType> = Converters.enumeratedValue<StorageItemType>([\n 'file',\n 'directory'\n]);\n\n/**\n * Converter for {@link IStorageTreeItem}.\n * @public\n */\nexport const storageTreeItem: Converter<IStorageTreeItem> = Converters.strictObject<IStorageTreeItem>({\n path: Converters.string,\n name: Converters.string,\n type: storageItemType\n});\n\n/**\n * Converter for path-based requests.\n * @public\n */\nexport const storagePathRequest: Converter<IStoragePathRequest> =\n Converters.strictObject<IStoragePathRequest>({\n path: Converters.string,\n namespace: Converters.string.optional()\n });\n\n/**\n * Converter for the wire content encoding.\n * @public\n */\nexport const storageContentEncoding: Converter<StorageContentEncoding> =\n Converters.enumeratedValue<StorageContentEncoding>(['utf8', 'base64']);\n\n/**\n * Converter for read-file requests.\n * @public\n */\nexport const storageReadFileRequest: Converter<IStorageReadFileRequest> =\n Converters.strictObject<IStorageReadFileRequest>({\n path: Converters.string,\n namespace: Converters.string.optional(),\n encoding: storageContentEncoding.optional()\n });\n\n/**\n * Converter for write-file requests.\n * @public\n */\nexport const storageWriteFileRequest: Converter<IStorageWriteFileRequest> =\n Converters.strictObject<IStorageWriteFileRequest>({\n path: Converters.string,\n contents: Converters.string,\n contentType: Converters.string.optional(),\n namespace: Converters.string.optional()\n });\n\n/**\n * Converter for sync requests.\n * @public\n */\nexport const storageSyncRequest: Converter<IStorageSyncRequest> =\n Converters.strictObject<IStorageSyncRequest>({\n namespace: Converters.string.optional()\n });\n\n/**\n * Converter for file responses.\n * @public\n */\nexport const storageFileResponse: Converter<IStorageFileResponse> =\n Converters.strictObject<IStorageFileResponse>({\n path: Converters.string,\n contents: Converters.string,\n contentType: Converters.string.optional(),\n encoding: storageContentEncoding.optional()\n });\n\n/**\n * Converter for children list responses.\n * @public\n */\nexport const storageTreeChildrenResponse: Converter<IStorageTreeChildrenResponse> =\n Converters.strictObject<IStorageTreeChildrenResponse>({\n path: Converters.string,\n children: Converters.arrayOf(storageTreeItem)\n });\n"]}
@@ -1,5 +1,5 @@
1
1
  import { type Result } from '@fgv/ts-utils';
2
- import type { IHttpStorageProvider, IStorageFileResponse, IStorageSyncResponse, IStorageTreeItem } from './model';
2
+ import type { IHttpStorageProvider, IStorageFileResponse, IStorageSyncResponse, IStorageTreeItem, StorageContentEncoding } from './model';
3
3
  import type { IHttpStorageProviderFactory } from './provider';
4
4
  /**
5
5
  * Options for creating filesystem-backed storage providers.
@@ -17,7 +17,7 @@ export declare class FsStorageProvider implements IHttpStorageProvider {
17
17
  constructor(rootPath: string);
18
18
  getItem(itemPath: string): Promise<Result<IStorageTreeItem>>;
19
19
  getChildren(itemPath: string): Promise<Result<ReadonlyArray<IStorageTreeItem>>>;
20
- getFile(itemPath: string): Promise<Result<IStorageFileResponse>>;
20
+ getFile(itemPath: string, encoding?: StorageContentEncoding): Promise<Result<IStorageFileResponse>>;
21
21
  saveFile(itemPath: string, contents: string, contentType?: string): Promise<Result<IStorageFileResponse>>;
22
22
  deleteFile(itemPath: string): Promise<Result<boolean>>;
23
23
  createDirectory(itemPath: string): Promise<Result<IStorageTreeItem>>;
@@ -1 +1 @@
1
- {"version":3,"file":"fsProvider.d.ts","sourceRoot":"","sources":["../../../src/packlets/storage/fsProvider.ts"],"names":[],"mappings":"AA0BA,OAAO,EAAuB,KAAK,MAAM,EAAW,MAAM,eAAe,CAAC;AAE1E,OAAO,KAAK,EACV,oBAAoB,EACpB,oBAAoB,EACpB,oBAAoB,EACpB,gBAAgB,EAEjB,MAAM,SAAS,CAAC;AACjB,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAC;AAE9D;;;GAGG;AACH,MAAM,WAAW,gCAAgC;IAC/C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAMD;;;GAGG;AACH,qBAAa,iBAAkB,YAAW,oBAAoB;IAC5D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEhB,QAAQ,EAAE,MAAM;IAItB,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;IAa5D,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAa/E,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;IAoBhE,QAAQ,CACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;IAmB3B,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAiBtD,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;IAapE,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;IAI1D,OAAO,CAAC,oBAAoB;IAU5B,OAAO,CAAC,WAAW;CASpB;AAuBD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAMhE;AAED;;;GAGG;AACH,qBAAa,wBAAyB,YAAW,2BAA2B;IAC1E,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEhB,OAAO,EAAE,gCAAgC;IAIrD,YAAY,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,oBAAoB,CAAC;CAmBtE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CASpE"}
1
+ {"version":3,"file":"fsProvider.d.ts","sourceRoot":"","sources":["../../../src/packlets/storage/fsProvider.ts"],"names":[],"mappings":"AA0BA,OAAO,EAAuB,KAAK,MAAM,EAAW,MAAM,eAAe,CAAC;AAE1E,OAAO,KAAK,EACV,oBAAoB,EACpB,oBAAoB,EACpB,oBAAoB,EACpB,gBAAgB,EAChB,sBAAsB,EAEvB,MAAM,SAAS,CAAC;AACjB,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAC;AAE9D;;;GAGG;AACH,MAAM,WAAW,gCAAgC;IAC/C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAMD;;;GAGG;AACH,qBAAa,iBAAkB,YAAW,oBAAoB;IAC5D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEhB,QAAQ,EAAE,MAAM;IAItB,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;IAa5D,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAa/E,OAAO,CAClB,QAAQ,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE,sBAAsB,GAChC,OAAO,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;IAoC3B,QAAQ,CACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;IAmB3B,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAiBtD,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;IAapE,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;IAI1D,OAAO,CAAC,oBAAoB;IAU5B,OAAO,CAAC,WAAW;CASpB;AAuBD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAMhE;AAED;;;GAGG;AACH,qBAAa,wBAAyB,YAAW,2BAA2B;IAC1E,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEhB,OAAO,EAAE,gCAAgC;IAIrD,YAAY,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,oBAAoB,CAAC;CAmBtE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CASpE"}
@@ -98,7 +98,7 @@ class FsStorageProvider {
98
98
  return (0, ts_utils_1.fail)(`${itemPath}: ${_toMessage(err)}`);
99
99
  }
100
100
  }
101
- async getFile(itemPath) {
101
+ async getFile(itemPath, encoding) {
102
102
  const resolved = this._resolveAbsolutePath(itemPath);
103
103
  if (resolved.isFailure()) {
104
104
  return (0, ts_utils_1.fail)(resolved.message);
@@ -108,6 +108,22 @@ class FsStorageProvider {
108
108
  if (!stats.isFile()) {
109
109
  return (0, ts_utils_1.fail)(`${itemPath}: not a file`);
110
110
  }
111
+ // Byte-faithful only when asked. On the base64 branch `readFile` is called
112
+ // with no encoding argument, so it yields the raw bytes and base64 carries
113
+ // them intact. The default branch below still decodes as UTF-8 — lossy, but
114
+ // it is what every existing caller already receives.
115
+ if (encoding === 'base64') {
116
+ const bytes = await fsp.readFile(resolved.value);
117
+ return (0, ts_utils_1.succeed)({
118
+ path: normalizeRequestPath(itemPath),
119
+ contents: bytes.toString('base64'),
120
+ encoding: 'base64'
121
+ });
122
+ }
123
+ // Deliberately no `encoding` field on the default path: absent means `'utf8'`,
124
+ // so an existing deployment's responses stay byte-identical. It also leaves a
125
+ // usable signal — a client that asked for base64 and got no field back knows
126
+ // the request was not honoured, rather than having to guess.
111
127
  const contents = await fsp.readFile(resolved.value, 'utf8');
112
128
  return (0, ts_utils_1.succeed)({
113
129
  path: normalizeRequestPath(itemPath),
@@ -1 +1 @@
1
- {"version":3,"file":"fsProvider.js","sourceRoot":"","sources":["../../../src/packlets/storage/fsProvider.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport * as fs from 'fs';\nimport * as fsp from 'fs/promises';\nimport * as path from 'path';\n\nimport { captureResult, fail, type Result, succeed } from '@fgv/ts-utils';\n\nimport type {\n IHttpStorageProvider,\n IStorageFileResponse,\n IStorageSyncResponse,\n IStorageTreeItem,\n StorageItemType\n} from './model';\nimport type { IHttpStorageProviderFactory } from './provider';\n\n/**\n * Options for creating filesystem-backed storage providers.\n * @public\n */\nexport interface IFsStorageProviderFactoryOptions {\n readonly rootPath: string;\n}\n\nfunction toItemType(stats: fs.Stats): StorageItemType {\n return stats.isDirectory() ? 'directory' : 'file';\n}\n\n/**\n * Filesystem-backed implementation of {@link IHttpStorageProvider}.\n * @public\n */\nexport class FsStorageProvider implements IHttpStorageProvider {\n private readonly _rootPath: string;\n\n public constructor(rootPath: string) {\n this._rootPath = rootPath;\n }\n\n public async getItem(itemPath: string): Promise<Result<IStorageTreeItem>> {\n const resolved = this._resolveAbsolutePath(itemPath);\n if (resolved.isFailure()) {\n return fail(resolved.message);\n }\n try {\n const stats = await fsp.stat(resolved.value);\n return succeed(this._toTreeItem(itemPath, toItemType(stats)));\n } catch (err: unknown) {\n return fail(`${itemPath}: ${_toMessage(err)}`);\n }\n }\n\n public async getChildren(itemPath: string): Promise<Result<ReadonlyArray<IStorageTreeItem>>> {\n const resolved = this._resolveAbsolutePath(itemPath);\n if (resolved.isFailure()) {\n return fail(resolved.message);\n }\n try {\n const entries = await fsp.readdir(resolved.value, { withFileTypes: true });\n return _mapEntries(entries, itemPath);\n } catch (err: unknown) {\n return fail(`${itemPath}: ${_toMessage(err)}`);\n }\n }\n\n public async getFile(itemPath: string): Promise<Result<IStorageFileResponse>> {\n const resolved = this._resolveAbsolutePath(itemPath);\n if (resolved.isFailure()) {\n return fail(resolved.message);\n }\n try {\n const stats = await fsp.stat(resolved.value);\n if (!stats.isFile()) {\n return fail(`${itemPath}: not a file`);\n }\n const contents = await fsp.readFile(resolved.value, 'utf8');\n return succeed({\n path: normalizeRequestPath(itemPath),\n contents\n });\n } catch (err: unknown) {\n return fail(`${itemPath}: ${_toMessage(err)}`);\n }\n }\n\n public async saveFile(\n itemPath: string,\n contents: string,\n contentType?: string\n ): Promise<Result<IStorageFileResponse>> {\n const resolved = this._resolveAbsolutePath(itemPath);\n if (resolved.isFailure()) {\n return fail(resolved.message);\n }\n try {\n const parentDir = path.dirname(resolved.value);\n await fsp.mkdir(parentDir, { recursive: true });\n await fsp.writeFile(resolved.value, contents, 'utf8');\n return succeed({\n path: normalizeRequestPath(itemPath),\n contents,\n contentType\n });\n } catch (err: unknown) {\n return fail(`${itemPath}: ${_toMessage(err)}`);\n }\n }\n\n public async deleteFile(itemPath: string): Promise<Result<boolean>> {\n const resolved = this._resolveAbsolutePath(itemPath);\n if (resolved.isFailure()) {\n return fail(resolved.message);\n }\n try {\n const stats = await fsp.stat(resolved.value);\n if (!stats.isFile()) {\n return fail(`${itemPath}: not a file`);\n }\n await fsp.unlink(resolved.value);\n return succeed(true);\n } catch (err: unknown) {\n return fail(`${itemPath}: ${_toMessage(err)}`);\n }\n }\n\n public async createDirectory(itemPath: string): Promise<Result<IStorageTreeItem>> {\n const resolved = this._resolveAbsolutePath(itemPath);\n if (resolved.isFailure()) {\n return fail(resolved.message);\n }\n try {\n await fsp.mkdir(resolved.value, { recursive: true });\n return succeed(this._toTreeItem(itemPath, 'directory'));\n } catch (err: unknown) {\n return fail(`${itemPath}: ${_toMessage(err)}`);\n }\n }\n\n public async sync(): Promise<Result<IStorageSyncResponse>> {\n return succeed({ synced: 0 });\n }\n\n private _resolveAbsolutePath(requestPath: string): Result<string> {\n const normalized = normalizeRequestPath(requestPath);\n const candidate = path.resolve(this._rootPath, `.${normalized}`);\n const relative = path.relative(this._rootPath, candidate);\n if (relative.startsWith('..') || path.isAbsolute(relative)) {\n return fail(`${requestPath}: path is outside storage root`);\n }\n return succeed(candidate);\n }\n\n private _toTreeItem(itemPath: string, type: StorageItemType): IStorageTreeItem {\n const normalizedPath = normalizeRequestPath(itemPath);\n const name = normalizedPath === '/' ? '/' : path.posix.basename(normalizedPath);\n return {\n path: normalizedPath,\n name,\n type\n };\n }\n}\n\nfunction _mapEntries(\n entries: ReadonlyArray<fs.Dirent>,\n parentPath: string\n): Result<ReadonlyArray<IStorageTreeItem>> {\n const normalizedParent = normalizeRequestPath(parentPath);\n const items: IStorageTreeItem[] = entries.map((entry) => {\n const isDirectory = entry.isDirectory();\n const childPath = normalizedParent === '/' ? `/${entry.name}` : `${normalizedParent}/${entry.name}`;\n return {\n path: childPath,\n name: entry.name,\n type: isDirectory ? 'directory' : 'file'\n };\n });\n return succeed(items);\n}\n\nfunction _toMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n/**\n * Normalizes a request path to a consistent POSIX format.\n * @public\n */\nexport function normalizeRequestPath(requestPath: string): string {\n if (requestPath.length === 0) {\n return '/';\n }\n const normalized = path.posix.normalize(requestPath.startsWith('/') ? requestPath : `/${requestPath}`);\n return normalized.startsWith('/') ? normalized : `/${normalized}`;\n}\n\n/**\n * Namespace-aware provider factory backed by filesystem directories.\n * @public\n */\nexport class FsStorageProviderFactory implements IHttpStorageProviderFactory {\n private readonly _rootPath: string;\n\n public constructor(options: IFsStorageProviderFactoryOptions) {\n this._rootPath = path.resolve(options.rootPath);\n }\n\n public forNamespace(namespace?: string): Result<IHttpStorageProvider> {\n const safeNamespace = sanitizeNamespace(namespace);\n if (safeNamespace.isFailure()) {\n return fail(safeNamespace.message);\n }\n\n const namespaceRoot = path.resolve(this._rootPath, safeNamespace.value);\n const relative = path.relative(this._rootPath, namespaceRoot);\n if (relative.startsWith('..') || path.isAbsolute(relative)) {\n return fail(`namespace '${safeNamespace.value}' is outside root`);\n }\n\n const ensureResult = captureResult(() => fs.mkdirSync(namespaceRoot, { recursive: true }));\n if (ensureResult.isFailure()) {\n return fail(`namespace '${safeNamespace.value}': ${ensureResult.message}`);\n }\n\n return succeed(new FsStorageProvider(namespaceRoot));\n }\n}\n\n/**\n * Sanitize namespace path segment.\n * @public\n */\nexport function sanitizeNamespace(namespace?: string): Result<string> {\n if (!namespace || namespace.trim().length === 0) {\n return succeed('default');\n }\n const trimmed = namespace.trim();\n if (!/^[a-zA-Z0-9._-]+$/.test(trimmed)) {\n return fail(`namespace '${namespace}': only [a-zA-Z0-9._-] allowed`);\n }\n return succeed(trimmed);\n}\n"]}
1
+ {"version":3,"file":"fsProvider.js","sourceRoot":"","sources":["../../../src/packlets/storage/fsProvider.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport * as fs from 'fs';\nimport * as fsp from 'fs/promises';\nimport * as path from 'path';\n\nimport { captureResult, fail, type Result, succeed } from '@fgv/ts-utils';\n\nimport type {\n IHttpStorageProvider,\n IStorageFileResponse,\n IStorageSyncResponse,\n IStorageTreeItem,\n StorageContentEncoding,\n StorageItemType\n} from './model';\nimport type { IHttpStorageProviderFactory } from './provider';\n\n/**\n * Options for creating filesystem-backed storage providers.\n * @public\n */\nexport interface IFsStorageProviderFactoryOptions {\n readonly rootPath: string;\n}\n\nfunction toItemType(stats: fs.Stats): StorageItemType {\n return stats.isDirectory() ? 'directory' : 'file';\n}\n\n/**\n * Filesystem-backed implementation of {@link IHttpStorageProvider}.\n * @public\n */\nexport class FsStorageProvider implements IHttpStorageProvider {\n private readonly _rootPath: string;\n\n public constructor(rootPath: string) {\n this._rootPath = rootPath;\n }\n\n public async getItem(itemPath: string): Promise<Result<IStorageTreeItem>> {\n const resolved = this._resolveAbsolutePath(itemPath);\n if (resolved.isFailure()) {\n return fail(resolved.message);\n }\n try {\n const stats = await fsp.stat(resolved.value);\n return succeed(this._toTreeItem(itemPath, toItemType(stats)));\n } catch (err: unknown) {\n return fail(`${itemPath}: ${_toMessage(err)}`);\n }\n }\n\n public async getChildren(itemPath: string): Promise<Result<ReadonlyArray<IStorageTreeItem>>> {\n const resolved = this._resolveAbsolutePath(itemPath);\n if (resolved.isFailure()) {\n return fail(resolved.message);\n }\n try {\n const entries = await fsp.readdir(resolved.value, { withFileTypes: true });\n return _mapEntries(entries, itemPath);\n } catch (err: unknown) {\n return fail(`${itemPath}: ${_toMessage(err)}`);\n }\n }\n\n public async getFile(\n itemPath: string,\n encoding?: StorageContentEncoding\n ): Promise<Result<IStorageFileResponse>> {\n const resolved = this._resolveAbsolutePath(itemPath);\n if (resolved.isFailure()) {\n return fail(resolved.message);\n }\n try {\n const stats = await fsp.stat(resolved.value);\n if (!stats.isFile()) {\n return fail(`${itemPath}: not a file`);\n }\n // Byte-faithful only when asked. On the base64 branch `readFile` is called\n // with no encoding argument, so it yields the raw bytes and base64 carries\n // them intact. The default branch below still decodes as UTF-8 — lossy, but\n // it is what every existing caller already receives.\n if (encoding === 'base64') {\n const bytes = await fsp.readFile(resolved.value);\n return succeed({\n path: normalizeRequestPath(itemPath),\n contents: bytes.toString('base64'),\n encoding: 'base64'\n });\n }\n // Deliberately no `encoding` field on the default path: absent means `'utf8'`,\n // so an existing deployment's responses stay byte-identical. It also leaves a\n // usable signal — a client that asked for base64 and got no field back knows\n // the request was not honoured, rather than having to guess.\n const contents = await fsp.readFile(resolved.value, 'utf8');\n return succeed({\n path: normalizeRequestPath(itemPath),\n contents\n });\n } catch (err: unknown) {\n return fail(`${itemPath}: ${_toMessage(err)}`);\n }\n }\n\n public async saveFile(\n itemPath: string,\n contents: string,\n contentType?: string\n ): Promise<Result<IStorageFileResponse>> {\n const resolved = this._resolveAbsolutePath(itemPath);\n if (resolved.isFailure()) {\n return fail(resolved.message);\n }\n try {\n const parentDir = path.dirname(resolved.value);\n await fsp.mkdir(parentDir, { recursive: true });\n await fsp.writeFile(resolved.value, contents, 'utf8');\n return succeed({\n path: normalizeRequestPath(itemPath),\n contents,\n contentType\n });\n } catch (err: unknown) {\n return fail(`${itemPath}: ${_toMessage(err)}`);\n }\n }\n\n public async deleteFile(itemPath: string): Promise<Result<boolean>> {\n const resolved = this._resolveAbsolutePath(itemPath);\n if (resolved.isFailure()) {\n return fail(resolved.message);\n }\n try {\n const stats = await fsp.stat(resolved.value);\n if (!stats.isFile()) {\n return fail(`${itemPath}: not a file`);\n }\n await fsp.unlink(resolved.value);\n return succeed(true);\n } catch (err: unknown) {\n return fail(`${itemPath}: ${_toMessage(err)}`);\n }\n }\n\n public async createDirectory(itemPath: string): Promise<Result<IStorageTreeItem>> {\n const resolved = this._resolveAbsolutePath(itemPath);\n if (resolved.isFailure()) {\n return fail(resolved.message);\n }\n try {\n await fsp.mkdir(resolved.value, { recursive: true });\n return succeed(this._toTreeItem(itemPath, 'directory'));\n } catch (err: unknown) {\n return fail(`${itemPath}: ${_toMessage(err)}`);\n }\n }\n\n public async sync(): Promise<Result<IStorageSyncResponse>> {\n return succeed({ synced: 0 });\n }\n\n private _resolveAbsolutePath(requestPath: string): Result<string> {\n const normalized = normalizeRequestPath(requestPath);\n const candidate = path.resolve(this._rootPath, `.${normalized}`);\n const relative = path.relative(this._rootPath, candidate);\n if (relative.startsWith('..') || path.isAbsolute(relative)) {\n return fail(`${requestPath}: path is outside storage root`);\n }\n return succeed(candidate);\n }\n\n private _toTreeItem(itemPath: string, type: StorageItemType): IStorageTreeItem {\n const normalizedPath = normalizeRequestPath(itemPath);\n const name = normalizedPath === '/' ? '/' : path.posix.basename(normalizedPath);\n return {\n path: normalizedPath,\n name,\n type\n };\n }\n}\n\nfunction _mapEntries(\n entries: ReadonlyArray<fs.Dirent>,\n parentPath: string\n): Result<ReadonlyArray<IStorageTreeItem>> {\n const normalizedParent = normalizeRequestPath(parentPath);\n const items: IStorageTreeItem[] = entries.map((entry) => {\n const isDirectory = entry.isDirectory();\n const childPath = normalizedParent === '/' ? `/${entry.name}` : `${normalizedParent}/${entry.name}`;\n return {\n path: childPath,\n name: entry.name,\n type: isDirectory ? 'directory' : 'file'\n };\n });\n return succeed(items);\n}\n\nfunction _toMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n/**\n * Normalizes a request path to a consistent POSIX format.\n * @public\n */\nexport function normalizeRequestPath(requestPath: string): string {\n if (requestPath.length === 0) {\n return '/';\n }\n const normalized = path.posix.normalize(requestPath.startsWith('/') ? requestPath : `/${requestPath}`);\n return normalized.startsWith('/') ? normalized : `/${normalized}`;\n}\n\n/**\n * Namespace-aware provider factory backed by filesystem directories.\n * @public\n */\nexport class FsStorageProviderFactory implements IHttpStorageProviderFactory {\n private readonly _rootPath: string;\n\n public constructor(options: IFsStorageProviderFactoryOptions) {\n this._rootPath = path.resolve(options.rootPath);\n }\n\n public forNamespace(namespace?: string): Result<IHttpStorageProvider> {\n const safeNamespace = sanitizeNamespace(namespace);\n if (safeNamespace.isFailure()) {\n return fail(safeNamespace.message);\n }\n\n const namespaceRoot = path.resolve(this._rootPath, safeNamespace.value);\n const relative = path.relative(this._rootPath, namespaceRoot);\n if (relative.startsWith('..') || path.isAbsolute(relative)) {\n return fail(`namespace '${safeNamespace.value}' is outside root`);\n }\n\n const ensureResult = captureResult(() => fs.mkdirSync(namespaceRoot, { recursive: true }));\n if (ensureResult.isFailure()) {\n return fail(`namespace '${safeNamespace.value}': ${ensureResult.message}`);\n }\n\n return succeed(new FsStorageProvider(namespaceRoot));\n }\n}\n\n/**\n * Sanitize namespace path segment.\n * @public\n */\nexport function sanitizeNamespace(namespace?: string): Result<string> {\n if (!namespace || namespace.trim().length === 0) {\n return succeed('default');\n }\n const trimmed = namespace.trim();\n if (!/^[a-zA-Z0-9._-]+$/.test(trimmed)) {\n return fail(`namespace '${namespace}': only [a-zA-Z0-9._-] allowed`);\n }\n return succeed(trimmed);\n}\n"]}
@@ -9,6 +9,22 @@ export type StorageNamespace = string;
9
9
  * @public
10
10
  */
11
11
  export type StorageItemType = 'file' | 'directory';
12
+ /**
13
+ * How a file's bytes are represented in the `contents` field on the wire.
14
+ *
15
+ * @remarks
16
+ * - `'utf8'` — `contents` is the file decoded as UTF-8 text. This is the
17
+ * historical (and default) representation, and it is **lossy**: the decode is
18
+ * the lenient WHATWG one, so any byte sequence that is not valid UTF-8 has
19
+ * already been replaced by U+FFFD by the time it reaches the wire, and the
20
+ * substitution is not recoverable downstream.
21
+ * - `'base64'` — `contents` is the file's bytes, base64-encoded. Byte-faithful
22
+ * and therefore the only representation over which a consumer can decide
23
+ * whether the stored bytes were valid UTF-8, or read genuinely binary content.
24
+ * Costs roughly 33% more payload.
25
+ * @public
26
+ */
27
+ export type StorageContentEncoding = 'utf8' | 'base64';
12
28
  /**
13
29
  * Storage tree item metadata.
14
30
  * @public
@@ -34,6 +50,19 @@ export interface IStorageFileResponse {
34
50
  readonly path: string;
35
51
  readonly contents: string;
36
52
  readonly contentType?: string;
53
+ /**
54
+ * How `contents` is encoded. Absent means `'utf8'`.
55
+ *
56
+ * @remarks
57
+ * **This field describes what the server actually produced, not what the
58
+ * client asked for**, and a client must branch on it rather than on its own
59
+ * request. A provider that does not implement `'base64'` ignores the request
60
+ * and answers with UTF-8 text; a client that base64-decoded on the strength of
61
+ * having *asked* would corrupt every such response. Trusting the field instead
62
+ * makes an older or simpler provider degrade to today's behavior rather than
63
+ * to garbage.
64
+ */
65
+ readonly encoding?: StorageContentEncoding;
37
66
  }
38
67
  /**
39
68
  * Request for path-based operations.
@@ -43,6 +72,26 @@ export interface IStoragePathRequest {
43
72
  readonly path: string;
44
73
  readonly namespace?: StorageNamespace;
45
74
  }
75
+ /**
76
+ * Request for reading a file, with an optional preferred content encoding.
77
+ *
78
+ * @remarks
79
+ * `encoding` is a *preference*, not a demand: a provider that cannot honour it
80
+ * answers in `'utf8'` and says so on {@link IStorageFileResponse.encoding}. The
81
+ * response is the authority.
82
+ * @public
83
+ */
84
+ export interface IStorageReadFileRequest extends IStoragePathRequest {
85
+ /**
86
+ * Preferred wire encoding for the response's `contents`. Absent means `'utf8'`.
87
+ *
88
+ * @remarks
89
+ * A preference, not a demand — see {@link IStorageFileResponse.encoding}, which
90
+ * reports what the server actually produced and is the field a client must
91
+ * branch on.
92
+ */
93
+ readonly encoding?: StorageContentEncoding;
94
+ }
46
95
  /**
47
96
  * Request for writing file contents.
48
97
  * @public
@@ -73,7 +122,15 @@ export interface IStorageSyncResponse {
73
122
  export interface IHttpStorageProvider {
74
123
  getItem(path: string): Promise<Result<IStorageTreeItem>>;
75
124
  getChildren(path: string): Promise<Result<ReadonlyArray<IStorageTreeItem>>>;
76
- getFile(path: string): Promise<Result<IStorageFileResponse>>;
125
+ /**
126
+ * Reads a file.
127
+ * @param path - Path of the file to read.
128
+ * @param encoding - Preferred wire encoding for the returned `contents`. A
129
+ * provider that does not implement the requested encoding MUST answer in
130
+ * `'utf8'` and report that on the response, rather than failing — the field is
131
+ * a preference and the response is the authority.
132
+ */
133
+ getFile(path: string, encoding?: StorageContentEncoding): Promise<Result<IStorageFileResponse>>;
77
134
  saveFile(path: string, contents: string, contentType?: string): Promise<Result<IStorageFileResponse>>;
78
135
  deleteFile(path: string): Promise<Result<boolean>>;
79
136
  createDirectory(path: string): Promise<Result<IStorageTreeItem>>;
@@ -1 +1 @@
1
- {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../../src/packlets/storage/model.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAE5C;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC;AAEtC;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,WAAW,CAAC;AAEnD;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;CAChC;AAED;;;GAGG;AACH,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAAC;CACpD;AAED;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,CAAC,EAAE,gBAAgB,CAAC;CACvC;AAED;;;GAGG;AACH,MAAM,WAAW,wBAAyB,SAAQ,mBAAmB;IACnE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,SAAS,CAAC,EAAE,gBAAgB,CAAC;CACvC;AAED;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC;IACzD,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAC5E,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC,CAAC;IAC7D,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC,CAAC;IACtG,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;IACnD,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC;IACjE,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC,CAAC;CAC/C"}
1
+ {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../../src/packlets/storage/model.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAE5C;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC;AAEtC;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,WAAW,CAAC;AAEnD;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEvD;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;CAChC;AAED;;;GAGG;AACH,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAAC;CACpD;AAED;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAE9B;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,sBAAsB,CAAC;CAC5C;AAED;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,CAAC,EAAE,gBAAgB,CAAC;CACvC;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,uBAAwB,SAAQ,mBAAmB;IAClE;;;;;;;OAOG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,sBAAsB,CAAC;CAC5C;AAED;;;GAGG;AACH,MAAM,WAAW,wBAAyB,SAAQ,mBAAmB;IACnE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,SAAS,CAAC,EAAE,gBAAgB,CAAC;CACvC;AAED;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC;IACzD,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAC5E;;;;;;;OAOG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC,CAAC;IAChG,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC,CAAC;IACtG,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;IACnD,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC;IACjE,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC,CAAC;CAC/C"}
@@ -1 +1 @@
1
- {"version":3,"file":"model.js","sourceRoot":"","sources":["../../../src/packlets/storage/model.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;GAoBG","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport type { Result } from '@fgv/ts-utils';\n\n/**\n * Namespace identifier for scoped storage.\n * @public\n */\nexport type StorageNamespace = string;\n\n/**\n * Storage item type.\n * @public\n */\nexport type StorageItemType = 'file' | 'directory';\n\n/**\n * Storage tree item metadata.\n * @public\n */\nexport interface IStorageTreeItem {\n readonly path: string;\n readonly name: string;\n readonly type: StorageItemType;\n}\n\n/**\n * Response for listing children.\n * @public\n */\nexport interface IStorageTreeChildrenResponse {\n readonly path: string;\n readonly children: ReadonlyArray<IStorageTreeItem>;\n}\n\n/**\n * Response for reading a file.\n * @public\n */\nexport interface IStorageFileResponse {\n readonly path: string;\n readonly contents: string;\n readonly contentType?: string;\n}\n\n/**\n * Request for path-based operations.\n * @public\n */\nexport interface IStoragePathRequest {\n readonly path: string;\n readonly namespace?: StorageNamespace;\n}\n\n/**\n * Request for writing file contents.\n * @public\n */\nexport interface IStorageWriteFileRequest extends IStoragePathRequest {\n readonly contents: string;\n readonly contentType?: string;\n}\n\n/**\n * Request for sync operation.\n * @public\n */\nexport interface IStorageSyncRequest {\n readonly namespace?: StorageNamespace;\n}\n\n/**\n * Sync response metadata.\n * @public\n */\nexport interface IStorageSyncResponse {\n readonly synced: number;\n}\n\n/**\n * Provider contract for storage backends.\n * All methods are async to support both filesystem and database backends.\n * @public\n */\nexport interface IHttpStorageProvider {\n getItem(path: string): Promise<Result<IStorageTreeItem>>;\n getChildren(path: string): Promise<Result<ReadonlyArray<IStorageTreeItem>>>;\n getFile(path: string): Promise<Result<IStorageFileResponse>>;\n saveFile(path: string, contents: string, contentType?: string): Promise<Result<IStorageFileResponse>>;\n deleteFile(path: string): Promise<Result<boolean>>;\n createDirectory(path: string): Promise<Result<IStorageTreeItem>>;\n sync(): Promise<Result<IStorageSyncResponse>>;\n}\n"]}
1
+ {"version":3,"file":"model.js","sourceRoot":"","sources":["../../../src/packlets/storage/model.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;GAoBG","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport type { Result } from '@fgv/ts-utils';\n\n/**\n * Namespace identifier for scoped storage.\n * @public\n */\nexport type StorageNamespace = string;\n\n/**\n * Storage item type.\n * @public\n */\nexport type StorageItemType = 'file' | 'directory';\n\n/**\n * How a file's bytes are represented in the `contents` field on the wire.\n *\n * @remarks\n * - `'utf8'` — `contents` is the file decoded as UTF-8 text. This is the\n * historical (and default) representation, and it is **lossy**: the decode is\n * the lenient WHATWG one, so any byte sequence that is not valid UTF-8 has\n * already been replaced by U+FFFD by the time it reaches the wire, and the\n * substitution is not recoverable downstream.\n * - `'base64'` — `contents` is the file's bytes, base64-encoded. Byte-faithful\n * and therefore the only representation over which a consumer can decide\n * whether the stored bytes were valid UTF-8, or read genuinely binary content.\n * Costs roughly 33% more payload.\n * @public\n */\nexport type StorageContentEncoding = 'utf8' | 'base64';\n\n/**\n * Storage tree item metadata.\n * @public\n */\nexport interface IStorageTreeItem {\n readonly path: string;\n readonly name: string;\n readonly type: StorageItemType;\n}\n\n/**\n * Response for listing children.\n * @public\n */\nexport interface IStorageTreeChildrenResponse {\n readonly path: string;\n readonly children: ReadonlyArray<IStorageTreeItem>;\n}\n\n/**\n * Response for reading a file.\n * @public\n */\nexport interface IStorageFileResponse {\n readonly path: string;\n readonly contents: string;\n readonly contentType?: string;\n\n /**\n * How `contents` is encoded. Absent means `'utf8'`.\n *\n * @remarks\n * **This field describes what the server actually produced, not what the\n * client asked for**, and a client must branch on it rather than on its own\n * request. A provider that does not implement `'base64'` ignores the request\n * and answers with UTF-8 text; a client that base64-decoded on the strength of\n * having *asked* would corrupt every such response. Trusting the field instead\n * makes an older or simpler provider degrade to today's behavior rather than\n * to garbage.\n */\n readonly encoding?: StorageContentEncoding;\n}\n\n/**\n * Request for path-based operations.\n * @public\n */\nexport interface IStoragePathRequest {\n readonly path: string;\n readonly namespace?: StorageNamespace;\n}\n\n/**\n * Request for reading a file, with an optional preferred content encoding.\n *\n * @remarks\n * `encoding` is a *preference*, not a demand: a provider that cannot honour it\n * answers in `'utf8'` and says so on {@link IStorageFileResponse.encoding}. The\n * response is the authority.\n * @public\n */\nexport interface IStorageReadFileRequest extends IStoragePathRequest {\n /**\n * Preferred wire encoding for the response's `contents`. Absent means `'utf8'`.\n *\n * @remarks\n * A preference, not a demand — see {@link IStorageFileResponse.encoding}, which\n * reports what the server actually produced and is the field a client must\n * branch on.\n */\n readonly encoding?: StorageContentEncoding;\n}\n\n/**\n * Request for writing file contents.\n * @public\n */\nexport interface IStorageWriteFileRequest extends IStoragePathRequest {\n readonly contents: string;\n readonly contentType?: string;\n}\n\n/**\n * Request for sync operation.\n * @public\n */\nexport interface IStorageSyncRequest {\n readonly namespace?: StorageNamespace;\n}\n\n/**\n * Sync response metadata.\n * @public\n */\nexport interface IStorageSyncResponse {\n readonly synced: number;\n}\n\n/**\n * Provider contract for storage backends.\n * All methods are async to support both filesystem and database backends.\n * @public\n */\nexport interface IHttpStorageProvider {\n getItem(path: string): Promise<Result<IStorageTreeItem>>;\n getChildren(path: string): Promise<Result<ReadonlyArray<IStorageTreeItem>>>;\n /**\n * Reads a file.\n * @param path - Path of the file to read.\n * @param encoding - Preferred wire encoding for the returned `contents`. A\n * provider that does not implement the requested encoding MUST answer in\n * `'utf8'` and report that on the response, rather than failing — the field is\n * a preference and the response is the authority.\n */\n getFile(path: string, encoding?: StorageContentEncoding): Promise<Result<IStorageFileResponse>>;\n saveFile(path: string, contents: string, contentType?: string): Promise<Result<IStorageFileResponse>>;\n deleteFile(path: string): Promise<Result<boolean>>;\n createDirectory(path: string): Promise<Result<IStorageTreeItem>>;\n sync(): Promise<Result<IStorageSyncResponse>>;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../src/packlets/storage/routes.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAY7C,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAC;AAE9D;;;GAGG;AACH,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,SAAS,EAAE,2BAA2B,CAAC;IAChD,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC;CACnC;AAaD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,2BAA2B,GAAG,IAAI,CAwJ9E"}
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../src/packlets/storage/routes.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAa7C,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAC;AAE9D;;;GAGG;AACH,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,SAAS,EAAE,2BAA2B,CAAC;IAChD,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC;CACnC;AAaD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,2BAA2B,GAAG,IAAI,CA8J9E"}
@@ -80,7 +80,8 @@ function createStorageRoutes(options) {
80
80
  return c.json(converted.value);
81
81
  });
82
82
  routes.get('/file', async (c) => {
83
- const parsed = converters_1.storagePathRequest.convert(parsePathQuery(c.req.query('path'), c.req.query('namespace')));
83
+ const requestedEncoding = c.req.query('encoding');
84
+ const parsed = converters_1.storageReadFileRequest.convert(Object.assign(Object.assign({}, parsePathQuery(c.req.query('path'), c.req.query('namespace'))), (requestedEncoding !== undefined ? { encoding: requestedEncoding } : {})));
84
85
  if (parsed.isFailure()) {
85
86
  return c.json({ error: `invalid request: ${parsed.message}` }, 400);
86
87
  }