@objectstack/service-storage 4.0.5 → 4.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -4
- package/dist/index.cjs +264 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +634 -19
- package/dist/index.d.ts +634 -19
- package/dist/index.js +263 -35
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -480,6 +480,9 @@ var LocalStorageAdapter = class {
|
|
|
480
480
|
}
|
|
481
481
|
};
|
|
482
482
|
|
|
483
|
+
// src/storage-service-plugin.ts
|
|
484
|
+
init_s3_storage_adapter();
|
|
485
|
+
|
|
483
486
|
// src/metadata-store.ts
|
|
484
487
|
var StorageMetadataStore = class {
|
|
485
488
|
constructor(engine) {
|
|
@@ -496,7 +499,7 @@ var StorageMetadataStore = class {
|
|
|
496
499
|
this.files.set(full.id, full);
|
|
497
500
|
if (this.engine) {
|
|
498
501
|
try {
|
|
499
|
-
await this.engine.insert("
|
|
502
|
+
await this.engine.insert("sys_file", full);
|
|
500
503
|
} catch {
|
|
501
504
|
}
|
|
502
505
|
}
|
|
@@ -505,7 +508,7 @@ var StorageMetadataStore = class {
|
|
|
505
508
|
async getFile(id) {
|
|
506
509
|
if (this.engine) {
|
|
507
510
|
try {
|
|
508
|
-
const found = await this.engine.findOne("
|
|
511
|
+
const found = await this.engine.findOne("sys_file", { where: { id } });
|
|
509
512
|
if (found) return found;
|
|
510
513
|
} catch {
|
|
511
514
|
}
|
|
@@ -519,7 +522,7 @@ var StorageMetadataStore = class {
|
|
|
519
522
|
this.files.set(id, merged);
|
|
520
523
|
if (this.engine) {
|
|
521
524
|
try {
|
|
522
|
-
await this.engine.update("
|
|
525
|
+
await this.engine.update("sys_file", merged, { where: { id } });
|
|
523
526
|
} catch {
|
|
524
527
|
}
|
|
525
528
|
}
|
|
@@ -529,7 +532,7 @@ var StorageMetadataStore = class {
|
|
|
529
532
|
this.files.delete(id);
|
|
530
533
|
if (this.engine) {
|
|
531
534
|
try {
|
|
532
|
-
await this.engine.delete("
|
|
535
|
+
await this.engine.delete("sys_file", { where: { id } });
|
|
533
536
|
} catch {
|
|
534
537
|
}
|
|
535
538
|
}
|
|
@@ -550,7 +553,7 @@ var StorageMetadataStore = class {
|
|
|
550
553
|
this.sessions.set(full.id, full);
|
|
551
554
|
if (this.engine) {
|
|
552
555
|
try {
|
|
553
|
-
await this.engine.insert("
|
|
556
|
+
await this.engine.insert("sys_upload_session", full);
|
|
554
557
|
} catch {
|
|
555
558
|
}
|
|
556
559
|
}
|
|
@@ -559,7 +562,7 @@ var StorageMetadataStore = class {
|
|
|
559
562
|
async getSession(id) {
|
|
560
563
|
if (this.engine) {
|
|
561
564
|
try {
|
|
562
|
-
const found = await this.engine.findOne("
|
|
565
|
+
const found = await this.engine.findOne("sys_upload_session", { where: { id } });
|
|
563
566
|
if (found) return found;
|
|
564
567
|
} catch {
|
|
565
568
|
}
|
|
@@ -578,7 +581,7 @@ var StorageMetadataStore = class {
|
|
|
578
581
|
this.sessions.set(id, merged);
|
|
579
582
|
if (this.engine) {
|
|
580
583
|
try {
|
|
581
|
-
await this.engine.update("
|
|
584
|
+
await this.engine.update("sys_upload_session", merged, { where: { id } });
|
|
582
585
|
} catch {
|
|
583
586
|
}
|
|
584
587
|
}
|
|
@@ -588,7 +591,7 @@ var StorageMetadataStore = class {
|
|
|
588
591
|
this.sessions.delete(id);
|
|
589
592
|
if (this.engine) {
|
|
590
593
|
try {
|
|
591
|
-
await this.engine.delete("
|
|
594
|
+
await this.engine.delete("sys_upload_session", { where: { id } });
|
|
592
595
|
} catch {
|
|
593
596
|
}
|
|
594
597
|
}
|
|
@@ -881,6 +884,28 @@ function registerStorageRoutes(httpServer, storage, store, opts = {}) {
|
|
|
881
884
|
res.status(500).json({ error: err.message ?? "Internal error" });
|
|
882
885
|
}
|
|
883
886
|
});
|
|
887
|
+
httpServer.get(`${basePath}/files/:fileId`, async (req, res) => {
|
|
888
|
+
try {
|
|
889
|
+
const { fileId } = req.params;
|
|
890
|
+
const file = await store.getFile(fileId);
|
|
891
|
+
if (!file || file.status !== "committed") {
|
|
892
|
+
res.status(404).json({ error: "File not found or not committed" });
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
let url;
|
|
896
|
+
if (storage.getPresignedDownload) {
|
|
897
|
+
const desc = await storage.getPresignedDownload(file.key, presignedTtl);
|
|
898
|
+
url = desc.downloadUrl;
|
|
899
|
+
} else if (storage.getSignedUrl) {
|
|
900
|
+
url = await storage.getSignedUrl(file.key, presignedTtl);
|
|
901
|
+
} else {
|
|
902
|
+
url = `${basePath}/_local/file/${encodeURIComponent(file.key)}`;
|
|
903
|
+
}
|
|
904
|
+
res.status(302).header("Location", url).send("");
|
|
905
|
+
} catch (err) {
|
|
906
|
+
res.status(500).json({ error: err.message ?? "Internal error" });
|
|
907
|
+
}
|
|
908
|
+
});
|
|
884
909
|
httpServer.put(`${basePath}/_local/raw/:token`, async (req, res) => {
|
|
885
910
|
try {
|
|
886
911
|
const { token } = req.params;
|
|
@@ -933,7 +958,7 @@ function buildKey(scope, fileId, filename) {
|
|
|
933
958
|
// src/objects/system-file.object.ts
|
|
934
959
|
import { ObjectSchema, Field } from "@objectstack/spec/data";
|
|
935
960
|
var SystemFile = ObjectSchema.create({
|
|
936
|
-
name: "
|
|
961
|
+
name: "sys_file",
|
|
937
962
|
label: "System File",
|
|
938
963
|
pluralLabel: "System Files",
|
|
939
964
|
icon: "file",
|
|
@@ -979,7 +1004,7 @@ var SystemFile = ObjectSchema.create({
|
|
|
979
1004
|
label: "ACL",
|
|
980
1005
|
options: [
|
|
981
1006
|
{ label: "Private", value: "private" },
|
|
982
|
-
{ label: "Public Read", value: "
|
|
1007
|
+
{ label: "Public Read", value: "public_read" }
|
|
983
1008
|
]
|
|
984
1009
|
}),
|
|
985
1010
|
status: Field.select({
|
|
@@ -1012,7 +1037,7 @@ var SystemFile = ObjectSchema.create({
|
|
|
1012
1037
|
// src/objects/system-upload-session.object.ts
|
|
1013
1038
|
import { ObjectSchema as ObjectSchema2, Field as Field2 } from "@objectstack/spec/data";
|
|
1014
1039
|
var SystemUploadSession = ObjectSchema2.create({
|
|
1015
|
-
name: "
|
|
1040
|
+
name: "sys_upload_session",
|
|
1016
1041
|
label: "System Upload Session",
|
|
1017
1042
|
pluralLabel: "System Upload Sessions",
|
|
1018
1043
|
icon: "upload-cloud",
|
|
@@ -1099,6 +1124,103 @@ var SystemUploadSession = ObjectSchema2.create({
|
|
|
1099
1124
|
}
|
|
1100
1125
|
});
|
|
1101
1126
|
|
|
1127
|
+
// src/swappable-storage-service.ts
|
|
1128
|
+
var SwappableStorageService = class {
|
|
1129
|
+
constructor(initial, onSwap) {
|
|
1130
|
+
this.inner = initial;
|
|
1131
|
+
this.onSwap = onSwap;
|
|
1132
|
+
}
|
|
1133
|
+
/** Replace the inner adapter. */
|
|
1134
|
+
swap(next) {
|
|
1135
|
+
const previous = this.inner;
|
|
1136
|
+
this.inner = next;
|
|
1137
|
+
this.onSwap?.(previous, next);
|
|
1138
|
+
}
|
|
1139
|
+
/** Expose the active inner adapter — primarily for tests. */
|
|
1140
|
+
getInner() {
|
|
1141
|
+
return this.inner;
|
|
1142
|
+
}
|
|
1143
|
+
upload(key, data, options) {
|
|
1144
|
+
return this.inner.upload(key, data, options);
|
|
1145
|
+
}
|
|
1146
|
+
download(key) {
|
|
1147
|
+
return this.inner.download(key);
|
|
1148
|
+
}
|
|
1149
|
+
delete(key) {
|
|
1150
|
+
return this.inner.delete(key);
|
|
1151
|
+
}
|
|
1152
|
+
exists(key) {
|
|
1153
|
+
return this.inner.exists(key);
|
|
1154
|
+
}
|
|
1155
|
+
getInfo(key) {
|
|
1156
|
+
return this.inner.getInfo(key);
|
|
1157
|
+
}
|
|
1158
|
+
list(prefix) {
|
|
1159
|
+
if (typeof this.inner.list !== "function") {
|
|
1160
|
+
return Promise.reject(new Error("Active storage adapter does not support list()"));
|
|
1161
|
+
}
|
|
1162
|
+
return this.inner.list(prefix);
|
|
1163
|
+
}
|
|
1164
|
+
getSignedUrl(key, expiresIn) {
|
|
1165
|
+
if (typeof this.inner.getSignedUrl !== "function") {
|
|
1166
|
+
return Promise.reject(new Error("Active storage adapter does not support getSignedUrl()"));
|
|
1167
|
+
}
|
|
1168
|
+
return this.inner.getSignedUrl(key, expiresIn);
|
|
1169
|
+
}
|
|
1170
|
+
getPresignedUpload(key, expiresIn, options) {
|
|
1171
|
+
if (typeof this.inner.getPresignedUpload !== "function") {
|
|
1172
|
+
return Promise.reject(new Error("Active storage adapter does not support getPresignedUpload()"));
|
|
1173
|
+
}
|
|
1174
|
+
return this.inner.getPresignedUpload(key, expiresIn, options);
|
|
1175
|
+
}
|
|
1176
|
+
getPresignedDownload(key, expiresIn) {
|
|
1177
|
+
if (typeof this.inner.getPresignedDownload !== "function") {
|
|
1178
|
+
return Promise.reject(new Error("Active storage adapter does not support getPresignedDownload()"));
|
|
1179
|
+
}
|
|
1180
|
+
return this.inner.getPresignedDownload(key, expiresIn);
|
|
1181
|
+
}
|
|
1182
|
+
initiateChunkedUpload(key, options) {
|
|
1183
|
+
if (typeof this.inner.initiateChunkedUpload !== "function") {
|
|
1184
|
+
return Promise.reject(new Error("Active storage adapter does not support initiateChunkedUpload()"));
|
|
1185
|
+
}
|
|
1186
|
+
return this.inner.initiateChunkedUpload(key, options);
|
|
1187
|
+
}
|
|
1188
|
+
uploadChunk(uploadId, partNumber, data) {
|
|
1189
|
+
if (typeof this.inner.uploadChunk !== "function") {
|
|
1190
|
+
return Promise.reject(new Error("Active storage adapter does not support uploadChunk()"));
|
|
1191
|
+
}
|
|
1192
|
+
return this.inner.uploadChunk(uploadId, partNumber, data);
|
|
1193
|
+
}
|
|
1194
|
+
completeChunkedUpload(uploadId, parts) {
|
|
1195
|
+
if (typeof this.inner.completeChunkedUpload !== "function") {
|
|
1196
|
+
return Promise.reject(new Error("Active storage adapter does not support completeChunkedUpload()"));
|
|
1197
|
+
}
|
|
1198
|
+
return this.inner.completeChunkedUpload(uploadId, parts);
|
|
1199
|
+
}
|
|
1200
|
+
abortChunkedUpload(uploadId) {
|
|
1201
|
+
if (typeof this.inner.abortChunkedUpload !== "function") {
|
|
1202
|
+
return Promise.reject(new Error("Active storage adapter does not support abortChunkedUpload()"));
|
|
1203
|
+
}
|
|
1204
|
+
return this.inner.abortChunkedUpload(uploadId);
|
|
1205
|
+
}
|
|
1206
|
+
/**
|
|
1207
|
+
* Verify a presigned HMAC token (LocalStorageAdapter-specific).
|
|
1208
|
+
*
|
|
1209
|
+
* `IStorageService` does not declare this method, but `storage-routes`
|
|
1210
|
+
* type-narrows the active storage to `LocalStorageAdapter` to handle the
|
|
1211
|
+
* `/_local/raw/:token` PUT and GET endpoints. Without a passthrough on
|
|
1212
|
+
* the swappable wrapper, the route sees `verifyToken === undefined` and
|
|
1213
|
+
* returns 501 even though the underlying local adapter supports it.
|
|
1214
|
+
*/
|
|
1215
|
+
verifyToken(token, expectedOp) {
|
|
1216
|
+
const inner = this.inner;
|
|
1217
|
+
if (typeof inner.verifyToken !== "function") {
|
|
1218
|
+
throw new Error("Active storage adapter does not support verifyToken()");
|
|
1219
|
+
}
|
|
1220
|
+
return inner.verifyToken(token, expectedOp);
|
|
1221
|
+
}
|
|
1222
|
+
};
|
|
1223
|
+
|
|
1102
1224
|
// src/storage-service-plugin.ts
|
|
1103
1225
|
var StorageServicePlugin = class {
|
|
1104
1226
|
constructor(options = {}) {
|
|
@@ -1109,22 +1231,57 @@ var StorageServicePlugin = class {
|
|
|
1109
1231
|
this.store = null;
|
|
1110
1232
|
this.options = { adapter: "local", ...options };
|
|
1111
1233
|
}
|
|
1234
|
+
/** Build a concrete adapter from a values map (settings-derived). */
|
|
1235
|
+
async buildAdapterFromValues(values) {
|
|
1236
|
+
const adapter = String(values.adapter ?? "local");
|
|
1237
|
+
if (adapter === "s3") {
|
|
1238
|
+
const bucket = values.s3_bucket;
|
|
1239
|
+
const region = values.s3_region;
|
|
1240
|
+
if (!bucket || !region) {
|
|
1241
|
+
throw new Error("StorageServicePlugin: S3 adapter requires s3_bucket and s3_region");
|
|
1242
|
+
}
|
|
1243
|
+
const opts = {
|
|
1244
|
+
bucket,
|
|
1245
|
+
region,
|
|
1246
|
+
endpoint: values.s3_endpoint || void 0,
|
|
1247
|
+
accessKeyId: values.s3_access_key_id || void 0,
|
|
1248
|
+
secretAccessKey: values.s3_secret_access_key || void 0,
|
|
1249
|
+
forcePathStyle: !!values.s3_force_path_style
|
|
1250
|
+
};
|
|
1251
|
+
return new S3StorageAdapter(opts);
|
|
1252
|
+
}
|
|
1253
|
+
const rootDir = values.local_root || "./storage";
|
|
1254
|
+
return new LocalStorageAdapter({
|
|
1255
|
+
basePath: this.options.basePath ?? "/api/v1/storage",
|
|
1256
|
+
...this.options.local ?? {},
|
|
1257
|
+
// settings value wins over any constructor-provided local.rootDir
|
|
1258
|
+
rootDir
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1112
1261
|
async init(ctx) {
|
|
1113
1262
|
const adapter = this.options.adapter;
|
|
1263
|
+
let initial;
|
|
1114
1264
|
if (adapter === "s3") {
|
|
1115
|
-
const { S3StorageAdapter:
|
|
1265
|
+
const { S3StorageAdapter: S3Ctor } = await Promise.resolve().then(() => (init_s3_storage_adapter(), s3_storage_adapter_exports));
|
|
1116
1266
|
const s3Opts = this.options.s3;
|
|
1117
1267
|
if (!s3Opts) {
|
|
1118
1268
|
throw new Error('StorageServicePlugin: s3 options are required when adapter is "s3"');
|
|
1119
1269
|
}
|
|
1120
|
-
|
|
1270
|
+
initial = new S3Ctor(s3Opts);
|
|
1121
1271
|
} else {
|
|
1122
1272
|
const rootDir = this.options.local?.rootDir ?? "./storage";
|
|
1123
1273
|
const basePath = this.options.basePath ?? "/api/v1/storage";
|
|
1124
|
-
|
|
1274
|
+
initial = new LocalStorageAdapter({ rootDir, basePath, ...this.options.local });
|
|
1125
1275
|
}
|
|
1276
|
+
this.storage = new SwappableStorageService(initial, (prev, next) => {
|
|
1277
|
+
const prevName = prev?.constructor?.name ?? "unknown";
|
|
1278
|
+
const nextName = next?.constructor?.name ?? "unknown";
|
|
1279
|
+
ctx.logger.warn(
|
|
1280
|
+
`StorageServicePlugin: storage adapter swapped (${prevName} \u2192 ${nextName}). Existing files were NOT migrated and may be unreachable through the new adapter.`
|
|
1281
|
+
);
|
|
1282
|
+
});
|
|
1126
1283
|
ctx.registerService("file-storage", this.storage);
|
|
1127
|
-
ctx.logger.info(`StorageServicePlugin: registered ${adapter} storage adapter`);
|
|
1284
|
+
ctx.logger.info(`StorageServicePlugin: registered ${adapter} storage adapter (swappable)`);
|
|
1128
1285
|
try {
|
|
1129
1286
|
ctx.getService("manifest").register({
|
|
1130
1287
|
id: "com.objectstack.service.storage",
|
|
@@ -1138,31 +1295,101 @@ var StorageServicePlugin = class {
|
|
|
1138
1295
|
}
|
|
1139
1296
|
}
|
|
1140
1297
|
async start(ctx) {
|
|
1141
|
-
if (this.options.registerRoutes === false) return;
|
|
1142
1298
|
ctx.hook("kernel:ready", async () => {
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1299
|
+
if (this.options.registerRoutes !== false) {
|
|
1300
|
+
let httpServer = null;
|
|
1301
|
+
try {
|
|
1302
|
+
httpServer = ctx.getService("http-server");
|
|
1303
|
+
} catch {
|
|
1304
|
+
}
|
|
1305
|
+
if (httpServer && this.storage) {
|
|
1306
|
+
let engine = null;
|
|
1307
|
+
try {
|
|
1308
|
+
engine = ctx.getService("objectql");
|
|
1309
|
+
} catch {
|
|
1310
|
+
}
|
|
1311
|
+
this.store = new StorageMetadataStore(engine);
|
|
1312
|
+
registerStorageRoutes(httpServer, this.storage, this.store, {
|
|
1313
|
+
basePath: this.options.basePath ?? "/api/v1/storage",
|
|
1314
|
+
presignedTtl: this.options.presignedTtl,
|
|
1315
|
+
sessionTtl: this.options.sessionTtl
|
|
1316
|
+
});
|
|
1317
|
+
ctx.logger.info(
|
|
1318
|
+
"StorageServicePlugin: REST routes registered at " + (this.options.basePath ?? "/api/v1/storage")
|
|
1319
|
+
);
|
|
1320
|
+
} else if (!httpServer) {
|
|
1321
|
+
ctx.logger.warn(
|
|
1322
|
+
'StorageServicePlugin: no HTTP server available \u2014 REST routes not registered. File storage is still accessible programmatically via kernel.getService("file-storage").'
|
|
1323
|
+
);
|
|
1324
|
+
}
|
|
1153
1325
|
}
|
|
1154
|
-
|
|
1326
|
+
if (this.options.bindToSettings === false) return;
|
|
1155
1327
|
try {
|
|
1156
|
-
|
|
1328
|
+
const settings = ctx.getService("settings");
|
|
1329
|
+
if (!settings || typeof settings.createClient !== "function") return;
|
|
1330
|
+
const applySettings = async () => {
|
|
1331
|
+
if (!this.storage) return;
|
|
1332
|
+
try {
|
|
1333
|
+
const payload = await settings.getNamespace("storage");
|
|
1334
|
+
const values = {};
|
|
1335
|
+
for (const [k, v] of Object.entries(payload.values)) {
|
|
1336
|
+
values[k] = v?.value;
|
|
1337
|
+
}
|
|
1338
|
+
const hasAny = Object.values(values).some((v) => v !== void 0 && v !== null && v !== "");
|
|
1339
|
+
if (!hasAny) return;
|
|
1340
|
+
const next = await this.buildAdapterFromValues(values);
|
|
1341
|
+
this.storage.swap(next);
|
|
1342
|
+
} catch (err) {
|
|
1343
|
+
ctx.logger.warn(
|
|
1344
|
+
"StorageServicePlugin: failed to apply storage settings: " + (err?.message ?? err)
|
|
1345
|
+
);
|
|
1346
|
+
}
|
|
1347
|
+
};
|
|
1348
|
+
await applySettings();
|
|
1349
|
+
if (typeof settings.subscribe === "function") {
|
|
1350
|
+
settings.subscribe("storage", () => {
|
|
1351
|
+
void applySettings();
|
|
1352
|
+
});
|
|
1353
|
+
ctx.logger.info("StorageServicePlugin: bound to settings:changed for namespace=storage");
|
|
1354
|
+
}
|
|
1355
|
+
if (typeof settings.registerAction === "function" && this.storage) {
|
|
1356
|
+
const proxy = this.storage;
|
|
1357
|
+
settings.registerAction("storage", "test", async ({ values }) => {
|
|
1358
|
+
const probeKey = `__objectstack_probe__/${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
1359
|
+
const probeBytes = Buffer.from(`probe@${(/* @__PURE__ */ new Date()).toISOString()}`, "utf-8");
|
|
1360
|
+
try {
|
|
1361
|
+
let target = proxy;
|
|
1362
|
+
if (values && Object.keys(values).length > 0) {
|
|
1363
|
+
try {
|
|
1364
|
+
target = await this.buildAdapterFromValues(values);
|
|
1365
|
+
} catch (err) {
|
|
1366
|
+
return { ok: false, severity: "error", message: err?.message ?? String(err) };
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
await target.upload(probeKey, probeBytes, { contentType: "text/plain" });
|
|
1370
|
+
const got = await target.download(probeKey);
|
|
1371
|
+
if (!got || !Buffer.isBuffer(got) || got.toString("utf-8") !== probeBytes.toString("utf-8")) {
|
|
1372
|
+
return { ok: false, severity: "error", message: "Probe download did not match upload." };
|
|
1373
|
+
}
|
|
1374
|
+
await target.delete(probeKey);
|
|
1375
|
+
const adapter = String(values?.adapter ?? this.options.adapter ?? "local");
|
|
1376
|
+
return {
|
|
1377
|
+
ok: true,
|
|
1378
|
+
severity: "info",
|
|
1379
|
+
message: `Storage round-trip succeeded (adapter=${adapter}).`
|
|
1380
|
+
};
|
|
1381
|
+
} catch (err) {
|
|
1382
|
+
try {
|
|
1383
|
+
await proxy.delete(probeKey);
|
|
1384
|
+
} catch {
|
|
1385
|
+
}
|
|
1386
|
+
return { ok: false, severity: "error", message: err?.message ?? String(err) };
|
|
1387
|
+
}
|
|
1388
|
+
});
|
|
1389
|
+
ctx.logger.info("StorageServicePlugin: registered settings action storage/test");
|
|
1390
|
+
}
|
|
1157
1391
|
} catch {
|
|
1158
1392
|
}
|
|
1159
|
-
this.store = new StorageMetadataStore(engine);
|
|
1160
|
-
registerStorageRoutes(httpServer, this.storage, this.store, {
|
|
1161
|
-
basePath: this.options.basePath ?? "/api/v1/storage",
|
|
1162
|
-
presignedTtl: this.options.presignedTtl,
|
|
1163
|
-
sessionTtl: this.options.sessionTtl
|
|
1164
|
-
});
|
|
1165
|
-
ctx.logger.info("StorageServicePlugin: REST routes registered at " + (this.options.basePath ?? "/api/v1/storage"));
|
|
1166
1393
|
});
|
|
1167
1394
|
}
|
|
1168
1395
|
};
|
|
@@ -1174,6 +1401,7 @@ export {
|
|
|
1174
1401
|
S3StorageAdapter,
|
|
1175
1402
|
StorageMetadataStore,
|
|
1176
1403
|
StorageServicePlugin,
|
|
1404
|
+
SwappableStorageService,
|
|
1177
1405
|
SystemFile,
|
|
1178
1406
|
SystemUploadSession,
|
|
1179
1407
|
registerStorageRoutes
|