@openstax/ts-utils 1.32.4 → 1.32.6

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 (27) hide show
  1. package/dist/cjs/routing/index.d.ts +20 -2
  2. package/dist/cjs/routing/index.js +10 -1
  3. package/dist/cjs/services/apiGateway/index.d.ts +1 -1
  4. package/dist/cjs/services/documentStore/unversioned/file-system.d.ts +1 -0
  5. package/dist/cjs/services/documentStore/unversioned/file-system.js +18 -8
  6. package/dist/cjs/services/documentStore/versioned/file-system.d.ts +1 -0
  7. package/dist/cjs/services/documentStore/versioned/file-system.js +11 -5
  8. package/dist/cjs/services/fileServer/index.d.ts +12 -0
  9. package/dist/cjs/services/fileServer/localFileServer.js +52 -1
  10. package/dist/cjs/services/fileServer/s3FileServer.d.ts +1 -0
  11. package/dist/cjs/services/fileServer/s3FileServer.js +78 -0
  12. package/dist/cjs/services/searchProvider/openSearch.js +6 -4
  13. package/dist/cjs/tsconfig.without-specs.cjs.tsbuildinfo +1 -1
  14. package/dist/esm/routing/index.d.ts +20 -2
  15. package/dist/esm/routing/index.js +8 -0
  16. package/dist/esm/services/apiGateway/index.d.ts +1 -1
  17. package/dist/esm/services/documentStore/unversioned/file-system.d.ts +1 -0
  18. package/dist/esm/services/documentStore/unversioned/file-system.js +18 -8
  19. package/dist/esm/services/documentStore/versioned/file-system.d.ts +1 -0
  20. package/dist/esm/services/documentStore/versioned/file-system.js +11 -5
  21. package/dist/esm/services/fileServer/index.d.ts +12 -0
  22. package/dist/esm/services/fileServer/localFileServer.js +53 -2
  23. package/dist/esm/services/fileServer/s3FileServer.d.ts +1 -0
  24. package/dist/esm/services/fileServer/s3FileServer.js +76 -1
  25. package/dist/esm/services/searchProvider/openSearch.js +6 -4
  26. package/dist/esm/tsconfig.without-specs.esm.tsbuildinfo +1 -1
  27. package/package.json +4 -3
@@ -25,6 +25,20 @@ export type RouteMatchRecord<R> = R extends AnyRoute<R> ? {
25
25
  route: R;
26
26
  params: ParamsForRoute<R>;
27
27
  } : never;
28
+ type Flatten<T> = T extends any ? {
29
+ [K in keyof T]: T[K];
30
+ } : never;
31
+ export type ExternalRoute<R> = R extends Route<infer N, infer P, infer Sa, infer Sr, infer Ri, infer Ro> & infer E ? Route<N, P, Sa, Sr extends {
32
+ payload: any;
33
+ } ? Flatten<Pick<Sr, 'payload'>> : Record<string, never>, Ri, Ro> & Flatten<Omit<E, 'name' | 'path' | 'handler' | 'requestServiceProvider'>> : never;
34
+ /** this utility simplifies the route type to remove stuff that is only
35
+ * relevant internal to the route, like the service types, keeping only
36
+ * the payload type which is necessary for the apiGateway
37
+ *
38
+ * this helps avoid the "type too complicated" error that typescript throws
39
+ * when there are a lot of routes with complex services
40
+ **/
41
+ export declare const routesList: <R>(routes: R[]) => ExternalRoute<R>[];
28
42
  /**
29
43
  * The conditional type for the payload for a given route, `R`. This isn't a route structure, its
30
44
  * a convention based on the request middleware
@@ -140,7 +154,9 @@ export declare const makeCreateRoute: <Sa, Ri, Ex = {}>() => CreateRoute<Sa, Ri,
140
154
  */
