@unchainedshop/core-files 2.5.12 → 2.6.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/lib/db/MediaObjectsCollection.d.ts +2 -2
- package/lib/db/MediaObjectsCollection.js +1 -1
- package/lib/db/MediaObjectsCollection.js.map +1 -1
- package/lib/module/configureFilesModule.js +1 -1
- package/lib/module/configureFilesModule.js.map +1 -1
- package/lib/service/createDownloadStreamService.d.ts +2 -0
- package/lib/service/createDownloadStreamService.js +9 -0
- package/lib/service/createDownloadStreamService.js.map +1 -0
- package/lib/service/fileServices.js +2 -0
- package/lib/service/fileServices.js.map +1 -1
- package/lib/service/removeFilesService.js.map +1 -1
- package/lib/utils/getFileAdapter.d.ts +1 -1
- package/package.json +9 -9
- package/src/db/MediaObjectsCollection.ts +2 -3
- package/src/module/configureFilesModule.ts +1 -1
- package/src/service/createDownloadStreamService.ts +16 -0
- package/src/service/fileServices.ts +2 -0
- package/src/service/removeFilesService.ts +3 -3
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mongodb } from '@unchainedshop/mongodb';
|
|
2
2
|
import { File } from '@unchainedshop/types/files.js';
|
|
3
|
-
export declare const MediaObjectsCollection: (db: Db) => Promise<
|
|
3
|
+
export declare const MediaObjectsCollection: (db: mongodb.Db) => Promise<mongodb.Collection<File>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MediaObjectsCollection.js","sourceRoot":"","sources":["../../src/db/MediaObjectsCollection.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"MediaObjectsCollection.js","sourceRoot":"","sources":["../../src/db/MediaObjectsCollection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAGjE,MAAM,CAAC,MAAM,sBAAsB,GAAG,KAAK,EAAE,EAAc,EAAE,EAAE;IAC7D,MAAM,YAAY,GAAG,EAAE,CAAC,UAAU,CAAO,eAAe,CAAC,CAAC;IAE1D,MAAM,cAAc,CAAO,YAAY,EAAE;QACvC,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,EAAE,kBAAkB,EAAE,CAAC,EAAE,EAAE;QAC7D,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;KAC3B,CAAC,CAAC;IAEH,OAAO,YAAY,CAAC;AACtB,CAAC,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { emit, registerEvents } from '@unchainedshop/events';
|
|
2
|
-
import { generateDbFilterById, generateDbMutations } from '@unchainedshop/
|
|
2
|
+
import { generateDbFilterById, generateDbMutations } from '@unchainedshop/mongodb';
|
|
3
3
|
import { MediaObjectsCollection } from '../db/MediaObjectsCollection.js';
|
|
4
4
|
import { MediaObjectsSchema } from '../db/MediaObjectsSchema.js';
|
|
5
5
|
import { filesSettings } from '../files-settings.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"configureFilesModule.js","sourceRoot":"","sources":["../../src/module/configureFilesModule.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"configureFilesModule.js","sourceRoot":"","sources":["../../src/module/configureFilesModule.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { getFileAdapter } from '../utils/getFileAdapter.js';
|
|
2
|
+
export const createDownloadStreamService = async ({ fileId }, unchainedContext) => {
|
|
3
|
+
const { modules: { files }, } = unchainedContext;
|
|
4
|
+
const fileAdapter = getFileAdapter();
|
|
5
|
+
const file = await files.findFile({ fileId });
|
|
6
|
+
const stream = fileAdapter.createDownloadStream(file, unchainedContext);
|
|
7
|
+
return stream;
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=createDownloadStreamService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createDownloadStreamService.js","sourceRoot":"","sources":["../../src/service/createDownloadStreamService.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAE5D,MAAM,CAAC,MAAM,2BAA2B,GAAgC,KAAK,EAC3E,EAAE,MAAM,EAAE,EACV,gBAAgB,EAChB,EAAE;IACF,MAAM,EACJ,OAAO,EAAE,EAAE,KAAK,EAAE,GACnB,GAAG,gBAAgB,CAAC;IACrB,MAAM,WAAW,GAAG,cAAc,EAAE,CAAC;IAErC,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IAC9C,MAAM,MAAM,GAAG,WAAW,CAAC,oBAAoB,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;IACxE,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC"}
|
|
@@ -3,11 +3,13 @@ import { createSignedURLService } from './createSignedURLService.js';
|
|
|
3
3
|
import { uploadFileFromURLService } from './uploadFileFromURLService.js';
|
|
4
4
|
import { uploadFileFromStreamService } from './uploadFileFromStreamService.js';
|
|
5
5
|
import { removeFilesService } from './removeFilesService.js';
|
|
6
|
+
import { createDownloadStreamService } from './createDownloadStreamService.js';
|
|
6
7
|
export const fileServices = {
|
|
7
8
|
linkFile: linkFileService,
|
|
8
9
|
createSignedURL: createSignedURLService,
|
|
9
10
|
uploadFileFromURL: uploadFileFromURLService,
|
|
10
11
|
uploadFileFromStream: uploadFileFromStreamService,
|
|
11
12
|
removeFiles: removeFilesService,
|
|
13
|
+
createDownloadStream: createDownloadStreamService,
|
|
12
14
|
};
|
|
13
15
|
//# sourceMappingURL=fileServices.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fileServices.js","sourceRoot":"","sources":["../../src/service/fileServices.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,2BAA2B,EAAE,MAAM,kCAAkC,CAAC;AAC/E,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"fileServices.js","sourceRoot":"","sources":["../../src/service/fileServices.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,2BAA2B,EAAE,MAAM,kCAAkC,CAAC;AAC/E,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,2BAA2B,EAAE,MAAM,kCAAkC,CAAC;AAE/E,MAAM,CAAC,MAAM,YAAY,GAAiB;IACxC,QAAQ,EAAE,eAAe;IACzB,eAAe,EAAE,sBAAsB;IACvC,iBAAiB,EAAE,wBAAwB;IAC3C,oBAAoB,EAAE,2BAA2B;IACjD,WAAW,EAAE,kBAAkB;IAC/B,oBAAoB,EAAE,2BAA2B;CAClD,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"removeFilesService.js","sourceRoot":"","sources":["../../src/service/removeFilesService.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAE5D,MAAM,CAAC,MAAM,kBAAkB,GAAuB,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,YAAY,EAAE,EAAE;IACxF,MAAM,EACJ,OAAO,EAAE,EAAE,KAAK,EAAE,GACnB,GAAG,YAAY,CAAC;IAEjB,IAAI,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QACnE,MAAM,KAAK,CAAC,4DAA4D,CAAC,CAAC;IAE5E,MAAM,QAAQ,
|
|
1
|
+
{"version":3,"file":"removeFilesService.js","sourceRoot":"","sources":["../../src/service/removeFilesService.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAE5D,MAAM,CAAC,MAAM,kBAAkB,GAAuB,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,YAAY,EAAE,EAAE;IACxF,MAAM,EACJ,OAAO,EAAE,EAAE,KAAK,EAAE,GACnB,GAAG,YAAY,CAAC;IAEjB,IAAI,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QACnE,MAAM,KAAK,CAAC,4DAA4D,CAAC,CAAC;IAE5E,MAAM,QAAQ,GAAyB;QACrC,GAAG,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE;KACtB,CAAC;IAEF,MAAM,iBAAiB,GAAG,cAAc,EAAE,CAAC;IAE3C,MAAM,WAAW,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IAEpD,IAAI,CAAC;QACH,MAAM,iBAAiB,CAAC,WAAW,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;IACjE,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,sBAAsB;IACzC,CAAC;IAED,MAAM,eAAe,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAEtE,MAAM,KAAK,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;IAExC,OAAO,eAAe,CAAC,MAAM,CAAC;AAChC,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const getFileAdapter: () => import("@unchainedshop/
|
|
1
|
+
export declare const getFileAdapter: () => import("@unchainedshop/file-upload").IFileAdapter;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unchainedshop/core-files",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.1",
|
|
4
4
|
"main": "lib/files-index.js",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./lib/files-index.js",
|
|
@@ -31,18 +31,18 @@
|
|
|
31
31
|
},
|
|
32
32
|
"homepage": "https://github.com/unchainedshop/unchained#readme",
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@unchainedshop/events": "^2.
|
|
35
|
-
"@unchainedshop/file-upload": "^2.
|
|
36
|
-
"@unchainedshop/logger": "^2.
|
|
37
|
-
"@unchainedshop/utils": "^2.
|
|
34
|
+
"@unchainedshop/events": "^2.6.1",
|
|
35
|
+
"@unchainedshop/file-upload": "^2.6.1",
|
|
36
|
+
"@unchainedshop/logger": "^2.6.1",
|
|
37
|
+
"@unchainedshop/utils": "^2.6.1",
|
|
38
38
|
"mime-types": "^2.1.35",
|
|
39
39
|
"simpl-schema": "=3.2.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@types/node": "^20.10
|
|
43
|
-
"@unchainedshop/types": "^2.
|
|
42
|
+
"@types/node": "^20.11.10",
|
|
43
|
+
"@unchainedshop/types": "^2.6.1",
|
|
44
44
|
"jest": "^29.7.0",
|
|
45
|
-
"ts-jest": "^29.1.
|
|
46
|
-
"typescript": "^5.3.
|
|
45
|
+
"ts-jest": "^29.1.2",
|
|
46
|
+
"typescript": "^5.3.3"
|
|
47
47
|
}
|
|
48
48
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mongodb, buildDbIndexes } from '@unchainedshop/mongodb';
|
|
2
2
|
import { File } from '@unchainedshop/types/files.js';
|
|
3
|
-
import { buildDbIndexes } from '@unchainedshop/utils';
|
|
4
3
|
|
|
5
|
-
export const MediaObjectsCollection = async (db: Db) => {
|
|
4
|
+
export const MediaObjectsCollection = async (db: mongodb.Db) => {
|
|
6
5
|
const MediaObjects = db.collection<File>('media_objects');
|
|
7
6
|
|
|
8
7
|
await buildDbIndexes<File>(MediaObjects, [
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ModuleInput, ModuleMutations } from '@unchainedshop/types/core.js';
|
|
2
2
|
import { File, FilesModule, FilesSettingsOptions } from '@unchainedshop/types/files.js';
|
|
3
3
|
import { emit, registerEvents } from '@unchainedshop/events';
|
|
4
|
-
import { generateDbFilterById, generateDbMutations } from '@unchainedshop/
|
|
4
|
+
import { generateDbFilterById, generateDbMutations } from '@unchainedshop/mongodb';
|
|
5
5
|
import { MediaObjectsCollection } from '../db/MediaObjectsCollection.js';
|
|
6
6
|
import { MediaObjectsSchema } from '../db/MediaObjectsSchema.js';
|
|
7
7
|
import { filesSettings } from '../files-settings.js';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { CreateDownloadStreamService } from '@unchainedshop/types/files.js';
|
|
2
|
+
import { getFileAdapter } from '../utils/getFileAdapter.js';
|
|
3
|
+
|
|
4
|
+
export const createDownloadStreamService: CreateDownloadStreamService = async (
|
|
5
|
+
{ fileId },
|
|
6
|
+
unchainedContext,
|
|
7
|
+
) => {
|
|
8
|
+
const {
|
|
9
|
+
modules: { files },
|
|
10
|
+
} = unchainedContext;
|
|
11
|
+
const fileAdapter = getFileAdapter();
|
|
12
|
+
|
|
13
|
+
const file = await files.findFile({ fileId });
|
|
14
|
+
const stream = fileAdapter.createDownloadStream(file, unchainedContext);
|
|
15
|
+
return stream;
|
|
16
|
+
};
|
|
@@ -4,6 +4,7 @@ import { createSignedURLService } from './createSignedURLService.js';
|
|
|
4
4
|
import { uploadFileFromURLService } from './uploadFileFromURLService.js';
|
|
5
5
|
import { uploadFileFromStreamService } from './uploadFileFromStreamService.js';
|
|
6
6
|
import { removeFilesService } from './removeFilesService.js';
|
|
7
|
+
import { createDownloadStreamService } from './createDownloadStreamService.js';
|
|
7
8
|
|
|
8
9
|
export const fileServices: FileServices = {
|
|
9
10
|
linkFile: linkFileService,
|
|
@@ -11,4 +12,5 @@ export const fileServices: FileServices = {
|
|
|
11
12
|
uploadFileFromURL: uploadFileFromURLService,
|
|
12
13
|
uploadFileFromStream: uploadFileFromStreamService,
|
|
13
14
|
removeFiles: removeFilesService,
|
|
15
|
+
createDownloadStream: createDownloadStreamService,
|
|
14
16
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { File, RemoveFilesService } from '@unchainedshop/types/files.js';
|
|
2
|
+
import { mongodb } from '@unchainedshop/mongodb';
|
|
3
3
|
import { getFileAdapter } from '../utils/getFileAdapter.js';
|
|
4
4
|
|
|
5
5
|
export const removeFilesService: RemoveFilesService = async ({ fileIds }, unchainedAPI) => {
|
|
@@ -10,7 +10,7 @@ export const removeFilesService: RemoveFilesService = async ({ fileIds }, unchai
|
|
|
10
10
|
if (fileIds && typeof fileIds !== 'string' && !Array.isArray(fileIds))
|
|
11
11
|
throw Error('Media id/s to be removed not provided as a string or array');
|
|
12
12
|
|
|
13
|
-
const selector:
|
|
13
|
+
const selector: mongodb.Filter<File> = {
|
|
14
14
|
_id: { $in: fileIds },
|
|
15
15
|
};
|
|
16
16
|
|