@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
@@ -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 resolveConfigValue(config.corsAllowedHostRegex);
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 : getAuthTokenOrCookie(request, await cookieName())[0];
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
- : tuple(cookieValue, { cookie: cookie.serialize(cookieName, cookieValue) });
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 dynamodb = once(() => new DynamoDB({ apiVersion: '2012-08-10' }));
8
- export const dynamoUnversionedDocumentStore = (initializer) => () => (configProvider) => (_, hashKey, options) => {
7
+ export const dynamoUnversionedDocumentStore = (initializer) => {
9
8
  const init = ifDefined(initializer, {});
10
- const tableName = once(() => resolveConfigValue(configProvider[ifDefined(init.configSpace, 'dynamodb')].tableName));
11
- return {
12
- loadAllDocumentsTheBadWay: async () => {
13
- const loadAllResults = async (ExclusiveStartKey) => {
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 cmd = new ScanCommand({ TableName: await tableName(), ExclusiveStartKey });
16
- const result = await dynamodb().send(cmd);
17
- const resultItems = (_b = (_a = result.Items) === null || _a === void 0 ? void 0 : _a.map((item) => decodeDynamoDocument(item))) !== null && _b !== void 0 ? _b : [];
18
- if (result.LastEvaluatedKey) {
19
- return [...resultItems, ...await loadAllResults(result.LastEvaluatedKey)];
20
- }
21
- return resultItems;
22
- };
23
- return loadAllResults();
24
- },
25
- batchGetItem: async (ids) => {
26
- const table = await tableName();
27
- const key = hashKey.toString();
28
- const getBatches = async (requestItems) => {
29
- const cmd = new BatchGetItemCommand({
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 currentResponses = response.Responses ?
34
- response.Responses[table].map(response => decodeDynamoDocument(response)) : [];
35
- return currentResponses.concat(response.UnprocessedKeys ? await getBatches(response.UnprocessedKeys) : []);
36
- };
37
- return getBatches();
38
- },
39
- getItem: async (id) => {
40
- const cmd = new GetItemCommand({
41
- Key: { [hashKey.toString()]: encodeDynamoAttribute(id) },
42
- TableName: await tableName(),
43
- });
44
- return dynamodb().send(cmd).then(result => result.Item ? decodeDynamoDocument(result.Item) : undefined);
45
- },
46
- /* atomically increments the given item attribute by 1 */
47
- incrementItemAttribute: async (id, attribute) => {
48
- const key = hashKey.toString();
49
- const field = attribute.toString();
50
- const cmd = new UpdateItemCommand({
51
- Key: { [key]: encodeDynamoAttribute(id) },
52
- TableName: await tableName(),
53
- UpdateExpression: 'ADD #f :one',
54
- ConditionExpression: 'attribute_exists(#k)',
55
- ExpressionAttributeNames: { '#k': hashKey.toString(), '#f': field },
56
- ExpressionAttributeValues: { ':one': { N: '1' } },
57
- ReturnValues: 'ALL_NEW',
58
- });
59
- return dynamodb().send(cmd).then(async (item) => {
60
- var _a, _b, _c;
61
- const result = (_b = (_a = item.Attributes) === null || _a === void 0 ? void 0 : _a[field]) === null || _b === void 0 ? void 0 : _b['N'];
62
- if (!item.Attributes) {
63
- throw new NotFoundError(`Item with ${key} "${id}" does not exist`);
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
- if (!result) {
66
- throw new NotFoundError(`Item with ${key} "${id}" did not produce field "${field}"`);
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 updatedDoc = decodeDynamoDocument(item.Attributes);
69
- await ((_c = options === null || options === void 0 ? void 0 : options.afterWrite) === null || _c === void 0 ? void 0 : _c.call(options, updatedDoc));
70
- return parseFloat(result);
71
- }).catch((error) => {
72
- throw error.name === 'ConditionalCheckFailedException' ?
73
- new NotFoundError(`Item with ${key} "${id}" does not exist`) : error;
74
- });
75
- },
76
- /* replaces only specified attributes with the given data */
77
- patchItem: async (item) => {
78
- const id = item[hashKey];
79
- const key = hashKey.toString();
80
- if (!id) {
81
- throw new Error(`Key attribute "${key}" is required for patchItem`);
82
- }
83
- const entries = Object.entries(item).filter(([field]) => field !== key);
84
- if (entries.length === 0) {
85
- throw new Error('No attributes to update');
86
- }
87
- const updates = [];
88
- const expressionAttributeNames = { '#k': key };
89
- const expressionAttributeValues = {};
90
- entries.forEach(([field, value], index) => {
91
- updates.push(`#f${index} = :f${index}`);
92
- expressionAttributeNames[`#f${index}`] = field;
93
- expressionAttributeValues[`:f${index}`] = encodeDynamoAttribute(value);
94
- });
95
- const cmd = new UpdateItemCommand({
96
- Key: { [key]: encodeDynamoAttribute(id) },
97
- TableName: await tableName(),
98
- UpdateExpression: `SET ${updates.join(', ')}`,
99
- ConditionExpression: 'attribute_exists(#k)',
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
- if (!item.Attributes) {
107
- throw new NotFoundError(`Item with ${key} "${id}" does not exist`);
108
- }
109
- const updatedDoc = decodeDynamoDocument(item.Attributes);
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
- }).catch((error) => {
113
- throw error.name === 'ConditionalCheckFailedException' ?
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: async () => {
36
- const path = await tablePath;
37
- await mkTableDir;
38
- return new Promise((resolve, reject) => readdir(path, (err, files) => err ?
39
- reject(err) :
40
- Promise.all(files.map((file) => load(file)))
41
- .then((allData) => resolve(allData.filter(isDefined)), (err) => reject(err))));
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>;