@toa.io/extensions.storages 1.0.0-alpha.0 → 1.0.0-alpha.2

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 (88) hide show
  1. package/package.json +12 -11
  2. package/readme.md +63 -19
  3. package/schemas/annotation.cos.yaml +10 -0
  4. package/schemas/fs.cos.yaml +9 -0
  5. package/schemas/mem.cos.yaml +6 -0
  6. package/schemas/s3.cos.yaml +16 -0
  7. package/schemas/test.cos.yaml +8 -0
  8. package/schemas/tmp.cos.yaml +9 -0
  9. package/source/Annotation.ts +39 -0
  10. package/source/Aspect.ts +6 -4
  11. package/source/Factory.ts +31 -28
  12. package/source/Provider.ts +30 -5
  13. package/source/Scanner.ts +3 -3
  14. package/source/Storage.test.ts +110 -105
  15. package/source/Storage.ts +13 -6
  16. package/source/deployment.ts +21 -29
  17. package/source/providers/Declaration.ts +10 -0
  18. package/source/providers/FileSystem.test.ts +1 -9
  19. package/source/providers/FileSystem.ts +20 -15
  20. package/source/providers/Memory.ts +41 -0
  21. package/source/providers/S3.test.ts +133 -0
  22. package/source/providers/S3.ts +114 -39
  23. package/source/providers/Temporary.ts +8 -6
  24. package/source/providers/Test.ts +8 -8
  25. package/source/providers/index.test.ts +24 -19
  26. package/source/providers/index.ts +10 -9
  27. package/source/providers/readme.md +1 -1
  28. package/source/schemas.test.ts +58 -0
  29. package/source/schemas.ts +15 -0
  30. package/source/test/util.ts +25 -54
  31. package/transpiled/Annotation.d.ts +3 -0
  32. package/transpiled/Annotation.js +57 -0
  33. package/transpiled/Annotation.js.map +1 -0
  34. package/transpiled/Aspect.d.ts +8 -0
  35. package/transpiled/Aspect.js +25 -0
  36. package/transpiled/Aspect.js.map +1 -0
  37. package/transpiled/Entry.d.ts +14 -0
  38. package/transpiled/Entry.js +3 -0
  39. package/transpiled/Entry.js.map +1 -0
  40. package/transpiled/Factory.d.ts +9 -0
  41. package/transpiled/Factory.js +53 -0
  42. package/transpiled/Factory.js.map +1 -0
  43. package/transpiled/Provider.d.ts +20 -0
  44. package/transpiled/Provider.js +36 -0
  45. package/transpiled/Provider.js.map +1 -0
  46. package/transpiled/Scanner.d.ts +26 -0
  47. package/transpiled/Scanner.js +98 -0
  48. package/transpiled/Scanner.js.map +1 -0
  49. package/transpiled/Storage.d.ts +32 -0
  50. package/transpiled/Storage.js +176 -0
  51. package/transpiled/Storage.js.map +1 -0
  52. package/transpiled/deployment.d.ts +5 -0
  53. package/transpiled/deployment.js +68 -0
  54. package/transpiled/deployment.js.map +1 -0
  55. package/transpiled/index.d.ts +4 -0
  56. package/transpiled/index.js +10 -0
  57. package/transpiled/index.js.map +1 -0
  58. package/transpiled/manifest.d.ts +1 -0
  59. package/transpiled/manifest.js +9 -0
  60. package/transpiled/manifest.js.map +1 -0
  61. package/transpiled/providers/Declaration.d.ts +14 -0
  62. package/transpiled/providers/Declaration.js +3 -0
  63. package/transpiled/providers/Declaration.js.map +1 -0
  64. package/transpiled/providers/FileSystem.d.ts +15 -0
  65. package/transpiled/providers/FileSystem.js +44 -0
  66. package/transpiled/providers/FileSystem.js.map +1 -0
  67. package/transpiled/providers/Memory.d.ts +13 -0
  68. package/transpiled/providers/Memory.js +60 -0
  69. package/transpiled/providers/Memory.js.map +1 -0
  70. package/transpiled/providers/S3.d.ts +27 -0
  71. package/transpiled/providers/S3.js +154 -0
  72. package/transpiled/providers/S3.js.map +1 -0
  73. package/transpiled/providers/Temporary.d.ts +8 -0
  74. package/transpiled/providers/Temporary.js +14 -0
  75. package/transpiled/providers/Temporary.js.map +1 -0
  76. package/transpiled/providers/Test.d.ts +6 -0
  77. package/transpiled/providers/Test.js +15 -0
  78. package/transpiled/providers/Test.js.map +1 -0
  79. package/transpiled/providers/index.d.ts +13 -0
  80. package/transpiled/providers/index.js +16 -0
  81. package/transpiled/providers/index.js.map +1 -0
  82. package/transpiled/schemas.d.ts +9 -0
  83. package/transpiled/schemas.js +14 -0
  84. package/transpiled/schemas.js.map +1 -0
  85. package/transpiled/test/util.d.ts +29 -0
  86. package/transpiled/test/util.js +38 -0
  87. package/transpiled/test/util.js.map +1 -0
  88. package/transpiled/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,58 @@