141
155
  export declare const makeRenderRouteUrl: <Ru extends {
142
156
  path: string;
143
- }>() => <R extends Ru>(route: R, params: ParamsForRoute<R>, query?: QueryParams) => string;
157
+ }>() => <R>(route: ExternalRoute<R> extends Ru ? R & {
158
+ path: string;
159
+ } : R extends Ru ? R : never, params: ParamsForRoute<R>, query?: QueryParams) => string;
144
160
  /**
145
161
  * A pre-made result from `makeRenderRouteUrl`, this function interpolates parameter and query
146
162
  * arguments into a route path.
@@ -153,7 +169,9 @@ export declare const makeRenderRouteUrl: <Ru extends {
153
169
  * @param query the query parameters to add to the route path
154
170
  * @returns the interpolated route path
155
171
  */
156
- export declare const renderAnyRouteUrl: <R extends any>(route: R, params: ParamsForRoute<R>, query?: QueryParams) => string;
172
+ export declare const renderAnyRouteUrl: <R>(route: ExternalRoute<R> extends any ? R & {
173
+ path: string;
174
+ } : R extends any ? R : never, params: ParamsForRoute<R>, query?: QueryParams) => string;
157
175
  type RequestPathExtractor<Ri> = (request: Ri) => string;
158
176
  type RequestLogExtractor<Ri> = (request: Ri) => JsonCompatibleStruct;
159
177
  type RequestRouteMatcher<Ri, R> = (request: Ri, route: R) => boolean;
@@ -39,11 +39,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
39
39
  return (mod && mod.__esModule) ? mod : { "default": mod };
40
40
  };
41
41
  Object.defineProperty(exports, "__esModule", { value: true });
42
- exports.METHOD = exports.apiHtmlResponse = exports.apiTextResponse = exports.apiJsonResponse = exports.makeGetRequestResponder = exports.renderAnyRouteUrl = exports.makeRenderRouteUrl = exports.makeCreateRoute = void 0;
42
+ exports.METHOD = exports.apiHtmlResponse = exports.apiTextResponse = exports.apiJsonResponse = exports.makeGetRequestResponder = exports.renderAnyRouteUrl = exports.makeRenderRouteUrl = exports.makeCreateRoute = exports.routesList = void 0;
43
43
  const pathToRegexp = __importStar(require("path-to-regexp"));
44
44
  const query_string_1 = __importDefault(require("query-string"));
45
45
  const helpers_1 = require("../misc/helpers");
46
46
  const console_1 = require("../services/logger/console");
