@toa.io/extensions.storages 1.0.0-alpha.31 → 1.0.0-alpha.310
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/package.json +38 -18
- package/readme.md +115 -47
- package/transpiled/Aspect.d.ts +8 -4
- package/transpiled/Aspect.js +12 -14
- package/transpiled/Aspect.js.map +1 -1
- package/transpiled/Entry.d.ts +14 -11
- package/transpiled/Entry.js +1 -2
- package/transpiled/Factory.d.ts +1 -1
- package/transpiled/Factory.js +29 -30
- package/transpiled/Factory.js.map +1 -1
- package/transpiled/Provider.d.ts +17 -19
- package/transpiled/Provider.js +10 -33
- package/transpiled/Provider.js.map +1 -1
- package/transpiled/Scanner.d.ts +5 -4
- package/transpiled/Scanner.js +33 -26
- package/transpiled/Scanner.js.map +1 -1
- package/transpiled/Secrets.d.ts +5 -0
- package/transpiled/Secrets.js +2 -0
- package/transpiled/Secrets.js.map +1 -0
- package/transpiled/Storage.d.ts +23 -22
- package/transpiled/Storage.js +79 -142
- package/transpiled/Storage.js.map +1 -1
- package/transpiled/errors.d.ts +7 -0
- package/transpiled/errors.js +4 -0
- package/transpiled/errors.js.map +1 -0
- package/transpiled/index.d.ts +4 -4
- package/transpiled/index.js +1 -9
- package/transpiled/index.js.map +1 -1
- package/transpiled/measurements.d.ts +3 -0
- package/transpiled/measurements.js +23 -0
- package/transpiled/measurements.js.map +1 -0
- package/transpiled/providers/Cloudinary.d.ts +50 -0
- package/transpiled/providers/Cloudinary.js +223 -0
- package/transpiled/providers/Cloudinary.js.map +1 -0
- package/transpiled/providers/Declaration.d.ts +9 -5
- package/transpiled/providers/Declaration.js +1 -2
- package/transpiled/providers/FileSystem.d.ts +16 -11
- package/transpiled/providers/FileSystem.js +64 -43
- package/transpiled/providers/FileSystem.js.map +1 -1
- package/transpiled/providers/S3.d.ts +14 -19
- package/transpiled/providers/S3.js +72 -123
- package/transpiled/providers/S3.js.map +1 -1
- package/transpiled/providers/Spaces.d.ts +12 -0
- package/transpiled/providers/Spaces.js +13 -0
- package/transpiled/providers/Spaces.js.map +1 -0
- package/transpiled/providers/Temporary.d.ts +2 -3
- package/transpiled/providers/Temporary.js +7 -11
- package/transpiled/providers/Temporary.js.map +1 -1
- package/transpiled/providers/Test.d.ts +4 -4
- package/transpiled/providers/Test.js +6 -12
- package/transpiled/providers/Test.js.map +1 -1
- package/transpiled/providers/index.d.ts +23 -11
- package/transpiled/providers/index.js +30 -14
- package/transpiled/providers/index.js.map +1 -1
- package/transpiled/providers/index.types.d.ts +5 -0
- package/transpiled/providers/index.types.js +2 -0
- package/transpiled/providers/index.types.js.map +1 -0
- package/transpiled/test/util.d.ts +113 -6
- package/transpiled/test/util.js +68 -15
- package/transpiled/test/util.js.map +1 -1
- package/schemas/annotation.cos.yaml +0 -10
- package/schemas/fs.cos.yaml +0 -9
- package/schemas/mem.cos.yaml +0 -6
- package/schemas/s3.cos.yaml +0 -16
- package/schemas/test.cos.yaml +0 -8
- package/schemas/tmp.cos.yaml +0 -9
- package/source/Annotation.ts +0 -39
- package/source/Aspect.ts +0 -25
- package/source/Entry.ts +0 -14
- package/source/Factory.ts +0 -68
- package/source/Provider.ts +0 -37
- package/source/Scanner.ts +0 -153
- package/source/Storage.test.ts +0 -472
- package/source/Storage.ts +0 -235
- package/source/deployment.ts +0 -57
- package/source/index.ts +0 -5
- package/source/manifest.ts +0 -8
- package/source/providers/Declaration.ts +0 -10
- package/source/providers/FileSystem.test.ts +0 -5
- package/source/providers/FileSystem.ts +0 -63
- package/source/providers/Memory.ts +0 -55
- package/source/providers/S3.test.ts +0 -134
- package/source/providers/S3.ts +0 -196
- package/source/providers/Temporary.ts +0 -16
- package/source/providers/Test.ts +0 -13
- package/source/providers/index.test.ts +0 -136
- package/source/providers/index.ts +0 -16
- package/source/providers/readme.md +0 -25
- package/source/schemas.test.ts +0 -58
- package/source/schemas.ts +0 -15
- package/source/test/.env.example +0 -1
- package/source/test/albert.jpg +0 -0
- package/source/test/empty.txt +0 -0
- package/source/test/lenna.ascii +0 -64
- package/source/test/lenna.png +0 -0
- package/source/test/sample.avi +0 -0
- package/source/test/sample.avif +0 -0
- package/source/test/sample.gif +0 -0
- package/source/test/sample.heic +0 -0
- package/source/test/sample.jpeg +0 -0
- package/source/test/sample.jxl +0 -0
- package/source/test/sample.svg +0 -4
- package/source/test/sample.wav +0 -0
- package/source/test/sample.webp +0 -0
- package/source/test/util.ts +0 -45
- package/transpiled/Annotation.d.ts +0 -3
- package/transpiled/Annotation.js +0 -57
- package/transpiled/Annotation.js.map +0 -1
- package/transpiled/deployment.d.ts +0 -5
- package/transpiled/deployment.js +0 -68
- package/transpiled/deployment.js.map +0 -1
- package/transpiled/manifest.d.ts +0 -1
- package/transpiled/manifest.js +0 -9
- package/transpiled/manifest.js.map +0 -1
- package/transpiled/providers/Memory.d.ts +0 -15
- package/transpiled/providers/Memory.js +0 -72
- package/transpiled/providers/Memory.js.map +0 -1
- package/transpiled/schemas.d.ts +0 -9
- package/transpiled/schemas.js +0 -14
- package/transpiled/schemas.js.map +0 -1
- package/transpiled/tsconfig.tsbuildinfo +0 -1
- package/tsconfig.json +0 -12
|
@@ -1,29 +1,136 @@
|
|
|
1
|
+
import type { Secrets } from '../Secrets.ts';
|
|
2
|
+
import type { providers, S3Options, SpacesOptions, CloudinaryOptions, FileSystemOptions, TemporaryOptions } from '../providers/index.ts';
|
|
1
3
|
export declare const suites: ({
|
|
2
4
|
run: true;
|
|
3
5
|
provider: "tmp";
|
|
4
6
|
options: {
|
|
5
7
|
directory: string;
|
|
6
8
|
endpoint?: undefined;
|
|
7
|
-
region?: undefined;
|
|
8
9
|
bucket?: undefined;
|
|
10
|
+
space?: undefined;
|
|
11
|
+
region?: undefined;
|
|
12
|
+
environment?: undefined;
|
|
13
|
+
type?: undefined;
|
|
14
|
+
prefix?: undefined;
|
|
15
|
+
transformations?: undefined;
|
|
9
16
|
};
|
|
10
17
|
secrets?: undefined;
|
|
11
|
-
} | {
|
|
12
|
-
run: true;
|
|
13
|
-
provider: "mem";
|
|
14
|
-
options?: undefined;
|
|
15
|
-
secrets?: undefined;
|
|
16
18
|
} | {
|
|
17
19
|
run: boolean;
|
|
18
20
|
provider: "s3";
|
|
19
21
|
options: {
|
|
22
|
+
directory?: undefined;
|
|
20
23
|
endpoint: string;
|
|
21
24
|
region: string;
|
|
22
25
|
bucket: string;
|
|
26
|
+
space?: undefined;
|
|
27
|
+
environment?: undefined;
|
|
28
|
+
type?: undefined;
|
|
29
|
+
prefix?: undefined;
|
|
30
|
+
transformations?: undefined;
|
|
31
|
+
};
|
|
32
|
+
secrets: {
|
|
33
|
+
ACCESS_KEY_ID: string;
|
|
34
|
+
SECRET_ACCESS_KEY: string;
|
|
35
|
+
API_KEY?: undefined;
|
|
36
|
+
API_SECRET?: undefined;
|
|
37
|
+
};
|
|
38
|
+
} | {
|
|
39
|
+
run: boolean;
|
|
40
|
+
provider: "spaces";
|
|
41
|
+
options: {
|
|
23
42
|
directory?: undefined;
|
|
43
|
+
endpoint?: undefined;
|
|
44
|
+
bucket?: undefined;
|
|
45
|
+
space: string;
|
|
46
|
+
region: string;
|
|
47
|
+
environment?: undefined;
|
|
48
|
+
type?: undefined;
|
|
49
|
+
prefix?: undefined;
|
|
50
|
+
transformations?: undefined;
|
|
24
51
|
};
|
|
25
52
|
secrets: {
|
|
26
53
|
ACCESS_KEY_ID: string;
|
|
27
54
|
SECRET_ACCESS_KEY: string;
|
|
55
|
+
API_KEY?: undefined;
|
|
56
|
+
API_SECRET?: undefined;
|
|
57
|
+
};
|
|
58
|
+
} | {
|
|
59
|
+
run: boolean;
|
|
60
|
+
provider: "cloudinary";
|
|
61
|
+
options: {
|
|
62
|
+
directory?: undefined;
|
|
63
|
+
endpoint?: undefined;
|
|
64
|
+
bucket?: undefined;
|
|
65
|
+
space?: undefined;
|
|
66
|
+
region?: undefined;
|
|
67
|
+
environment: string;
|
|
68
|
+
type: "image";
|
|
69
|
+
prefix: string;
|
|
70
|
+
transformations: ({
|
|
71
|
+
extension: string;
|
|
72
|
+
transformation: {
|
|
73
|
+
width: string;
|
|
74
|
+
height: string;
|
|
75
|
+
zoom: string;
|
|
76
|
+
crop: string;
|
|
77
|
+
gravity: string;
|
|
78
|
+
angle?: undefined;
|
|
79
|
+
fetch_format?: undefined;
|
|
80
|
+
};
|
|
81
|
+
optional: true;
|
|
82
|
+
condition?: undefined;
|
|
83
|
+
} | {
|
|
84
|
+
extension: string;
|
|
85
|
+
transformation: {
|
|
86
|
+
gravity?: undefined;
|
|
87
|
+
width: string;
|
|
88
|
+
height: string;
|
|
89
|
+
zoom: string;
|
|
90
|
+
crop: string;
|
|
91
|
+
angle?: undefined;
|
|
92
|
+
fetch_format?: undefined;
|
|
93
|
+
};
|
|
94
|
+
optional: true;
|
|
95
|
+
condition?: undefined;
|
|
96
|
+
} | {
|
|
97
|
+
extension: string;
|
|
98
|
+
condition: string;
|
|
99
|
+
transformation: {
|
|
100
|
+
gravity?: undefined;
|
|
101
|
+
width?: undefined;
|
|
102
|
+
height?: undefined;
|
|
103
|
+
zoom?: undefined;
|
|
104
|
+
crop?: undefined;
|
|
105
|
+
angle: number;
|
|
106
|
+
fetch_format?: undefined;
|
|
107
|
+
};
|
|
108
|
+
optional: true;
|
|
109
|
+
} | {
|
|
110
|
+
condition?: undefined;
|
|
111
|
+
extension: string;
|
|
112
|
+
transformation: {
|
|
113
|
+
gravity?: undefined;
|
|
114
|
+
width?: undefined;
|
|
115
|
+
height?: undefined;
|
|
116
|
+
zoom?: undefined;
|
|
117
|
+
crop?: undefined;
|
|
118
|
+
angle?: undefined;
|
|
119
|
+
fetch_format: string;
|
|
120
|
+
};
|
|
121
|
+
optional: true;
|
|
122
|
+
})[];
|
|
123
|
+
};
|
|
124
|
+
secrets: {
|
|
125
|
+
ACCESS_KEY_ID?: undefined;
|
|
126
|
+
SECRET_ACCESS_KEY?: undefined;
|
|
127
|
+
API_KEY: string;
|
|
128
|
+
API_SECRET: string;
|
|
28
129
|
};
|
|
29
130
|
})[];
|
|
131
|
+
export interface Suite {
|
|
132
|
+
run: boolean;
|
|
133
|
+
provider: keyof typeof providers;
|
|
134
|
+
options?: S3Options | SpacesOptions | CloudinaryOptions | FileSystemOptions | TemporaryOptions;
|
|
135
|
+
secrets?: Secrets;
|
|
136
|
+
}
|
package/transpiled/test/util.js
CHANGED
|
@@ -1,13 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.suites = void 0;
|
|
7
|
-
const node_path_1 = require("node:path");
|
|
8
|
-
const dotenv_1 = __importDefault(require("dotenv"));
|
|
9
|
-
dotenv_1.default.config({ path: (0, node_path_1.join)(__dirname, '.env') });
|
|
10
|
-
exports.suites = [
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import dotenv from 'dotenv';
|
|
3
|
+
dotenv.config({ path: join(import.meta.dirname, '.env') });
|
|
4
|
+
export const suites = [
|
|
11
5
|
{
|
|
12
6
|
run: true,
|
|
13
7
|
provider: 'tmp',
|
|
@@ -15,15 +9,11 @@ exports.suites = [
|
|
|
15
9
|
directory: 'toa-storages-temp'
|
|
16
10
|
}
|
|
17
11
|
},
|
|
18
|
-
{
|
|
19
|
-
run: true,
|
|
20
|
-
provider: 'mem'
|
|
21
|
-
},
|
|
22
12
|
{
|
|
23
13
|
run: process.env.RUN_S3 === '1',
|
|
24
14
|
provider: 's3',
|
|
25
15
|
options: {
|
|
26
|
-
endpoint: 'http://localhost:
|
|
16
|
+
endpoint: 'http://localhost:31050',
|
|
27
17
|
region: 'us-west-1',
|
|
28
18
|
bucket: 'test-bucket'
|
|
29
19
|
},
|
|
@@ -31,6 +21,69 @@ exports.suites = [
|
|
|
31
21
|
ACCESS_KEY_ID: 'developer',
|
|
32
22
|
SECRET_ACCESS_KEY: 'secret'
|
|
33
23
|
}
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
run: process.env.RUN_SPACES === '1',
|
|
27
|
+
provider: 'spaces',
|
|
28
|
+
options: {
|
|
29
|
+
space: 'ants',
|
|
30
|
+
region: 'fra1'
|
|
31
|
+
},
|
|
32
|
+
secrets: {
|
|
33
|
+
ACCESS_KEY_ID: process.env.SPACES_KEY_ID ?? '',
|
|
34
|
+
SECRET_ACCESS_KEY: process.env.SPACES_ACCESS_KEY ?? ''
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
run: process.env.RUN_CLOUDINARY === '1',
|
|
39
|
+
provider: 'cloudinary',
|
|
40
|
+
options: {
|
|
41
|
+
environment: 'dl5z4zgth',
|
|
42
|
+
type: 'image',
|
|
43
|
+
prefix: 'toa-dev',
|
|
44
|
+
transformations: [
|
|
45
|
+
{
|
|
46
|
+
extension: '(?<width>\\d*)x(?<height>\\d*)(z(?<zoom>\\d*))?',
|
|
47
|
+
transformation: {
|
|
48
|
+
width: '<width>',
|
|
49
|
+
height: '<height>',
|
|
50
|
+
zoom: '<zoom>',
|
|
51
|
+
crop: 'thumb',
|
|
52
|
+
gravity: 'face'
|
|
53
|
+
},
|
|
54
|
+
optional: true
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
extension: '\\[(?<width>\\d*)x(?<height>\\d*)\\](z(?<zoom>\\d+))?',
|
|
58
|
+
transformation: {
|
|
59
|
+
width: '<width>',
|
|
60
|
+
height: '<height>',
|
|
61
|
+
zoom: '<zoom>',
|
|
62
|
+
crop: 'fit'
|
|
63
|
+
},
|
|
64
|
+
optional: true
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
extension: 'vertical',
|
|
68
|
+
condition: 'w_gt_h',
|
|
69
|
+
transformation: {
|
|
70
|
+
angle: 90
|
|
71
|
+
},
|
|
72
|
+
optional: true
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
extension: '(?<format>jpeg|webp)',
|
|
76
|
+
transformation: {
|
|
77
|
+
fetch_format: '<format>'
|
|
78
|
+
},
|
|
79
|
+
optional: true
|
|
80
|
+
}
|
|
81
|
+
]
|
|
82
|
+
},
|
|
83
|
+
secrets: {
|
|
84
|
+
API_KEY: process.env.CLOUDINARY_API_KEY ?? '',
|
|
85
|
+
API_SECRET: process.env.CLOUDINARY_API_SECRET ?? ''
|
|
86
|
+
}
|
|
34
87
|
}
|
|
35
88
|
// add more providers here, use `run` as a condition to run the test
|
|
36
89
|
// e.g.: `run: process.env.ACCESS_KEY_ID !== undefined`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"util.js","sourceRoot":"","sources":["../../source/test/util.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"util.js","sourceRoot":"","sources":["../../source/test/util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,MAAM,MAAM,QAAQ,CAAA;AAW3B,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,CAAC,CAAA;AAE1D,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB;QACE,GAAG,EAAE,IAAI;QACT,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE;YACP,SAAS,EAAE,mBAAmB;SAC/B;KACF;IACD;QACE,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,MAAM,KAAK,GAAG;QAC/B,QAAQ,EAAE,IAAI;QACd,OAAO,EAAE;YACP,QAAQ,EAAE,wBAAwB;YAClC,MAAM,EAAE,WAAW;YACnB,MAAM,EAAE,aAAa;SACtB;QACD,OAAO,EAAE;YACP,aAAa,EAAE,WAAW;YAC1B,iBAAiB,EAAE,QAAQ;SAC5B;KACF;IACD;QACE,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU,KAAK,GAAG;QACnC,QAAQ,EAAE,QAAQ;QAClB,OAAO,EAAE;YACP,KAAK,EAAE,MAAM;YACb,MAAM,EAAE,MAAM;SACf;QACD,OAAO,EAAE;YACP,aAAa,EAAE,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,EAAE;YAC9C,iBAAiB,EAAE,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,EAAE;SACvD;KACF;IACD;QACE,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc,KAAK,GAAG;QACvC,QAAQ,EAAE,YAAY;QACtB,OAAO,EAAE;YACP,WAAW,EAAE,WAAW;YACxB,IAAI,EAAE,OAAO;YACb,MAAM,EAAE,SAAS;YACjB,eAAe,EAAE;gBACf;oBACE,SAAS,EAAE,iDAAiD;oBAC5D,cAAc,EAAE;wBACd,KAAK,EAAE,SAAS;wBAChB,MAAM,EAAE,UAAU;wBAClB,IAAI,EAAE,QAAQ;wBACd,IAAI,EAAE,OAAO;wBACb,OAAO,EAAE,MAAM;qBAChB;oBACD,QAAQ,EAAE,IAAI;iBACf;gBACD;oBACE,SAAS,EAAE,uDAAuD;oBAClE,cAAc,EAAE;wBACd,KAAK,EAAE,SAAS;wBAChB,MAAM,EAAE,UAAU;wBAClB,IAAI,EAAE,QAAQ;wBACd,IAAI,EAAE,KAAK;qBACZ;oBACD,QAAQ,EAAE,IAAI;iBACf;gBACD;oBACE,SAAS,EAAE,UAAU;oBACrB,SAAS,EAAE,QAAQ;oBACnB,cAAc,EAAE;wBACd,KAAK,EAAE,EAAE;qBACV;oBACD,QAAQ,EAAE,IAAI;iBACf;gBACD;oBACE,SAAS,EAAE,sBAAsB;oBACjC,cAAc,EAAE;wBACd,YAAY,EAAE,UAAU;qBACzB;oBACD,QAAQ,EAAE,IAAI;iBACf;aACF;SACF;QACD,OAAO,EAAE;YACP,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,EAAE;YAC7C,UAAU,EAAE,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,EAAE;SACpD;KACF;IACD,oEAAoE;IACpE,uDAAuD;CACtC,CAAA"}
|
package/schemas/fs.cos.yaml
DELETED
package/schemas/mem.cos.yaml
DELETED
package/schemas/s3.cos.yaml
DELETED
package/schemas/test.cos.yaml
DELETED
package/schemas/tmp.cos.yaml
DELETED
package/source/Annotation.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert'
|
|
2
|
-
import { providers } from './providers'
|
|
3
|
-
import * as schemas from './schemas'
|
|
4
|
-
import type { Declaration } from './providers'
|
|
5
|
-
import type { Schema } from '@toa.io/schemas'
|
|
6
|
-
|
|
7
|
-
export type Annotation = Record<string, Declaration>
|
|
8
|
-
|
|
9
|
-
export function validateAnnotation (annotation: unknown): asserts annotation is Annotation {
|
|
10
|
-
try {
|
|
11
|
-
schemas.annotation.validate(annotation)
|
|
12
|
-
} catch (error) {
|
|
13
|
-
explain(annotation)
|
|
14
|
-
|
|
15
|
-
// if all declarations are valid, re-throw the error
|
|
16
|
-
throw error
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/*
|
|
21
|
-
It is required because `oneOf` schema is used for the annotation validation.
|
|
22
|
-
*/
|
|
23
|
-
function explain (annotation: unknown): void {
|
|
24
|
-
assert.ok(typeof annotation === 'object' && annotation !== null,
|
|
25
|
-
'TOA_STORAGES is not an object')
|
|
26
|
-
|
|
27
|
-
for (const declaration of Object.values(annotation)) {
|
|
28
|
-
assert.ok(typeof declaration === 'object' && declaration !== null &&
|
|
29
|
-
declaration.provider in providers,
|
|
30
|
-
`Unknown provider '${declaration.provider}'`)
|
|
31
|
-
|
|
32
|
-
assert.ok(declaration.provider in schemas,
|
|
33
|
-
`No schema for provider '${declaration.provider}'`)
|
|
34
|
-
|
|
35
|
-
const schema: Schema<Declaration> = schemas[declaration.provider as keyof typeof providers]
|
|
36
|
-
|
|
37
|
-
schema.validate(declaration, `Storage '${declaration.provider}' annotation`)
|
|
38
|
-
}
|
|
39
|
-
}
|
package/source/Aspect.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert'
|
|
2
|
-
import { Connector, type extensions } from '@toa.io/core'
|
|
3
|
-
import { type Storage, type Storages } from './Storage'
|
|
4
|
-
|
|
5
|
-
export class Aspect extends Connector implements extensions.Aspect {
|
|
6
|
-
public readonly name = 'storages'
|
|
7
|
-
|
|
8
|
-
private readonly storages: Storages
|
|
9
|
-
|
|
10
|
-
public constructor (storages: Storages) {
|
|
11
|
-
super()
|
|
12
|
-
|
|
13
|
-
this.storages = storages
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
public invoke (name: string, method: keyof Storage, ...args: unknown[]): unknown {
|
|
17
|
-
const storage = this.storages[name]
|
|
18
|
-
|
|
19
|
-
assert.ok(storage !== undefined, `Storage '${name}' is not defined`)
|
|
20
|
-
|
|
21
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
22
|
-
// @ts-expect-error
|
|
23
|
-
return storage[method](...args)
|
|
24
|
-
}
|
|
25
|
-
}
|
package/source/Entry.ts
DELETED
package/source/Factory.ts
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert'
|
|
2
|
-
import { decode } from '@toa.io/generic'
|
|
3
|
-
import { providers } from './providers'
|
|
4
|
-
import { Storage, type Storages } from './Storage'
|
|
5
|
-
import { Aspect } from './Aspect'
|
|
6
|
-
import { SERIALIZATION_PREFIX } from './deployment'
|
|
7
|
-
import { validateAnnotation } from './Annotation'
|
|
8
|
-
import type { Declaration } from './providers'
|
|
9
|
-
import type { Annotation } from './Annotation'
|
|
10
|
-
import type { ProviderConstructor, ProviderSecrets } from './Provider'
|
|
11
|
-
|
|
12
|
-
export class Factory {
|
|
13
|
-
private readonly annotation: Annotation
|
|
14
|
-
|
|
15
|
-
public constructor () {
|
|
16
|
-
const env = process.env.TOA_STORAGES
|
|
17
|
-
|
|
18
|
-
assert.ok(env !== undefined, 'TOA_STORAGES is not defined')
|
|
19
|
-
|
|
20
|
-
this.annotation = decode(env)
|
|
21
|
-
|
|
22
|
-
validateAnnotation(this.annotation)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
public aspect (): Aspect {
|
|
26
|
-
const storages = this.createStorages()
|
|
27
|
-
|
|
28
|
-
return new Aspect(storages)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
private createStorages (): Storages {
|
|
32
|
-
const storages: Storages = {}
|
|
33
|
-
|
|
34
|
-
for (const [name, declaration] of Object.entries(this.annotation))
|
|
35
|
-
storages[name] = this.createStorage(name, declaration)
|
|
36
|
-
|
|
37
|
-
return storages
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
private createStorage (name: string, declaration: Declaration): Storage {
|
|
41
|
-
const { provider: providerId, ...options } = declaration
|
|
42
|
-
const Provider: ProviderConstructor = providers[providerId]
|
|
43
|
-
const secrets = this.resolveSecrets(name, Provider)
|
|
44
|
-
const provider = new Provider(options, secrets)
|
|
45
|
-
|
|
46
|
-
return new Storage(provider)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
private resolveSecrets (storageName: string,
|
|
50
|
-
Class: ProviderConstructor): ProviderSecrets {
|
|
51
|
-
if (Class.SECRETS === undefined)
|
|
52
|
-
return {}
|
|
53
|
-
|
|
54
|
-
const secrets: Record<string, string | undefined> = {}
|
|
55
|
-
|
|
56
|
-
for (const secret of Class.SECRETS) {
|
|
57
|
-
const variable = `${SERIALIZATION_PREFIX}_${storageName}_${secret.name}`.toUpperCase()
|
|
58
|
-
const value = process.env[variable]
|
|
59
|
-
|
|
60
|
-
assert.ok(secret.optional === true || value !== undefined,
|
|
61
|
-
`'${variable}' is not defined`)
|
|
62
|
-
|
|
63
|
-
secrets[secret.name] = value
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return secrets
|
|
67
|
-
}
|
|
68
|
-
}
|
package/source/Provider.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { type Readable } from 'node:stream'
|
|
2
|
-
import * as assert from 'node:assert'
|
|
3
|
-
|
|
4
|
-
export type ProviderSecrets<K extends string = string> = Record<K | string, string | undefined>
|
|
5
|
-
|
|
6
|
-
export interface ProviderSecret {
|
|
7
|
-
readonly name: string
|
|
8
|
-
readonly optional?: boolean
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export abstract class Provider<Options = void> {
|
|
12
|
-
public static readonly SECRETS: readonly ProviderSecret[] = []
|
|
13
|
-
|
|
14
|
-
public constructor (_: Options, secrets?: ProviderSecrets) {
|
|
15
|
-
for (const { name, optional = false } of new.target.SECRETS)
|
|
16
|
-
assert.ok(optional || secrets?.[name] !== undefined, `Missing secret '${name}'`)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
public abstract get (path: string): Promise<Readable | null>
|
|
20
|
-
|
|
21
|
-
public abstract list (path: string): Promise<string[]>
|
|
22
|
-
|
|
23
|
-
public abstract put (path: string, filename: string, stream: Readable): Promise<void>
|
|
24
|
-
|
|
25
|
-
public abstract delete (path: string): Promise<void>
|
|
26
|
-
|
|
27
|
-
public abstract move (from: string, to: string): Promise<void>
|
|
28
|
-
|
|
29
|
-
public abstract moveDir (from: string, to: string): Promise<void>
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface ProviderConstructor {
|
|
33
|
-
readonly SECRETS: readonly ProviderSecret[]
|
|
34
|
-
prototype: Provider
|
|
35
|
-
|
|
36
|
-
new (options: any, secrets?: ProviderSecrets): Provider
|
|
37
|
-
}
|
package/source/Scanner.ts
DELETED
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import { PassThrough, type TransformCallback } from 'node:stream'
|
|
2
|
-
import { createHash } from 'node:crypto'
|
|
3
|
-
import { negotiate } from '@toa.io/agent'
|
|
4
|
-
import { Err } from 'error-value'
|
|
5
|
-
|
|
6
|
-
export class Scanner extends PassThrough {
|
|
7
|
-
public size = 0
|
|
8
|
-
public type = 'application/octet-stream'
|
|
9
|
-
public error: Error | null = null
|
|
10
|
-
|
|
11
|
-
private readonly hash = createHash('md5')
|
|
12
|
-
private readonly claim?: string
|
|
13
|
-
private readonly accept?: string
|
|
14
|
-
private position = 0
|
|
15
|
-
private detected = false
|
|
16
|
-
private readonly chunks: Buffer[] = []
|
|
17
|
-
|
|
18
|
-
public constructor (control?: ScanOptions) {
|
|
19
|
-
super()
|
|
20
|
-
|
|
21
|
-
this.claim = control?.claim
|
|
22
|
-
this.accept = control?.accept
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
public digest (): string {
|
|
26
|
-
return this.hash.digest('hex')
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
public override _transform
|
|
30
|
-
(buffer: Buffer, encoding: BufferEncoding, callback: TransformCallback): void {
|
|
31
|
-
super._transform(buffer, encoding, callback)
|
|
32
|
-
|
|
33
|
-
this.process(buffer)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
private readonly process = (buffer: Buffer): void => {
|
|
37
|
-
this.size += buffer.length
|
|
38
|
-
this.hash.update(buffer)
|
|
39
|
-
|
|
40
|
-
if (this.detected)
|
|
41
|
-
return
|
|
42
|
-
|
|
43
|
-
if (this.position + buffer.length > HEADER_SIZE)
|
|
44
|
-
buffer = buffer.subarray(0, HEADER_SIZE - this.position)
|
|
45
|
-
|
|
46
|
-
this.chunks.push(buffer)
|
|
47
|
-
this.position += buffer.length
|
|
48
|
-
|
|
49
|
-
if (this.position === HEADER_SIZE)
|
|
50
|
-
this.complete()
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
private complete (): void {
|
|
54
|
-
const header = Buffer.concat(this.chunks).toString('hex')
|
|
55
|
-
|
|
56
|
-
const signature = SIGNATURES
|
|
57
|
-
.find(({ off, hex, expression }) => {
|
|
58
|
-
const sig = header.slice(off, off + hex.length)
|
|
59
|
-
|
|
60
|
-
return expression === undefined
|
|
61
|
-
? sig === hex
|
|
62
|
-
: expression.test(sig)
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
const type = signature?.type ?? this.claim
|
|
66
|
-
|
|
67
|
-
if (type !== undefined) {
|
|
68
|
-
this.match(type)
|
|
69
|
-
this.type = type
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
this.verify(signature)
|
|
73
|
-
this.detected = true
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
private verify (signature: Signature | undefined): void {
|
|
77
|
-
if (this.claim === undefined || this.claim === 'application/octet-stream')
|
|
78
|
-
return
|
|
79
|
-
|
|
80
|
-
const mismatch = signature === undefined
|
|
81
|
-
? KNOWN_TYPES.has(this.claim)
|
|
82
|
-
: this.claim !== signature.type
|
|
83
|
-
|
|
84
|
-
if (mismatch)
|
|
85
|
-
this.interrupt(ERR_TYPE_MISMATCH)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
private match (type: string): void {
|
|
89
|
-
if (this.accept === undefined)
|
|
90
|
-
return
|
|
91
|
-
|
|
92
|
-
const unacceptable = negotiate(this.accept, [type]) === null
|
|
93
|
-
|
|
94
|
-
if (unacceptable)
|
|
95
|
-
this.interrupt(ERR_NOT_ACCEPTABLE)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
private interrupt (error: Error): void {
|
|
99
|
-
this.error = error
|
|
100
|
-
this.end()
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// https://en.wikipedia.org/wiki/List_of_file_signatures
|
|
105
|
-
const SIGNATURES: Signature[] = [
|
|
106
|
-
{ hex: 'ff d8 ff e0', off: 0, type: 'image/jpeg' },
|
|
107
|
-
{ hex: 'ff d8 ff e1', off: 0, type: 'image/jpeg' },
|
|
108
|
-
{ hex: 'ff d8 ff ee', off: 0, type: 'image/jpeg' },
|
|
109
|
-
{ hex: 'ff d8 ff db', off: 0, type: 'image/jpeg' },
|
|
110
|
-
{ hex: '89 50 4e 47', off: 0, type: 'image/png' },
|
|
111
|
-
{ hex: '47 49 46 38', off: 0, type: 'image/gif' },
|
|
112
|
-
{ hex: '52 49 46 46 ?? ?? ?? ?? 57 45 42 50', off: 0, type: 'image/webp' },
|
|
113
|
-
{ hex: '4a 58 4c 20 0d 0a 87 0a', off: 8, type: 'image/jxl' },
|
|
114
|
-
{ hex: '66 74 79 70 68 65 69 63', off: 8, type: 'image/heic' },
|
|
115
|
-
{ hex: '66 74 79 70 61 76 69 66', off: 8, type: 'image/avif' },
|
|
116
|
-
{ hex: '52 49 46 46 ?? ?? ?? ?? 41 56 49 20', off: 0, type: 'video/avi' },
|
|
117
|
-
{ hex: '52 49 46 46 ?? ?? ?? ?? 57 41 56 45', off: 0, type: 'audio/wav' }
|
|
118
|
-
/*
|
|
119
|
-
When adding a new signature, include a copyright-free sample file in the `.tests` directory
|
|
120
|
-
and update the 'signatures' test group in `Storage.test.ts`.
|
|
121
|
-
*/
|
|
122
|
-
].map((signature: Signature) => {
|
|
123
|
-
signature.hex = signature.hex.replaceAll(' ', '')
|
|
124
|
-
|
|
125
|
-
if (signature.hex.includes('??')) {
|
|
126
|
-
const expression = signature.hex.replaceAll(/(?<wildcards>\?{1,24})/g,
|
|
127
|
-
(_, wildcards) => `[0-9a-f]{${wildcards.length}}`)
|
|
128
|
-
|
|
129
|
-
signature.expression = new RegExp(expression, 'i')
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return signature
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
const HEADER_SIZE = SIGNATURES
|
|
136
|
-
.reduce((max, { off, hex }) => Math.max(max, off + hex.length), 0) / 2
|
|
137
|
-
|
|
138
|
-
const KNOWN_TYPES = new Set(SIGNATURES.map(({ type }) => type))
|
|
139
|
-
|
|
140
|
-
const ERR_TYPE_MISMATCH = Err('TYPE_MISMATCH')
|
|
141
|
-
const ERR_NOT_ACCEPTABLE = Err('NOT_ACCEPTABLE')
|
|
142
|
-
|
|
143
|
-
export interface ScanOptions {
|
|
144
|
-
claim?: string
|
|
145
|
-
accept?: string
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
interface Signature {
|
|
149
|
-
type: string
|
|
150
|
-
off: number
|
|
151
|
-
hex: string
|
|
152
|
-
expression?: RegExp
|
|
153
|
-
}
|