1
+ import * as schemas from './schemas'
2
+
3
+ const ok = {
4
+ a: {
5
+ provider: 'tmp',
6
+ directory: 'ok'
7
+ },
8
+ b: {
9
+ provider: 'fs',
10
+ path: 'ok'
11
+ },
12
+ c: {
13
+ provider: 's3',
14
+ bucket: 'my-bucket'
15
+ },
16
+ d: {
17
+ provider: 'mem'
18
+ }
19
+ }
20
+
21
+ const oh = [
22
+ {
23
+ whatever: {
24
+ provider: 'non-existent'
25
+ }
26
+ },
27
+ {
28
+ whatever: {
29
+ provider: 'fs'
30
+ }
31
+ },
32
+ {
33
+ whatever: {
34
+ provider: 'tmp',
35
+ extra: true
36
+ }
37
+ },
38
+ {
39
+ whatever: {
40
+
41
+ provider: 's3'
42
+ }
43
+ },
44
+ {
45
+ whatever: {
46
+ provider: 'mem',
47
+ extra: true
48
+ }
49
+ }
50
+ ]
51
+
52
+ it('should pass', () => {
53
+ expect(() => schemas.annotation.validate(ok)).not.toThrow()
54
+ })
55
+
56
+ it.each(oh)('should fail', (value) => {
57
+ expect(() => schemas.annotation.validate(value)).toThrow()
58
+ })
@@ -0,0 +1,15 @@
1
+ import { resolve } from 'node:path'
2
+ import { namespace } from '@toa.io/schemas'
3
+ import type { Declaration } from './providers'
4
+ import type { Schema } from '@toa.io/schemas'
5
+ import type { Annotation } from './Annotation'
6
+
7
+ const path = resolve(__dirname, '../schemas')
8
+ const ns = namespace(path)
9
+
10
+ export const annotation: Schema<Annotation> = ns.schema<Annotation>('annotation')
11
+ export const s3: Schema<Declaration> = ns.schema<Declaration>('s3')
12
+ export const fs: Schema<Declaration> = ns.schema<Declaration>('fs')
13
+ export const mem: Schema<Declaration> = ns.schema<Declaration>('mem')
14
+ export const tmp: Schema<Declaration> = ns.schema<Declaration>('tmp')
15
+ export const test: Schema<Declaration> = ns.schema<Declaration>('test')
@@ -1,74 +1,45 @@
1
1
  import { join } from 'node:path'
2
- import fs from 'node:fs/promises'
3
- import { createReadStream, type ReadStream } from 'node:fs'
4
- import { CreateBucketCommand, S3Client } from '@aws-sdk/client-s3'
5
2
  import dotenv from 'dotenv'
