@openstax/ts-utils 1.26.2 → 1.27.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.
- package/dist/cjs/middleware/lambdaCorsResponseMiddleware.js +4 -2
- package/dist/cjs/services/accountsGateway/index.js +5 -4
- package/dist/cjs/services/authProvider/decryption.js +3 -1
- package/dist/cjs/services/authProvider/index.d.ts +2 -1
- package/dist/cjs/services/authProvider/index.js +4 -1
- package/dist/cjs/services/authProvider/subrequest.js +2 -0
- package/dist/cjs/services/documentStore/unversioned/dynamodb.d.ts +6 -0
- package/dist/cjs/services/documentStore/unversioned/dynamodb.js +145 -112
- package/dist/cjs/services/documentStore/unversioned/file-system.d.ts +4 -0
- package/dist/cjs/services/documentStore/unversioned/file-system.js +19 -7
- package/dist/cjs/services/documentStore/versioned/dynamodb.d.ts +2 -0
- package/dist/cjs/services/documentStore/versioned/dynamodb.js +125 -123
- package/dist/cjs/services/fileServer/s3FileServer.d.ts +1 -1
- package/dist/cjs/services/fileServer/s3FileServer.js +5 -2
- package/dist/cjs/services/launchParams/verifier.js +3 -1
- package/dist/cjs/services/searchProvider/index.d.ts +26 -7
- package/dist/cjs/services/searchProvider/memorySearchTheBadWay.d.ts +5 -3
- package/dist/cjs/services/searchProvider/memorySearchTheBadWay.js +97 -41
- package/dist/cjs/services/searchProvider/openSearch.d.ts +2 -2
- package/dist/cjs/services/searchProvider/openSearch.js +13 -9
- package/dist/cjs/tsconfig.without-specs.cjs.tsbuildinfo +1 -1
- package/dist/esm/middleware/lambdaCorsResponseMiddleware.js +4 -2
- package/dist/esm/services/accountsGateway/index.js +5 -4
- package/dist/esm/services/authProvider/decryption.js +3 -1
- package/dist/esm/services/authProvider/index.d.ts +2 -1
- package/dist/esm/services/authProvider/index.js +4 -1
- package/dist/esm/services/authProvider/subrequest.js +2 -0
- package/dist/esm/services/documentStore/unversioned/dynamodb.d.ts +6 -0
- package/dist/esm/services/documentStore/unversioned/dynamodb.js +146 -113
- package/dist/esm/services/documentStore/unversioned/file-system.d.ts +4 -0
- package/dist/esm/services/documentStore/unversioned/file-system.js +19 -7
- package/dist/esm/services/documentStore/versioned/dynamodb.d.ts +2 -0
- package/dist/esm/services/documentStore/versioned/dynamodb.js +125 -123
- package/dist/esm/services/fileServer/s3FileServer.d.ts +1 -1
- package/dist/esm/services/fileServer/s3FileServer.js +5 -2
- package/dist/esm/services/launchParams/verifier.js +3 -1
- package/dist/esm/services/searchProvider/index.d.ts +26 -7
- package/dist/esm/services/searchProvider/memorySearchTheBadWay.d.ts +5 -3
- package/dist/esm/services/searchProvider/memorySearchTheBadWay.js +97 -41
- package/dist/esm/services/searchProvider/openSearch.d.ts +2 -2
- package/dist/esm/services/searchProvider/openSearch.js +13 -9
- package/dist/esm/tsconfig.without-specs.esm.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/dist/cjs/coolFile.d.ts +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { resolveConfigValue } from '../config/resolveConfigValue';
|
|
2
|
+
import { once } from '../misc/helpers';
|
|
2
3
|
import { apiTextResponse } from '../routing';
|
|
3
4
|
/**
|
|
4
5
|
* Creates response middleware that adds CORS headers to responses from approved hosts.
|
|
@@ -11,14 +12,15 @@ import { apiTextResponse } from '../routing';
|
|
|
11
12
|
* }),
|
|
12
13
|
*/
|
|
13
14
|
export const createLambdaCorsResponseMiddleware = (config) => () => (responsePromise, { request }) => {
|
|
15
|
+
const getAllowedHostRegex = once(() => resolveConfigValue(config.corsAllowedHostRegex));
|
|
14
16
|
const cors = async () => {
|
|
15
|
-
const allowedHost = await
|
|
17
|
+
const allowedHost = await getAllowedHostRegex();
|
|
16
18
|
if (request.headers.origin && request.headers.origin !== 'null' && new URL(request.headers.origin).hostname.match(new RegExp(allowedHost))) {
|
|
17
19
|
return {
|
|
18
20
|
'Access-Control-Allow-Origin': request.headers.origin,
|
|
19
21
|
'Access-Control-Allow-Credentials': 'true',
|
|
20
22
|
'Access-Control-Allow-Headers': 'content-type, x-request-id',
|
|
21
|
-
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
|
|
23
|
+
'Access-Control-Allow-Methods': 'POST, GET, PUT, PATCH, DELETE, OPTIONS',
|
|
22
24
|
};
|
|
23
25
|
}
|
|
24
26
|
};
|
|
@@ -2,6 +2,7 @@ import { chunk, isEqual } from 'lodash';
|
|
|
2
2
|
import queryString from 'query-string';
|
|
3
3
|
import { resolveConfigValue } from '../../config';
|
|
4
4
|
import { ifDefined, isDefined } from '../../guards';
|
|
5
|
+
import { once } from '../../misc/helpers';
|
|
5
6
|
import { METHOD } from '../../routing';
|
|
6
7
|
import { Level } from '../logger';
|
|
7
8
|
class ApiError extends Error {
|
|
@@ -12,15 +13,15 @@ class ApiError extends Error {
|
|
|
12
13
|
}
|
|
13
14
|
export const accountsGateway = (initializer) => (configProvider) => {
|
|
14
15
|
const config = configProvider[ifDefined(initializer.configSpace, 'accounts')];
|
|
15
|
-
const accountsBase = resolveConfigValue(config.accountsBase);
|
|
16
|
-
const accountsAuthToken = resolveConfigValue(config.accountsAuthToken);
|
|
16
|
+
const accountsBase = once(() => resolveConfigValue(config.accountsBase));
|
|
17
|
+
const accountsAuthToken = once(() => resolveConfigValue(config.accountsAuthToken));
|
|
17
18
|
return ({ logger }) => {
|
|
18
19
|
const request = async (method, path, options, statuses = [200, 201]) => {
|
|
19
|
-
const host = (await accountsBase).replace(/\/+$/, '');
|
|
20
|
+
const host = (await accountsBase()).replace(/\/+$/, '');
|
|
20
21
|
const url = `${host}/api/${path}`;
|
|
21
22
|
const config = {
|
|
22
23
|
headers: {
|
|
23
|
-
Authorization: `Bearer ${options.token || await accountsAuthToken}`,
|
|
24
|
+
Authorization: `Bearer ${options.token || await accountsAuthToken()}`,
|
|
24
25
|
},
|
|
25
26
|
method,
|
|
26
27
|
};
|
|
@@ -11,6 +11,7 @@ export const decryptionAuthProvider = (initializer) => (configProvider) => {
|
|
|
11
11
|
const signaturePublicKey = once(() => resolveConfigValue(config.signaturePublicKey));
|
|
12
12
|
return ({ request, logger }) => {
|
|
13
13
|
let user;
|
|
14
|
+
const getAuthToken = async () => getAuthTokenOrCookie(request, await cookieName())[0];
|
|
14
15
|
const getAuthorizedFetchConfig = async () => {
|
|
15
16
|
const [token, headers] = getAuthTokenOrCookie(request, await cookieName());
|
|
16
17
|
if (!token) {
|
|
@@ -19,7 +20,7 @@ export const decryptionAuthProvider = (initializer) => (configProvider) => {
|
|
|
19
20
|
return { headers };
|
|
20
21
|
};
|
|
21
22
|
const getPayload = async (tokenString) => {
|
|
22
|
-
const token = tokenString !== null && tokenString !== void 0 ? tokenString :
|
|
23
|
+
const token = tokenString !== null && tokenString !== void 0 ? tokenString : await getAuthToken();
|
|
23
24
|
if (!token) {
|
|
24
25
|
return undefined;
|
|
25
26
|
}
|
|
@@ -40,6 +41,7 @@ export const decryptionAuthProvider = (initializer) => (configProvider) => {
|
|
|
40
41
|
return undefined;
|
|
41
42
|
};
|
|
42
43
|
return {
|
|
44
|
+
getAuthToken,
|
|
43
45
|
getAuthorizedFetchConfig,
|
|
44
46
|
getTokenExpiration: async (tokenString) => {
|
|
45
47
|
var _a;
|
|
@@ -37,6 +37,7 @@ export interface ApiUser extends TokenUser {
|
|
|
37
37
|
}
|
|
38
38
|
export declare type User = TokenUser | ApiUser;
|
|
39
39
|
export declare type AuthProvider = {
|
|
40
|
+
getAuthToken: () => Promise<string | null>;
|
|
40
41
|
getUser: () => Promise<User | undefined>;
|
|
41
42
|
/**
|
|
42
43
|
* gets second argument for `fetch` that has authentication token or cookie
|
|
@@ -58,4 +59,4 @@ export declare const getAuthTokenOrCookie: (request: CookieAuthProviderRequest,
|
|
|
58
59
|
Authorization: string;
|
|
59
60
|
}] | [string, {
|
|
60
61
|
cookie: string;
|
|
61
|
-
}];
|
|
62
|
+
}] | [null, {}];
|
|
@@ -2,6 +2,7 @@ import cookie from 'cookie';
|
|
|
2
2
|
import { tuple } from '../../misc/helpers';
|
|
3
3
|
import { getHeader } from '../../routing/helpers';
|
|
4
4
|
export const stubAuthProvider = (user) => ({
|
|
5
|
+
getAuthToken: () => Promise.resolve('authToken'),
|
|
5
6
|
getUser: () => Promise.resolve(user),
|
|
6
7
|
getAuthorizedFetchConfig: () => Promise.resolve(user ? { headers: { Authorization: user.uuid } } : {})
|
|
7
8
|
});
|
|
@@ -14,5 +15,7 @@ export const getAuthTokenOrCookie = (request, cookieName, queryKey = 'auth') =>
|
|
|
14
15
|
? tuple(authParam, { Authorization: `Bearer ${authParam}` })
|
|
15
16
|
: authHeader && authHeader.length >= 8 && authHeader.startsWith('Bearer ')
|
|
16
17
|
? tuple(authHeader.slice(7), { Authorization: authHeader })
|
|
17
|
-
:
|
|
18
|
+
: cookieValue
|
|
19
|
+
? tuple(cookieValue, { cookie: cookie.serialize(cookieName, cookieValue) })
|
|
20
|
+
: tuple(null, {});
|
|
18
21
|
};
|
|
@@ -9,6 +9,7 @@ export const subrequestAuthProvider = (initializer) => (configProvider) => {
|
|
|
9
9
|
const accountsBase = once(() => resolveConfigValue(config.accountsBase));
|
|
10
10
|
return ({ request, logger }) => {
|
|
11
11
|
let user;
|
|
12
|
+
const getAuthToken = async () => getAuthTokenOrCookie(request, await cookieName())[0];
|
|
12
13
|
const getAuthorizedFetchConfig = async () => {
|
|
13
14
|
const [token, headers] = getAuthTokenOrCookie(request, await cookieName());
|
|
14
15
|
if (!token) {
|
|
@@ -31,6 +32,7 @@ export const subrequestAuthProvider = (initializer) => (configProvider) => {
|
|
|
31
32
|
return user;
|
|
32
33
|
};
|
|
33
34
|
return {
|
|
35
|
+
getAuthToken,
|
|
34
36
|
getAuthorizedFetchConfig,
|
|
35
37
|
getUser: async () => {
|
|
36
38
|
if (!user) {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { DynamoDB } from '@aws-sdk/client-dynamodb';
|
|
1
2
|
import { Config, TDocument } from '..';
|
|
2
3
|
import { ConfigProviderForConfig } from '../../../config';
|
|
3
4
|
interface Initializer<C> {
|
|
4
5
|
configSpace?: C;
|
|
6
|
+
dynamoClient?: DynamoDB;
|
|
5
7
|
}
|
|
6
8
|
export declare const dynamoUnversionedDocumentStore: <C extends string = "dynamodb">(initializer?: Initializer<C> | undefined) => <T extends TDocument<T>>() => (configProvider: { [key in C]: {
|
|
7
9
|
tableName: import("../../../config").ConfigValueProvider<string>;
|
|
@@ -9,6 +11,10 @@ export declare const dynamoUnversionedDocumentStore: <C extends string = "dynamo
|
|
|
9
11
|
afterWrite?: ((item: T) => void | Promise<void>) | undefined;
|
|
10
12
|
} | undefined) => {
|
|
11
13
|
loadAllDocumentsTheBadWay: () => Promise<T[]>;
|
|
14
|
+
getItemsByField: (key: keyof T, value: T[K], pageKey?: string | undefined) => Promise<{
|
|
15
|
+
items: T[];
|
|
16
|
+
nextPageToken: string | undefined;
|
|
17
|
+
}>;
|
|
12
18
|
batchGetItem: (ids: T[K][]) => Promise<T[]>;
|
|
13
19
|
getItem: (id: T[K]) => Promise<T | undefined>;
|
|
14
20
|
incrementItemAttribute: (id: T[K], attribute: keyof T) => Promise<number>;
|
|
@@ -1,129 +1,162 @@
|
|
|
1
|
-
import { BatchGetItemCommand, DynamoDB, GetItemCommand, PutItemCommand, ScanCommand, UpdateItemCommand } from '@aws-sdk/client-dynamodb';
|
|
1
|
+
import { BatchGetItemCommand, DynamoDB, GetItemCommand, PutItemCommand, QueryCommand, ScanCommand, UpdateItemCommand } from '@aws-sdk/client-dynamodb';
|
|
2
2
|
import { once } from '../../..';
|
|
3
3
|
import { resolveConfigValue } from '../../../config';
|
|
4
4
|
import { NotFoundError } from '../../../errors';
|
|
5
5
|
import { ifDefined } from '../../../guards';
|
|
6
6
|
import { decodeDynamoDocument, encodeDynamoAttribute, encodeDynamoDocument } from '../dynamoEncoding';
|
|
7
|
-
const
|
|
8
|
-
export const dynamoUnversionedDocumentStore = (initializer) => () => (configProvider) => (_, hashKey, options) => {
|
|
7
|
+
export const dynamoUnversionedDocumentStore = (initializer) => {
|
|
9
8
|
const init = ifDefined(initializer, {});
|
|
10
|
-
const
|
|
11
|
-
return {
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
const dynamodb = once(() => { var _a; return (_a = init.dynamoClient) !== null && _a !== void 0 ? _a : new DynamoDB({ apiVersion: '2012-08-10' }); });
|
|
10
|
+
return () => (configProvider) => (_, hashKey, options) => {
|
|
11
|
+
const tableName = once(() => resolveConfigValue(configProvider[ifDefined(init.configSpace, 'dynamodb')].tableName));
|
|
12
|
+
return {
|
|
13
|
+
loadAllDocumentsTheBadWay: async () => {
|
|
14
|
+
const loadAllResults = async (ExclusiveStartKey) => {
|
|
15
|
+
var _a, _b;
|
|
16
|
+
const cmd = new ScanCommand({ TableName: await tableName(), ExclusiveStartKey });
|
|
17
|
+
const result = await dynamodb().send(cmd);
|
|
18
|
+
const resultItems = (_b = (_a = result.Items) === null || _a === void 0 ? void 0 : _a.map((item) => decodeDynamoDocument(item))) !== null && _b !== void 0 ? _b : [];
|
|
19
|
+
if (result.LastEvaluatedKey) {
|
|
20
|
+
return [...resultItems, ...await loadAllResults(result.LastEvaluatedKey)];
|
|
21
|
+
}
|
|
22
|
+
return resultItems;
|
|
23
|
+
};
|
|
24
|
+
return loadAllResults();
|
|
25
|
+
},
|
|
26
|
+
/*
|
|
27
|
+
* requires that a global secondary index exist on the table
|
|
28
|
+
*/
|
|
29
|
+
getItemsByField: async (key, value, pageKey) => {
|
|
14
30
|
var _a, _b;
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
RequestItems: requestItems !== null && requestItems !== void 0 ? requestItems : { [table]: { Keys: ids.map((id) => ({ [key]: encodeDynamoAttribute(id) })) } },
|
|
31
|
+
const ExclusiveStartKey = pageKey
|
|
32
|
+
? JSON.parse(Buffer.from(pageKey, 'base64').toString('utf-8'))
|
|
33
|
+
: undefined;
|
|
34
|
+
const table = await tableName();
|
|
35
|
+
const cmd = new QueryCommand({
|
|
36
|
+
TableName: table,
|
|
37
|
+
IndexName: key.toString(),
|
|
38
|
+
ExclusiveStartKey,
|
|
39
|
+
KeyConditionExpression: '#hk = :hkv',
|
|
40
|
+
ExpressionAttributeValues: {
|
|
41
|
+
':hkv': encodeDynamoAttribute(value),
|
|
42
|
+
},
|
|
43
|
+
ExpressionAttributeNames: {
|
|
44
|
+
'#hk': key.toString(),
|
|
45
|
+
},
|
|
31
46
|
});
|
|
32
47
|
const response = await dynamodb().send(cmd);
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
return
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
48
|
+
const items = (_b = (_a = response.Items) === null || _a === void 0 ? void 0 : _a.map((item) => decodeDynamoDocument(item))) !== null && _b !== void 0 ? _b : [];
|
|
49
|
+
const nextPageToken = response.LastEvaluatedKey;
|
|
50
|
+
return {
|
|
51
|
+
items,
|
|
52
|
+
nextPageToken: nextPageToken
|
|
53
|
+
? Buffer.from(JSON.stringify(nextPageToken)).toString('base64')
|
|
54
|
+
: undefined,
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
batchGetItem: async (ids) => {
|
|
58
|
+
const table = await tableName();
|
|
59
|
+
const key = hashKey.toString();
|
|
60
|
+
const getBatches = async (requestItems) => {
|
|
61
|
+
const cmd = new BatchGetItemCommand({
|
|
62
|
+
RequestItems: requestItems !== null && requestItems !== void 0 ? requestItems : { [table]: { Keys: ids.map((id) => ({ [key]: encodeDynamoAttribute(id) })) } },
|
|
63
|
+
});
|
|
64
|
+
const response = await dynamodb().send(cmd);
|
|
65
|
+
const currentResponses = response.Responses ?
|
|
66
|
+
response.Responses[table].map(response => decodeDynamoDocument(response)) : [];
|
|
67
|
+
return currentResponses.concat(response.UnprocessedKeys ? await getBatches(response.UnprocessedKeys) : []);
|
|
68
|
+
};
|
|
69
|
+
return getBatches();
|
|
70
|
+
},
|
|
71
|
+
getItem: async (id) => {
|
|
72
|
+
const cmd = new GetItemCommand({
|
|
73
|
+
Key: { [hashKey.toString()]: encodeDynamoAttribute(id) },
|
|
74
|
+
TableName: await tableName(),
|
|
75
|
+
});
|
|
76
|
+
return dynamodb().send(cmd).then(result => result.Item ? decodeDynamoDocument(result.Item) : undefined);
|
|
77
|
+
},
|
|
78
|
+
/* atomically increments the given item attribute by 1 */
|
|
79
|
+
incrementItemAttribute: async (id, attribute) => {
|
|
80
|
+
const key = hashKey.toString();
|
|
81
|
+
const field = attribute.toString();
|
|
82
|
+
const cmd = new UpdateItemCommand({
|
|
83
|
+
Key: { [key]: encodeDynamoAttribute(id) },
|
|
84
|
+
TableName: await tableName(),
|
|
85
|
+
UpdateExpression: 'ADD #f :one',
|
|
86
|
+
ConditionExpression: 'attribute_exists(#k)',
|
|
87
|
+
ExpressionAttributeNames: { '#k': hashKey.toString(), '#f': field },
|
|
88
|
+
ExpressionAttributeValues: { ':one': { N: '1' } },
|
|
89
|
+
ReturnValues: 'ALL_NEW',
|
|
90
|
+
});
|
|
91
|
+
return dynamodb().send(cmd).then(async (item) => {
|
|
92
|
+
var _a, _b, _c;
|
|
93
|
+
const result = (_b = (_a = item.Attributes) === null || _a === void 0 ? void 0 : _a[field]) === null || _b === void 0 ? void 0 : _b['N'];
|
|
94
|
+
if (!item.Attributes) {
|
|
95
|
+
throw new NotFoundError(`Item with ${key} "${id}" does not exist`);
|
|
96
|
+
}
|
|
97
|
+
if (!result) {
|
|
98
|
+
throw new NotFoundError(`Item with ${key} "${id}" did not produce field "${field}"`);
|
|
99
|
+
}
|
|
100
|
+
const updatedDoc = decodeDynamoDocument(item.Attributes);
|
|
101
|
+
await ((_c = options === null || options === void 0 ? void 0 : options.afterWrite) === null || _c === void 0 ? void 0 : _c.call(options, updatedDoc));
|
|
102
|
+
return parseFloat(result);
|
|
103
|
+
}).catch((error) => {
|
|
104
|
+
throw error.name === 'ConditionalCheckFailedException' ?
|
|
105
|
+
new NotFoundError(`Item with ${key} "${id}" does not exist`) : error;
|
|
106
|
+
});
|
|
107
|
+
},
|
|
108
|
+
/* replaces only specified attributes with the given data */
|
|
109
|
+
patchItem: async (item) => {
|
|
110
|
+
const id = item[hashKey];
|
|
111
|
+
const key = hashKey.toString();
|
|
112
|
+
if (!id) {
|
|
113
|
+
throw new Error(`Key attribute "${key}" is required for patchItem`);
|
|
64
114
|
}
|
|
65
|
-
|
|
66
|
-
|
|
115
|
+
const entries = Object.entries(item).filter(([field]) => field !== key);
|
|
116
|
+
if (entries.length === 0) {
|
|
117
|
+
throw new Error('No attributes to update');
|
|
67
118
|
}
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
ExpressionAttributeNames: expressionAttributeNames,
|
|
101
|
-
ExpressionAttributeValues: expressionAttributeValues,
|
|
102
|
-
ReturnValues: 'ALL_NEW',
|
|
103
|
-
});
|
|
104
|
-
return dynamodb().send(cmd).then(async (item) => {
|
|
119
|
+
const updates = [];
|
|
120
|
+
const expressionAttributeNames = { '#k': key };
|
|
121
|
+
const expressionAttributeValues = {};
|
|
122
|
+
entries.forEach(([field, value], index) => {
|
|
123
|
+
updates.push(`#f${index} = :f${index}`);
|
|
124
|
+
expressionAttributeNames[`#f${index}`] = field;
|
|
125
|
+
expressionAttributeValues[`:f${index}`] = encodeDynamoAttribute(value);
|
|
126
|
+
});
|
|
127
|
+
const cmd = new UpdateItemCommand({
|
|
128
|
+
Key: { [key]: encodeDynamoAttribute(id) },
|
|
129
|
+
TableName: await tableName(),
|
|
130
|
+
UpdateExpression: `SET ${updates.join(', ')}`,
|
|
131
|
+
ConditionExpression: 'attribute_exists(#k)',
|
|
132
|
+
ExpressionAttributeNames: expressionAttributeNames,
|
|
133
|
+
ExpressionAttributeValues: expressionAttributeValues,
|
|
134
|
+
ReturnValues: 'ALL_NEW',
|
|
135
|
+
});
|
|
136
|
+
return dynamodb().send(cmd).then(async (item) => {
|
|
137
|
+
var _a;
|
|
138
|
+
if (!item.Attributes) {
|
|
139
|
+
throw new NotFoundError(`Item with ${key} "${id}" does not exist`);
|
|
140
|
+
}
|
|
141
|
+
const updatedDoc = decodeDynamoDocument(item.Attributes);
|
|
142
|
+
await ((_a = options === null || options === void 0 ? void 0 : options.afterWrite) === null || _a === void 0 ? void 0 : _a.call(options, updatedDoc));
|
|
143
|
+
return updatedDoc;
|
|
144
|
+
}).catch((error) => {
|
|
145
|
+
throw error.name === 'ConditionalCheckFailedException' ?
|
|
146
|
+
new NotFoundError(`Item with ${key} "${id}" does not exist`) : error;
|
|
147
|
+
});
|
|
148
|
+
},
|
|
149
|
+
/* replaces the entire document with the given data */
|
|
150
|
+
putItem: async (item) => {
|
|
105
151
|
var _a;
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
152
|
+
const cmd = new PutItemCommand({
|
|
153
|
+
TableName: await tableName(),
|
|
154
|
+
Item: encodeDynamoDocument(item),
|
|
155
|
+
});
|
|
156
|
+
const updatedDoc = await dynamodb().send(cmd).then(() => item);
|
|
110
157
|
await ((_a = options === null || options === void 0 ? void 0 : options.afterWrite) === null || _a === void 0 ? void 0 : _a.call(options, updatedDoc));
|
|
111
158
|
return updatedDoc;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
new NotFoundError(`Item with ${key} "${id}" does not exist`) : error;
|
|
115
|
-
});
|
|
116
|
-
},
|
|
117
|
-
/* replaces the entire document with the given data */
|
|
118
|
-
putItem: async (item) => {
|
|
119
|
-
var _a;
|
|
120
|
-
const cmd = new PutItemCommand({
|
|
121
|
-
TableName: await tableName(),
|
|
122
|
-
Item: encodeDynamoDocument(item),
|
|
123
|
-
});
|
|
124
|
-
const updatedDoc = await dynamodb().send(cmd).then(() => item);
|
|
125
|
-
await ((_a = options === null || options === void 0 ? void 0 : options.afterWrite) === null || _a === void 0 ? void 0 : _a.call(options, updatedDoc));
|
|
126
|
-
return updatedDoc;
|
|
127
|
-
},
|
|
159
|
+
},
|
|
160
|
+
};
|
|
128
161
|
};
|
|
129
162
|
};
|
|
@@ -9,6 +9,10 @@ export declare const fileSystemUnversionedDocumentStore: <C extends string = "fi
|
|
|
9
9
|
tableName: import("../../../config").ConfigValueProvider<string>;
|
|
10
10
|
}; }) => <K extends keyof T>(_: {}, hashKey: K) => {
|
|
11
11
|
loadAllDocumentsTheBadWay: () => Promise<T[]>;
|
|
12
|
+
getItemsByField: (key: keyof T, value: T[K], pageKey?: string | undefined) => Promise<{
|
|
13
|
+
items: T[];
|
|
14
|
+
nextPageToken: string | undefined;
|
|
15
|
+
}>;
|
|
12
16
|
batchGetItem: (ids: T[K][]) => Promise<Exclude<Awaited<T>, undefined>[]>;
|
|
13
17
|
getItem: (id: T[K]) => Promise<T | undefined>;
|
|
14
18
|
incrementItemAttribute: (id: T[K], attribute: keyof T) => Promise<number>;
|
|
@@ -31,14 +31,26 @@ export const fileSystemUnversionedDocumentStore = (initializer) => () => (config
|
|
|
31
31
|
});
|
|
32
32
|
});
|
|
33
33
|
};
|
|
34
|
+
const loadAllDocumentsTheBadWay = async () => {
|
|
35
|
+
const path = await tablePath;
|
|
36
|
+
await mkTableDir;
|
|
37
|
+
return new Promise((resolve, reject) => readdir(path, (err, files) => err ?
|
|
38
|
+
reject(err) :
|
|
39
|
+
Promise.all(files.map((file) => load(file)))
|
|
40
|
+
.then((allData) => resolve(allData.filter(isDefined)), (err) => reject(err))));
|
|
41
|
+
};
|
|
34
42
|
return {
|
|
35
|
-
loadAllDocumentsTheBadWay
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
43
|
+
loadAllDocumentsTheBadWay,
|
|
44
|
+
getItemsByField: async (key, value, pageKey) => {
|
|
45
|
+
const pageSize = 10;
|
|
46
|
+
const items = await loadAllDocumentsTheBadWay();
|
|
47
|
+
const filteredItems = items.filter((item) => item[key] === value);
|
|
48
|
+
const startIndex = pageKey ? parseInt(pageKey, 10) : 0;
|
|
49
|
+
const paginatedItems = filteredItems.slice(startIndex, startIndex + pageSize);
|
|
50
|
+
const nextPageToken = startIndex + pageSize < filteredItems.length
|
|
51
|
+
? (startIndex + pageSize).toString()
|
|
52
|
+
: undefined;
|
|
53
|
+
return { items: paginatedItems, nextPageToken };
|
|
42
54
|
},
|
|
43
55
|
batchGetItem: async (ids) => {
|
|
44
56
|
const items = await Promise.all(ids.map((id) => load(hashFilename(id))));
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { DynamoDB } from '@aws-sdk/client-dynamodb';
|
|
1
2
|
import { Config } from '..';
|
|
2
3
|
import { ConfigProviderForConfig } from '../../../config';
|
|
3
4
|
import { VersionedDocumentAuthor, VersionedTDocument } from '.';
|
|
4
5
|
interface Initializer<C> {
|
|
5
6
|
configSpace?: C;
|
|
7
|
+
dynamoClient?: DynamoDB;
|
|
6
8
|
}
|
|
7
9
|
export declare const dynamoVersionedDocumentStore: <C extends string = "dynamodb">(initializer?: Initializer<C> | undefined) => <T extends VersionedTDocument<T>>() => (configProvider: { [key in C]: {
|
|
8
10
|
tableName: import("../../../config").ConfigValueProvider<string>;
|