47
+ /** this utility simplifies the route type to remove stuff that is only
48
+ * relevant internal to the route, like the service types, keeping only
49
+ * the payload type which is necessary for the apiGateway
50
+ *
51
+ * this helps avoid the "type too complicated" error that typescript throws
52
+ * when there are a lot of routes with complex services
53
+ **/
54
+ const routesList = (routes) => routes;
55
+ exports.routesList = routesList;
47
56
  /**
48
57
  * Makes a createRoute function that can be used to create routes (this is a factory factory). The
49
58
  * `makeCreateRoute` function is typically called once in the backend and once in the frontend to
@@ -38,7 +38,7 @@ export type ApiClientResponse<Ro> = Ro extends any ? {
38
38
  } : never;
39
39
  export type ExpandRoute<T> = T extends ((...args: infer A) => infer R) & {
40
40
  renderUrl: (...args: infer Ar) => Promise<string>;
41
- } ? (...args: A) => R & {
41
+ } ? ((...args: A) => R) & {
42
42
  renderUrl: (...args: Ar) => Promise<string>;
43
43
  } : never;
44
44
  export type MapRoutesToClient<Ru> = [Ru] extends [AnyRoute<Ru>] ? {
@@ -8,6 +8,7 @@ interface Initializer<C> {
8
8
  export declare const fileSystemUnversionedDocumentStore: <C extends string = "fileSystem">(initializer: Initializer<C>) => <T extends TDocument<T>>() => (configProvider: { [_key in C]: ConfigProviderForConfig<Config>; }) => <K extends keyof T>(_: {}, hashKey: K, options?: {
9
9
  afterWrite?: (item: T) => void | Promise<void>;
10
10
  batchAfterWrite?: (items: T[]) => void | Promise<void>;
11
+ skipAssert?: boolean;
11
12
  }) => {
12
13
  loadAllDocumentsTheBadWay: () => Promise<T[]>;
13
14
  getItemsByField: (key: keyof T, value: T[K], pageKey?: string) => Promise<{
@@ -45,9 +45,11 @@ const errors_1 = require("../../../errors");
45
45
  const guards_1 = require("../../../guards");
46
46
  const fileSystemAssert_1 = require("../fileSystemAssert");
47
47
  const fileSystemUnversionedDocumentStore = (initializer) => () => (configProvider) => (_, hashKey, options) => {
48
+ var _a;
48
49
  const tableName = (0, config_1.resolveConfigValue)(configProvider[initializer.configSpace || 'fileSystem'].tableName);
49
50
  const tablePath = tableName.then((table) => path_1.default.join(initializer.dataDir, table));
50
51
  const { mkdir, readdir, readFile, writeFile } = (0, guards_1.ifDefined)(initializer.fs, fsModule);
52
+ const skipAssert = (_a = options === null || options === void 0 ? void 0 : options.skipAssert) !== null && _a !== void 0 ? _a : false;
51
53
  const mkTableDir = new Promise((resolve, reject) => tablePath.then((path) => mkdir(path, { recursive: true }, (err) => err && err.code !== 'EEXIST' ? reject(err) : resolve())));
52
54
  const hashFilename = (value) => `${(0, __1.hashValue)(value)}.json`;
53
55
  const filePath = async (filename) => path_1.default.join(await tablePath, filename);
@@ -82,7 +84,8 @@ const fileSystemUnversionedDocumentStore = (initializer) => () => (configProvide
82
84
  return {
83
85
  loadAllDocumentsTheBadWay,
84
86
  getItemsByField: async (key, value, pageKey) => {
85
- (0, fileSystemAssert_1.assertNoUndefined)(value, [key]);
87
+ if (!skipAssert)
88
+ (0, fileSystemAssert_1.assertNoUndefined)(value, [key]);
86
89
  const pageSize = 10;
87
90
  const items = await loadAllDocumentsTheBadWay();
88
91
  const filteredItems = items.filter((item) => item[key] === value);
@@ -95,17 +98,20 @@ const fileSystemUnversionedDocumentStore = (initializer) => () => (configProvide
95
98
  },
96
99
  batchGetItem: async (ids) => {
97
100
  const items = await Promise.all(ids.map((id) => {
98
- (0, fileSystemAssert_1.assertNoUndefined)(id, ['id']);
101
+ if (!skipAssert)
102
+ (0, fileSystemAssert_1.assertNoUndefined)(id, ['id']);
99
103
  return load(hashFilename(id));
100
104
  }));
101
105
  return items.filter(guards_1.isDefined);
102
106
  },
103
107
  getItem: (id) => {
104
- (0, fileSystemAssert_1.assertNoUndefined)(id, ['id']);
108
+ if (!skipAssert)
109
+ (0, fileSystemAssert_1.assertNoUndefined)(id, ['id']);
105
110
  return load(hashFilename(id));
106
111
  },
107
112
  incrementItemAttribute: async (id, attribute) => {
108
- (0, fileSystemAssert_1.assertNoUndefined)(id, ['id']);
113
+ if (!skipAssert)
114
+ (0, fileSystemAssert_1.assertNoUndefined)(id, ['id']);
109
115
  const filename = hashFilename(id);
110
116
  const path = await filePath(filename);
111
117
  await mkTableDir;
@@ -136,13 +142,15 @@ const fileSystemUnversionedDocumentStore = (initializer) => () => (configProvide
136
142
  throw new errors_1.NotFoundError(`Item with ${hashKey.toString()} "${id}" does not exist`);
137
143
  }
138
144
  const newItem = { ...data, ...item };
139
- (0, fileSystemAssert_1.assertNoUndefined)(newItem);
145
+ if (!skipAssert)
146
+ (0, fileSystemAssert_1.assertNoUndefined)(newItem);
140
147
  return new Promise((resolve, reject) => {
141
148
  writeFile(path, JSON.stringify(newItem, null, 2), (err) => err ? reject(err) : resolve(newItem));
142
149
  });
143
150
  },
144
151
  putItem: async (item) => {
145
- (0, fileSystemAssert_1.assertNoUndefined)(item);
152
+ if (!skipAssert)
153
+ (0, fileSystemAssert_1.assertNoUndefined)(item);
146
154
  const path = await filePath(hashFilename(item[hashKey]));
147
155
  await mkTableDir;
148
156
  return new Promise((resolve, reject) => {
@@ -150,7 +158,8 @@ const fileSystemUnversionedDocumentStore = (initializer) => () => (configProvide
150
158
  });
151
159
  },
152
160
  createItem: async (item) => {
153
- (0, fileSystemAssert_1.assertNoUndefined)(item);
161
+ if (!skipAssert)
162
+ (0, fileSystemAssert_1.assertNoUndefined)(item);
154
163
  const hashed = hashFilename(item[hashKey]);
155
164
  const existingItem = await load(hashed);
156
165
  if (existingItem) {
@@ -171,7 +180,8 @@ const fileSystemUnversionedDocumentStore = (initializer) => () => (configProvide
171
180
  // Process items sequentially to ensure consistent conflict detection
172
181
  // Note: concurrency parameter is ignored for filesystem to avoid race conditions
173
182
  for (const item of items) {
174
- (0, fileSystemAssert_1.assertNoUndefined)(item);
183
+ if (!skipAssert)
184
+ (0, fileSystemAssert_1.assertNoUndefined)(item);
175
185
  try {
176
186
  const hashed = hashFilename(item[hashKey]);
177
187
  const existingItem = await load(hashed);
@@ -8,6 +8,7 @@ interface Initializer<C> {
8
8
  }
9
9
  export declare const fileSystemVersionedDocumentStore: <C extends string = "fileSystem">(initializer: Initializer<C>) => <T extends VersionedTDocument<T>>() => (configProvider: { [_key in C]: ConfigProviderForConfig<Config>; }) => <K extends keyof T, A extends undefined | ((...a: any[]) => Promise<VersionedDocumentAuthor>)>(_: {}, hashKey: K, options?: {
10
10
  getAuthor?: A;
11
+ skipAssert?: boolean;
11
12
  }) => {
12
13
  loadAllDocumentsTheBadWay: () => Promise<T[]>;
13
14
  getVersions: (id: T[K], startVersion?: number) => Promise<{
@@ -5,7 +5,9 @@ const fileSystemAssert_1 = require("../fileSystemAssert");
5
5
  const file_system_1 = require("../unversioned/file-system");
6
6
  const PAGE_LIMIT = 5;
7
7
  const fileSystemVersionedDocumentStore = (initializer) => () => (configProvider) => (_, hashKey, options) => {
8
- const unversionedDocuments = (0, file_system_1.fileSystemUnversionedDocumentStore)(initializer)()(configProvider)({}, 'id');
8
+ var _a;
9
+ const skipAssert = (_a = options === null || options === void 0 ? void 0 : options.skipAssert) !== null && _a !== void 0 ? _a : false;
10
+ const unversionedDocuments = (0, file_system_1.fileSystemUnversionedDocumentStore)(initializer)()(configProvider)({ skipAssert: skipAssert }, 'id');
9
11
  return {
10
12
  loadAllDocumentsTheBadWay: () => {
11
13
  return unversionedDocuments.loadAllDocumentsTheBadWay().then(documents => documents.map(document => {
@@ -13,7 +15,8 @@ const fileSystemVersionedDocumentStore = (initializer) => () => (configProvider)
13
15
  }));
14
16
  },
15
17
  getVersions: async (id, startVersion) => {
16
- (0, fileSystemAssert_1.assertNoUndefined)(id, ['id']);
18
+ if (!skipAssert)
19
+ (0, fileSystemAssert_1.assertNoUndefined)(id, ['id']);
17
20
  const item = await unversionedDocuments.getItem(id);
18
21
  const versions = item === null || item === void 0 ? void 0 : item.items.reverse();
19
22
  if (!versions) {
@@ -28,7 +31,8 @@ const fileSystemVersionedDocumentStore = (initializer) => () => (configProvider)
28
31
  };
29
32
  },
30
33
  getItem: async (id, timestamp) => {
31
- (0, fileSystemAssert_1.assertNoUndefined)(id, ['id']);
34
+ if (!skipAssert)
35
+ (0, fileSystemAssert_1.assertNoUndefined)(id, ['id']);
32
36
  const item = await unversionedDocuments.getItem(id);
33
37
  if (timestamp) {
34
38
  return item === null || item === void 0 ? void 0 : item.items.find(version => version.timestamp === timestamp);
@@ -44,7 +48,8 @@ const fileSystemVersionedDocumentStore = (initializer) => () => (configProvider)
44
48
  save: async (changes) => {
45
49
  var _a;
46
50
  const document = { ...item, ...changes, timestamp, author };
47
- (0, fileSystemAssert_1.assertNoUndefined)(document);
51
+ if (!skipAssert)
52
+ (0, fileSystemAssert_1.assertNoUndefined)(document);
48
53
  const container = (_a = await unversionedDocuments.getItem(document[hashKey])) !== null && _a !== void 0 ? _a : { id: document[hashKey], items: [] };
49
54
  const updated = { ...container, items: [...container.items, document] };
50
55
  await unversionedDocuments.putItem(updated);
@@ -56,7 +61,8 @@ const fileSystemVersionedDocumentStore = (initializer) => () => (configProvider)
56
61
  var _a;
57
62
  const author = (options === null || options === void 0 ? void 0 : options.getAuthor) ? await options.getAuthor(...authorArgs) : authorArgs[0];
58
63
  const document = { ...item, timestamp: new Date().getTime(), author };
59
- (0, fileSystemAssert_1.assertNoUndefined)(document);
64
+ if (!skipAssert)
65
+ (0, fileSystemAssert_1.assertNoUndefined)(document);
60
66
  const container = (_a = await unversionedDocuments.getItem(document[hashKey])) !== null && _a !== void 0 ? _a : { id: document[hashKey], items: [] };
61
67
  const updated = { ...container, items: [...container.items, document] };
62
68
  await unversionedDocuments.putItem(updated);
@@ -13,6 +13,18 @@ export declare const isFolderValue: (thing: any) => thing is FolderValue;
13
13
  export interface FileServerAdapter {
14
14
  putFileContent: (source: FileValue, content: string) => Promise<FileValue>;
15
15
  getSignedViewerUrl: (source: FileValue) => Promise<string>;
16
+ getPublicViewerUrl: (source: FileValue) => Promise<string>;
16
17
  getFileContent: (source: FileValue) => Promise<Buffer>;
18
+ getSignedFileUploadConfig: () => Promise<{
19
+ url: string;
20
+ payload: {
21
+ [key: string]: string;
22
+ };
23
+ }>;
24
+ copyFileTo: (source: FileValue, destinationPath: string) => Promise<FileValue>;
25
+ copyFileToDirectory: (source: FileValue, destinationDirectory: string) => Promise<FileValue>;
26
+ isTemporaryUpload: (source: FileValue) => boolean;
27
+ getFileChecksum: (source: FileValue) => Promise<string>;
28
+ filesEqual: (sourceA: FileValue, sourceB: FileValue) => Promise<boolean>;
17
29
  }
18
30
  export declare const isFileOrFolder: (thing: any) => thing is FileValue | FolderValue;
@@ -5,18 +5,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.localFileServer = void 0;
7
7
  /* cspell:ignore originalname */