3
+ import type { ProviderSecrets } from '../Provider'
4
+ import type { providers } from '../providers'
5
+ import type { FileSystemOptions } from '../providers/FileSystem'
6
+ import type { S3Options } from '../providers/S3'
7
+ import type { TemporaryOptions } from '../providers/Temporary'
6
8
 
7
9
  dotenv.config({ path: join(__dirname, '.env') })
8
10
 
9
- const suites: Suite[] = [
11
+ export const suites = [
10
12
  {
11
13
  run: true,
12
- ref: 'tmp:///toa-storages-temp'
14
+ provider: 'tmp',
15
+ options: {
16
+ directory: 'toa-storages-temp'
17
+ }
18
+ },
19
+ {
20
+ run: true,
21
+ provider: 'mem'
13
22
  },
14
23
  {
15
24
  run: process.env.RUN_S3 === '1',
16
- ref: 's3://us-west-1/test-bucket?endpoint=http://s3.localhost.localstack.cloud:4566',
25
+ provider: 's3',
26
+ options: {
27
+ endpoint: 'http://localhost:4566',
28
+ region: 'us-west-1',
29
+ bucket: 'test-bucket'
30
+ },
17
31
  secrets: {
18
32
  ACCESS_KEY_ID: 'developer',
19
33
  SECRET_ACCESS_KEY: 'secret'
20
- },
21
- init: initS3
34
+ }
22
35
  }
23
36
  // add more providers here, use `run` as a condition to run the test
24
37
  // e.g.: `run: process.env.ACCESS_KEY_ID !== undefined`
25
- ]
26
-
27
- function map (suite: Suite): Case {
28
- const url = new URL(suite.ref)
29
-
30
- return [url.protocol, url, suite.secrets ?? {}, suite.init]
31
- }
32
-
33
- export const cases = suites.filter(({ run }) => run).map(map)
34
-
35
- export function rnd (): string {
36
- return Math.random().toString(36).slice(2)
37
- }
38
-
39
- export function open (rel: string): ReadStream {
40
- const path = join(__dirname, rel)
41
-
42
- return createReadStream(path)
43
- }
44
-
45
- export async function read (rel: string): Promise<Buffer> {
46
- const path = join(__dirname, rel)
47
-
48
- return await fs.readFile(path)
49
- }
50
-
51
- async function initS3 (url: URL, secrets: Record<string, string>): Promise<void> {
52
- const client = new S3Client({
53
- credentials: {
54
- accessKeyId: secrets.ACCESS_KEY_ID,
55
- secretAccessKey: secrets.SECRET_ACCESS_KEY
56
- },
57
- region: url.host,
58
- endpoint: url.searchParams.get('endpoint') ?? undefined
59
- })
60
-
61
- const command = new CreateBucketCommand({ Bucket: url.pathname.substring(1) })
62
-
63
- await client.send(command).catch(() => undefined)
64
- }
38
+ ] satisfies Suite[]
65
39
 
66
40
  interface Suite {
67
41
  run: boolean
68
- ref: string
69
- secrets?: Record<string, string>
70
- init?: SuiteInit
42
+ provider: keyof typeof providers
43
+ options?: FileSystemOptions | S3Options | TemporaryOptions
44
+ secrets?: ProviderSecrets
71
45
  }
