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