8
+ const crypto_1 = __importDefault(require("crypto"));
8
9
  const fs_1 = __importDefault(require("fs"));
9
10
  const https_1 = __importDefault(require("https"));
10
11
  const path_1 = __importDefault(require("path"));
11
12
  const cors_1 = __importDefault(require("cors"));
12
13
  const express_1 = __importDefault(require("express"));
13
14
  const multer_1 = __importDefault(require("multer"));
15
+ const uuid_1 = require("uuid");
14
16
  const assertions_1 = require("../../assertions");
15
17
  const config_1 = require("../../config");
16
18
  const guards_1 = require("../../guards");
17
19
  const helpers_1 = require("../../misc/helpers");
18
20
  /* istanbul ignore next */
19
- const startServer = (0, helpers_1.once)((port, uploadDir) => {
21
+ const startServer = (0, helpers_1.memoize)((port, uploadDir) => {
20
22
  // TODO - re-evaluate the `preservePath` behavior to match whatever s3 does
21
23
  const upload = (0, multer_1.default)({ dest: uploadDir, preservePath: true });
22
24
  const fileServerApp = (0, express_1.default)();
@@ -61,6 +63,9 @@ const localFileServer = (initializer) => (configProvider) => {
61
63
  const getSignedViewerUrl = async (source) => {
62
64
  return `https://${await host}:${await port}/${source.path}`;
63
65
  };
66
+ const getPublicViewerUrl = async (source) => {
67
+ return `https://${await host}:${await port}/${source.path}`;
68
+ };
64
69
  const getFileContent = async (source) => {
65
70
  const filePath = path_1.default.join(await fileDir, source.path);
66
71
  return fs_1.default.promises.readFile(filePath);
@@ -72,10 +77,56 @@ const localFileServer = (initializer) => (configProvider) => {
72
77
  await fs_1.default.promises.writeFile(filePath, content);
73
78
  return source;
74
79
  };
80
+ const getSignedFileUploadConfig = async () => {
81
+ const prefix = 'uploads/' + (0, uuid_1.v4)();
82
+ return {
83
+ url: `https://${await host}:${await port}/`,
84
+ payload: {
85
+ key: prefix + '/${filename}',
86
+ }
87
+ };
88
+ };
89
+ const copyFileTo = async (source, destinationPath) => {
90
+ const sourcePath = path_1.default.join(await fileDir, source.path);
91
+ const destPath = path_1.default.join(await fileDir, destinationPath);
92
+ const destDirectory = path_1.default.dirname(destPath);
93
+ await fs_1.default.promises.mkdir(destDirectory, { recursive: true });
94
+ await fs_1.default.promises.copyFile(sourcePath, destPath);
95
+ return {
96
+ ...source,
97
+ path: destinationPath
98
+ };
99
+ };
100
+ const copyFileToDirectory = async (source, destination) => {
101
+ const destinationPath = path_1.default.join(destination, source.label);
102
+ return copyFileTo(source, destinationPath);
103
+ };
104
+ const isTemporaryUpload = (source) => {
105
+ return source.path.indexOf('uploads/') === 0;
106
+ };
107
+ const getFileChecksum = async (source) => {
108
+ const filePath = path_1.default.join(await fileDir, source.path);
109
+ const fileContent = await fs_1.default.promises.readFile(filePath);
110
+ return crypto_1.default.createHash('md5').update(fileContent).digest('hex');
111
+ };
112
+ const filesEqual = async (sourceA, sourceB) => {
113
+ const [aSum, bSum] = await Promise.all([
114
+ getFileChecksum(sourceA),
115
+ getFileChecksum(sourceB)
116
+ ]);
117
+ return aSum === bSum;
118
+ };
75
119
  return {
76
120
  getSignedViewerUrl,
121
+ getPublicViewerUrl,
77
122
  getFileContent,
78
123
  putFileContent,
124
+ getSignedFileUploadConfig,
125
+ copyFileTo,
126
+ copyFileToDirectory,
127
+ isTemporaryUpload,
128
+ getFileChecksum,
129
+ filesEqual,
79
130
  };
80
131
  };
81
132
  exports.localFileServer = localFileServer;
@@ -4,6 +4,7 @@ import { FileServerAdapter } from '.';
4
4
  export type Config = {
5
5
  bucketName: string;
6
6
  bucketRegion: string;
7
+ publicViewerDomain?: string;
7
8
  };
8
9
  interface Initializer<C> {
9
10
  configSpace?: C;
@@ -1,9 +1,15 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.s3FileServer = void 0;
4
7
  /* cspell:ignore presigner */
8
+ const path_1 = __importDefault(require("path"));
5
9
  const client_s3_1 = require("@aws-sdk/client-s3");
10
+ const s3_presigned_post_1 = require("@aws-sdk/s3-presigned-post");
6
11
  const s3_request_presigner_1 = require("@aws-sdk/s3-request-presigner");
12
+ const uuid_1 = require("uuid");
7
13
  const __1 = require("../..");
8
14
  const assertions_1 = require("../../assertions");
9
15
  const config_1 = require("../../config");
@@ -12,6 +18,9 @@ const s3FileServer = (initializer) => (configProvider) => {
12
18
  const config = configProvider[(0, guards_1.ifDefined)(initializer.configSpace, 'deployed')];
13
19
  const bucketName = (0, __1.once)(() => (0, config_1.resolveConfigValue)(config.bucketName));
14
20
  const bucketRegion = (0, __1.once)(() => (0, config_1.resolveConfigValue)(config.bucketRegion));
21
+ const publicViewerDomain = (0, __1.once)(() => 'publicViewerDomain' in config && config.publicViewerDomain
22
+ ? (0, config_1.resolveConfigValue)(config.publicViewerDomain)
23
+ : undefined);
15
24
  const s3Service = (0, __1.once)(async () => {
16
25
  var _a, _b;
17
26
  const args = { apiVersion: '2012-08-10', region: await bucketRegion() };
@@ -27,6 +36,10 @@ const s3FileServer = (initializer) => (configProvider) => {
27
36
  expiresIn: 3600, // 1 hour
28
37
  });
29
38
  };
39
+ const getPublicViewerUrl = async (source) => {
40
+ const host = (0, assertions_1.assertDefined)(await publicViewerDomain(), new Error(`Tried to get public viewer URL for ${source.path} but no publicViewerDomain configured`));
41
+ return `https://${host}/${source.path}`;
42
+ };
30
43
  const getFileContent = async (source) => {
31
44
  const bucket = await bucketName();
32
45
  const command = new client_s3_1.GetObjectCommand({ Bucket: bucket, Key: source.path });
@@ -44,10 +57,75 @@ const s3FileServer = (initializer) => (configProvider) => {
44
57
  await (await s3Service()).send(command);
45
58
  return source;
46
59
  };
60
+ /*
61
+ * https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/modules/_aws_sdk_s3_presigned_post.html
62
+ * https://docs.aws.amazon.com/AmazonS3/latest/userguide/HTTPPOSTExamples.html
63
+ * https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-post-example.html
64
+ */
65
+ const getSignedFileUploadConfig = async () => {
66
+ const prefix = 'uploads/' + (0, uuid_1.v4)();
67
+ const bucket = (await bucketName());
68
+ const Conditions = [
69
+ { acl: 'private' },
70
+ { bucket },
71
+ ['starts-with', '$key', prefix]
72
+ ];
73
+ const defaultFields = {
74
+ acl: 'private',
75
+ };
76
+ const { url, fields } = await (0, s3_presigned_post_1.createPresignedPost)(await s3Service(), {
77
+ Bucket: bucket,
78
+ Key: prefix + '/${filename}',
79
+ Conditions,
80
+ Fields: defaultFields,
81
+ Expires: 3600, // 1 hour
82
+ });
83
+ return {
84
+ url, payload: fields
85
+ };
86
+ };
87
+ const copyFileTo = async (source, destinationPath) => {
88
+ const bucket = (await bucketName());
89
+ const destinationPathWithoutLeadingSlash = destinationPath.replace(/^\//, '');
90
+ const command = new client_s3_1.CopyObjectCommand({
91
+ Bucket: bucket,
92
+ Key: destinationPathWithoutLeadingSlash,
93
+ CopySource: path_1.default.join(bucket, source.path),
94
+ });
95
+ await (await s3Service()).send(command);
96
+ return {
97
+ ...source,
98
+ path: destinationPathWithoutLeadingSlash
99
+ };
100
+ };
101
+ const copyFileToDirectory = async (source, destination) => {
102
+ const destinationPath = path_1.default.join(destination, source.label);
103
+ return copyFileTo(source, destinationPath);
104
+ };
105
+ const isTemporaryUpload = (source) => {
106
+ return source.path.indexOf('uploads/') === 0;
107
+ };
108
+ const getFileChecksum = async (source) => {
109
+ const bucket = (await bucketName());
110
+ const command = new client_s3_1.HeadObjectCommand({ Bucket: bucket, Key: source.path });
111
+ const response = await (await s3Service()).send(command);
112
+ return (0, assertions_1.assertDefined)(response.ETag);
113
+ };
114
+ const filesEqual = async (sourceA, sourceB) => {
115
+ const [aSum, bSum] = await Promise.all([getFileChecksum(sourceA), getFileChecksum(sourceB)]);
116
+ return aSum === bSum;
117
+ };
47
118
  return {
48
119
  getFileContent,
49
120
  putFileContent,
50
121
  getSignedViewerUrl,
122
+ getPublicViewerUrl,
123
+ getSignedFileUploadConfig,
124
+ copyFileTo,
125
+ copyFileToDirectory,
126
+ isTemporaryUpload,
127
+ getFileChecksum,
128
+ filesEqual,
51
129
  };
52
130
  };
53
131
  exports.s3FileServer = s3FileServer;
@@ -29,10 +29,6 @@ const openSearchService = (initializer = {}) => (configProvider) => {
29
29
  maxRetries: 4, // default is 3
30
30
  requestTimeout: 5000, // default is 30000
31
31
  pingTimeout: 2000, // default is 30000
32
- sniffOnConnectionFault: true,
33
- sniffOnStart: true,
34
- resurrectStrategy: 'ping',
35
- agent: { keepAlive: false },
36
32
  node: await (0, config_1.resolveConfigValue)(config.node),
37
33
  }));
38
34
  return (indexConfig) => {
@@ -68,6 +64,9 @@ const openSearchService = (initializer = {}) => (configProvider) => {
68
64
  body: params.body,
69
65
  id: params.id,
70
66
  refresh: true
67
+ }, {
68
+ requestTimeout: 10000,
69
+ maxRetries: 1,
71
70
  });
72
71
  };
73
72
  const bulkIndex = async (items) => {
@@ -79,6 +78,9 @@ const openSearchService = (initializer = {}) => (configProvider) => {
79
78
  item.body
80
79
  ]),
81
80
  refresh: true
81
+ }, {
82
+ requestTimeout: 10000,
83
+ maxRetries: 1,
82
84
  });
83
85
  };
84
86
  const search = async (options) => {