72
-
73
- type SuiteInit = (url: URL, secrets: Record<string, string>) => Promise<void> | void
74
- type Case = [string, URL, Record<string, string>, SuiteInit?]
@@ -0,0 +1,3 @@
1
+ import type { Declaration } from './providers';
2
+ export type Annotation = Record<string, Declaration>;
3
+ export declare function validateAnnotation(annotation: unknown): asserts annotation is Annotation;
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.validateAnnotation = void 0;
30
+ const node_assert_1 = __importDefault(require("node:assert"));
31
+ const providers_1 = require("./providers");
32
+ const schemas = __importStar(require("./schemas"));
33
+ function validateAnnotation(annotation) {
34
+ try {
35
+ schemas.annotation.validate(annotation);
36
+ }
37
+ catch (error) {
38
+ explain(annotation);
39
+ // if all declarations are valid, re-throw the error
40
+ throw error;
41
+ }
42
+ }
43
+ exports.validateAnnotation = validateAnnotation;
44
+ /*
45
+ It is required because `oneOf` schema is used for the annotation validation.
46
+ */
47
+ function explain(annotation) {
48
+ node_assert_1.default.ok(typeof annotation === 'object' && annotation !== null, 'TOA_STORAGES is not an object');
49
+ for (const declaration of Object.values(annotation)) {
50
+ node_assert_1.default.ok(typeof declaration === 'object' && declaration !== null &&
51
+ declaration.provider in providers_1.providers, `Unknown provider '${declaration.provider}'`);
52
+ node_assert_1.default.ok(declaration.provider in schemas, `No schema for provider '${declaration.provider}'`);
53
+ const schema = schemas[declaration.provider];
54
+ schema.validate(declaration, `Storage '${declaration.provider}' annotation`);
55
+ }
56
+ }
57
+ //# sourceMappingURL=Annotation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Annotation.js","sourceRoot":"","sources":["../source/Annotation.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,8DAAgC;AAChC,2CAAuC;AACvC,mDAAoC;AAMpC,SAAgB,kBAAkB,CAAE,UAAmB;IACrD,IAAI,CAAC;QACH,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAA;IACzC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,UAAU,CAAC,CAAA;QAEnB,oDAAoD;QACpD,MAAM,KAAK,CAAA;IACb,CAAC;AACH,CAAC;AATD,gDASC;AAED;;GAEG;AACH,SAAS,OAAO,CAAE,UAAmB;IACnC,qBAAM,CAAC,EAAE,CAAC,OAAO,UAAU,KAAK,QAAQ,IAAI,UAAU,KAAK,IAAI,EAC7D,+BAA+B,CAAC,CAAA;IAElC,KAAK,MAAM,WAAW,IAAI,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;QACpD,qBAAM,CAAC,EAAE,CAAC,OAAO,WAAW,KAAK,QAAQ,IAAI,WAAW,KAAK,IAAI;YAC/D,WAAW,CAAC,QAAQ,IAAI,qBAAS,EACjC,qBAAqB,WAAW,CAAC,QAAQ,GAAG,CAAC,CAAA;QAE/C,qBAAM,CAAC,EAAE,CAAC,WAAW,CAAC,QAAQ,IAAI,OAAO,EACvC,2BAA2B,WAAW,CAAC,QAAQ,GAAG,CAAC,CAAA;QAErD,MAAM,MAAM,GAAwB,OAAO,CAAC,WAAW,CAAC,QAAkC,CAAC,CAAA;QAE3F,MAAM,CAAC,QAAQ,CAAC,WAAW,EAAE,YAAY,WAAW,CAAC,QAAQ,cAAc,CAAC,CAAA;IAC9E,CAAC;AACH,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { Connector, type extensions } from '@toa.io/core';
2
+ import { type Storage, type Storages } from './Storage';
3
+ export declare class Aspect extends Connector implements extensions.Aspect {
4
+ readonly name = "storages";
5
+ private readonly storages;
6
+ constructor(storages: Storages);
7
+ invoke(name: string, method: keyof Storage, ...args: unknown[]): unknown;
8
+ }
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Aspect = void 0;
7
+ const node_assert_1 = __importDefault(require("node:assert"));
8
+ const core_1 = require("@toa.io/core");
9
+ class Aspect extends core_1.Connector {
10
+ name = 'storages';
11
+ storages;
12
+ constructor(storages) {
13
+ super();
14
+ this.storages = storages;
15
+ }
16
+ invoke(name, method, ...args) {
17
+ const storage = this.storages[name];
18
+ node_assert_1.default.ok(storage !== undefined, `Storage '${name}' is not defined`);
19
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
20
+ // @ts-expect-error
21
+ return storage[method](...args);
22
+ }
23
+ }
24
+ exports.Aspect = Aspect;
25
+ //# sourceMappingURL=Aspect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Aspect.js","sourceRoot":"","sources":["../source/Aspect.ts"],"names":[],"mappings":";;;;;;AAAA,8DAAgC;AAChC,uCAAyD;AAGzD,MAAa,MAAO,SAAQ,gBAAS;IACnB,IAAI,GAAG,UAAU,CAAA;IAEhB,QAAQ,CAAU;IAEnC,YAAoB,QAAkB;QACpC,KAAK,EAAE,CAAA;QAEP,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAA;IAC1B,CAAC;IAEM,MAAM,CAAE,IAAY,EAAE,MAAqB,EAAE,GAAG,IAAe;QACpE,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;QAEnC,qBAAM,CAAC,EAAE,CAAC,OAAO,KAAK,SAAS,EAAE,YAAY,IAAI,kBAAkB,CAAC,CAAA;QAEpE,6DAA6D;QAC7D,mBAAmB;QACnB,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI,CAAC,CAAA;IACjC,CAAC;CACF;AApBD,wBAoBC"}
@@ -0,0 +1,14 @@
1
+ export interface Entry {
2
+ id: string;
3
+ size: number;
4
+ type: string;
5
+ created: number;
6
+ variants: Variant[];
7
+ meta: Record<string, unknown>;
8
+ }
9
+ interface Variant {
10
+ name: string;
11
+ size: number;
12
+ type: string;
13
+ }
14
+ export {};
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=Entry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Entry.js","sourceRoot":"","sources":["../source/Entry.ts"],"names":[],"mappings":""}
@@ -0,0 +1,9 @@
1
+ import { Aspect } from './Aspect';
2
+ export declare class Factory {
3
+ private readonly annotation;
4
+ constructor();
5
+ aspect(): Aspect;
6
+ private createStorages;
7
+ private createStorage;
8
+ private resolveSecrets;
9
+ }
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Factory = void 0;
7
+ const node_assert_1 = __importDefault(require("node:assert"));
8
+ const generic_1 = require("@toa.io/generic");
9
+ const providers_1 = require("./providers");
10
+ const Storage_1 = require("./Storage");
11
+ const Aspect_1 = require("./Aspect");
12
+ const deployment_1 = require("./deployment");
13
+ const Annotation_1 = require("./Annotation");
14
+ class Factory {
15
+ annotation;
16
+ constructor() {
17
+ const env = process.env.TOA_STORAGES;
18
+ node_assert_1.default.ok(env !== undefined, 'TOA_STORAGES is not defined');
19
+ this.annotation = (0, generic_1.decode)(env);
20
+ (0, Annotation_1.validateAnnotation)(this.annotation);
21
+ }
22
+ aspect() {
23
+ const storages = this.createStorages();
24
+ return new Aspect_1.Aspect(storages);
25
+ }
26
+ createStorages() {
27
+ const storages = {};
28
+ for (const [name, declaration] of Object.entries(this.annotation))
29
+ storages[name] = this.createStorage(name, declaration);
30
+ return storages;
31
+ }
32
+ createStorage(name, declaration) {
33
+ const { provider: providerId, ...options } = declaration;
34
+ const Provider = providers_1.providers[providerId];
35
+ const secrets = this.resolveSecrets(name, Provider);
36
+ const provider = new Provider(options, secrets);
37
+ return new Storage_1.Storage(provider);
38
+ }
39
+ resolveSecrets(storageName, Class) {
40
+ if (Class.SECRETS === undefined)
41
+ return {};
42
+ const secrets = {};
43
+ for (const secret of Class.SECRETS) {
44
+ const variable = `${deployment_1.SERIALIZATION_PREFIX}_${storageName}_${secret.name}`.toUpperCase();
45
+ const value = process.env[variable];
46
+ node_assert_1.default.ok(secret.optional === true || value !== undefined, `'${variable}' is not defined`);
47
+ secrets[secret.name] = value;
48
+ }
49
+ return secrets;
50
+ }
51
+ }
52
+ exports.Factory = Factory;
53
+ //# sourceMappingURL=Factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Factory.js","sourceRoot":"","sources":["../source/Factory.ts"],"names":[],"mappings":";;;;;;AAAA,8DAAgC;AAChC,6CAAwC;AACxC,2CAAuC;AACvC,uCAAkD;AAClD,qCAAiC;AACjC,6CAAmD;AACnD,6CAAiD;AAKjD,MAAa,OAAO;IACD,UAAU,CAAY;IAEvC;QACE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAA;QAEpC,qBAAM,CAAC,EAAE,CAAC,GAAG,KAAK,SAAS,EAAE,6BAA6B,CAAC,CAAA;QAE3D,IAAI,CAAC,UAAU,GAAG,IAAA,gBAAM,EAAC,GAAG,CAAC,CAAA;QAE7B,IAAA,+BAAkB,EAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IACrC,CAAC;IAEM,MAAM;QACX,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,EAAE,CAAA;QAEtC,OAAO,IAAI,eAAM,CAAC,QAAQ,CAAC,CAAA;IAC7B,CAAC;IAEO,cAAc;QACpB,MAAM,QAAQ,GAAa,EAAE,CAAA;QAE7B,KAAK,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC;YAC/D,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,WAAW,CAAC,CAAA;QAExD,OAAO,QAAQ,CAAA;IACjB,CAAC;IAEO,aAAa,CAAE,IAAY,EAAE,WAAwB;QAC3D,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,OAAO,EAAE,GAAG,WAAW,CAAA;QACxD,MAAM,QAAQ,GAAwB,qBAAS,CAAC,UAAU,CAAC,CAAA;QAC3D,MAAM,OAAO,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;QACnD,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;QAE/C,OAAO,IAAI,iBAAO,CAAC,QAAQ,CAAC,CAAA;IAC9B,CAAC;IAEO,cAAc,CAAE,WAAmB,EACzC,KAA0B;QAC1B,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS;YAC7B,OAAO,EAAE,CAAA;QAEX,MAAM,OAAO,GAAuC,EAAE,CAAA;QAEtD,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;YACnC,MAAM,QAAQ,GAAG,GAAG,iCAAoB,IAAI,WAAW,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;YACtF,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;YAEnC,qBAAM,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EACvD,IAAI,QAAQ,kBAAkB,CAAC,CAAA;YAEjC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAA;QAC9B,CAAC;QAED,OAAO,OAAO,CAAA;IAChB,CAAC;CACF;AAxDD,0BAwDC"}
@@ -0,0 +1,20 @@
1
+ /// <reference types="node" />
2
+ import { type Readable } from 'node:stream';
3
+ export type ProviderSecrets<K extends string = string> = Record<K | string, string | undefined>;
4
+ export interface ProviderSecret {
5
+ readonly name: string;
6
+ readonly optional?: boolean;
7
+ }
8
+ export declare abstract class Provider<Options = void> {
9
+ static readonly SECRETS: readonly ProviderSecret[];
10
+ constructor(_: Options, secrets?: ProviderSecrets);
11
+ abstract get(path: string): Promise<Readable | null>;
12
+ abstract put(path: string, filename: string, stream: Readable): Promise<void>;
13
+ abstract delete(path: string): Promise<void>;
14
+ abstract move(from: string, to: string): Promise<void>;
15
+ }
16
+ export interface ProviderConstructor {
17
+ readonly SECRETS: readonly ProviderSecret[];
18
+ prototype: Provider;
19
+ new (options: any, secrets?: ProviderSecrets): Provider;
20
+ }
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.Provider = void 0;
27
+ const assert = __importStar(require("node:assert"));
28
+ class Provider {
29
+ static SECRETS = [];
30
+ constructor(_, secrets) {
31
+ for (const { name, optional = false } of new.target.SECRETS)
32
+ assert.ok(optional || secrets?.[name] !== undefined, `Missing secret '${name}'`);
33
+ }
34
+ }
35
+ exports.Provider = Provider;
36
+ //# sourceMappingURL=Provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Provider.js","sourceRoot":"","sources":["../source/Provider.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AACA,oDAAqC;AASrC,MAAsB,QAAQ;IACrB,MAAM,CAAU,OAAO,GAA8B,EAAE,CAAA;IAE9D,YAAoB,CAAU,EAAE,OAAyB;QACvD,KAAK,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,KAAK,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO;YACzD,MAAM,CAAC,EAAE,CAAC,QAAQ,IAAI,OAAO,EAAE,CAAC,IAAI,CAAC,KAAK,SAAS,EAAE,mBAAmB,IAAI,GAAG,CAAC,CAAA;IACpF,CAAC;;AANH,4BAeC"}
@@ -0,0 +1,26 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ import { PassThrough, type TransformCallback } from 'node:stream';
4
+ export declare class Scanner extends PassThrough {
5
+ size: number;
6
+ type: string;
7
+ error: Error | null;
8
+ private readonly hash;
9
+ private readonly claim?;
10
+ private readonly accept?;
11
+ private position;
12
+ private detected;
13
+ private readonly chunks;
14
+ constructor(control?: ScanOptions);
15
+ digest(): string;
16
+ _transform(buffer: Buffer, encoding: BufferEncoding, callback: TransformCallback): void;
17
+ private readonly process;
18
+ private complete;
19
+ private verify;
20
+ private match;
21
+ private interrupt;
22
+ }
23
+ export interface ScanOptions {
24
+ claim?: string;
25
+ accept?: string;
26
+ }
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Scanner = void 0;
4
+ const node_stream_1 = require("node:stream");
5
+ const node_crypto_1 = require("node:crypto");
6
+ const agent_1 = require("@toa.io/agent");
7
+ const error_value_1 = require("error-value");
8
+ class Scanner extends node_stream_1.PassThrough {
9
+ size = 0;
10
+ type = 'application/octet-stream';
11
+ error = null;
12
+ hash = (0, node_crypto_1.createHash)('md5');
13
+ claim;
14
+ accept;
15
+ position = 0;
16
+ detected = false;
17
+ chunks = [];
18
+ constructor(control) {
19
+ super();
20
+ this.claim = control?.claim;
21
+ this.accept = control?.accept;
22
+ }
23
+ digest() {
24
+ return this.hash.digest('hex');
25
+ }
26
+ _transform(buffer, encoding, callback) {
27
+ super._transform(buffer, encoding, callback);
28
+ this.process(buffer);
29
+ }
30
+ process = (buffer) => {
31
+ this.size += buffer.length;
32
+ this.hash.update(buffer);
33
+ if (this.detected)
34
+ return;
35
+ if (this.position + buffer.length > HEADER_SIZE)
36
+ buffer = buffer.subarray(0, HEADER_SIZE - this.position);
37
+ this.chunks.push(buffer);
38
+ this.position += buffer.length;
39
+ if (this.position === HEADER_SIZE)
40
+ this.complete();
41
+ };
42
+ complete() {
43
+ const header = Buffer.concat(this.chunks).toString('hex');
44
+ const signature = SIGNATURES
45
+ .find(({ hex, off }) => header.slice(off, off + hex.length) === hex);
46
+ const type = signature?.type ?? this.claim;
47
+ if (type !== undefined) {
48
+ this.match(type);
49
+ this.type = type;
50
+ }
51
+ this.verify(signature);
52
+ this.detected = true;
53
+ }
54
+ verify(signature) {
55
+ if (this.claim === undefined || this.claim === 'application/octet-stream')
56
+ return;
57
+ const mismatch = signature === undefined
58
+ ? KNOWN_TYPES.has(this.claim)
59
+ : this.claim !== signature.type;
60
+ if (mismatch)
61
+ this.interrupt(ERR_TYPE_MISMATCH);
62
+ }
63
+ match(type) {
64
+ if (this.accept === undefined)
65
+ return;
66
+ const unacceptable = (0, agent_1.negotiate)(this.accept, [type]) === null;
67
+ if (unacceptable)
68
+ this.interrupt(ERR_NOT_ACCEPTABLE);
69
+ }
70
+ interrupt(error) {
71
+ this.error = error;
72
+ this.end();
73
+ }
74
+ }
75
+ exports.Scanner = Scanner;
76
+ // https://en.wikipedia.org/wiki/List_of_file_signatures
77
+ const SIGNATURES = [
78
+ { hex: 'ffd8ffe0', off: 0, type: 'image/jpeg' },
79
+ { hex: 'ffd8ffe1', off: 0, type: 'image/jpeg' },
80
+ { hex: 'ffd8ffee', off: 0, type: 'image/jpeg' },
81
+ { hex: 'ffd8ffdb', off: 0, type: 'image/jpeg' },
82
+ { hex: '89504e47', off: 0, type: 'image/png' },
83
+ { hex: '47494638', off: 0, type: 'image/gif' },
84
+ { hex: '52494646', off: 0, type: 'image/webp' },
85
+ { hex: '4a584c200d0a870a', off: 8, type: 'image/jxl' },
86
+ { hex: '6674797068656963', off: 8, type: 'image/heic' },
87
+ { hex: '6674797061766966', off: 8, type: 'image/avif' }
88
+ /*
89
+ When adding a new signature, include a copyright-free sample file in the `.tests` directory
90
+ and update the 'signatures' test group in `Storage.test.ts`.
91
+ */
92
+ ];
93
+ const HEADER_SIZE = SIGNATURES
94
+ .reduce((max, { off, hex }) => Math.max(max, off + hex.length), 0) / 2;
95
+ const KNOWN_TYPES = new Set(SIGNATURES.map(({ type }) => type));
96
+ const ERR_TYPE_MISMATCH = (0, error_value_1.Err)('TYPE_MISMATCH');
97
+ const ERR_NOT_ACCEPTABLE = (0, error_value_1.Err)('NOT_ACCEPTABLE');
98
+ //# sourceMappingURL=Scanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Scanner.js","sourceRoot":"","sources":["../source/Scanner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /// <reference types="node" />
2
+ import { Readable } from 'node:stream';
3
+ import type { ScanOptions } from './Scanner';
4
+ import type { Provider } from './Provider';
5
+ import type { Entry } from './Entry';
6
+ export declare class Storage {
7
+ private readonly provider;
8
+ constructor(provider: Provider);
9
+ put(path: string, stream: Readable, options?: Options): Maybe<Entry>;
10
+ get(path: string): Maybe<Entry>;
11
+ fetch(path: string): Maybe<Readable>;
12
+ delete(path: string): Maybe<void>;
13
+ list(path: string): Promise<string[]>;
14
+ permute(path: string, ids: string[]): Maybe<void>;
15
+ conceal(path: string): Maybe<void>;
16
+ reveal(path: string): Maybe<void>;
17
+ diversify(path: string, name: string, stream: Readable): Maybe<void>;
18
+ annotate(path: string, key: string, value?: unknown): Maybe<void>;
19
+ private transit;
20
+ private persist;
21
+ private create;
22
+ private save;
23
+ private enroll;
24
+ private parse;
25
+ }
26
+ type Maybe<T> = Promise<T | Error>;
27
+ type Meta = Record<string, string>;
28
+ interface Options extends ScanOptions {
29
+ meta?: Meta;
30
+ }
31
+ export type Storages = Record<string, Storage>;
32
+ export {};