@openstax/ts-utils 1.14.0 → 1.15.1

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.
@@ -1,3 +1,4 @@
1
+ import cookie from 'cookie';
1
2
  import { once } from '../..';
2
3
  import { resolveConfigValue } from '../../config';
3
4
  import { ifDefined } from '../../guards';
@@ -16,10 +17,12 @@ export const subrequestAuthProvider = (initializer) => (configProvider) => {
16
17
  return { headers };
17
18
  };
18
19
  const loadUser = async () => {
19
- const [token, headers] = getAuthTokenOrCookie(request, await cookieName());
20
+ const resolvedCookeiName = await cookieName();
21
+ const [token] = getAuthTokenOrCookie(request, resolvedCookeiName);
20
22
  if (!token) {
21
23
  return undefined;
22
24
  }
25
+ const headers = { cookie: cookie.serialize(resolvedCookeiName, token) };
23
26
  return initializer.fetch((await accountsBase()).replace(/\/+$/, '') + '/api/user', { headers })
24
27
  .then(response => response.json());
25
28
  };
@@ -9,6 +9,8 @@ export declare const dynamoUnversionedDocumentStore: <C extends string = "dynamo
9
9
  loadAllDocumentsTheBadWay: () => Promise<T[]>;
10
10
  batchGetItem: (ids: T[K][]) => Promise<T[]>;
11
11
  getItem: (id: T[K]) => Promise<T | undefined>;
12
+ incrementItemAttribute: (id: T[K], attribute: keyof T) => Promise<number>;
13
+ patchItem: (item: Partial<T>) => Promise<T>;
12
14
  putItem: (item: T) => Promise<T>;
13
15
  };
14
16
  export {};
@@ -1,4 +1,4 @@
1
- import { BatchGetItemCommand, DynamoDB, GetItemCommand, PutItemCommand, ScanCommand } from '@aws-sdk/client-dynamodb';
1
+ import { BatchGetItemCommand, DynamoDB, GetItemCommand, PutItemCommand, ScanCommand, UpdateItemCommand } from '@aws-sdk/client-dynamodb';
2
2
  import { once } from '../../..';
3
3
  import { resolveConfigValue } from '../../../config';
4
4
  import { ifDefined } from '../../../guards';
@@ -42,7 +42,64 @@ export const dynamoUnversionedDocumentStore = (initializer) => () => (configProv
42
42
  });
43
43
  return dynamodb().send(cmd).then(result => result.Item ? decodeDynamoDocument(result.Item) : undefined);
44
44
  },
45
- /* saves a new version of a document with the given data */
45
+ /* atomically increments the given item attribute by 1 */
46
+ incrementItemAttribute: async (id, attribute) => {
47
+ const key = hashKey.toString();
48
+ const field = attribute.toString();
49
+ const cmd = new UpdateItemCommand({
50
+ Key: { [key]: encodeDynamoAttribute(id) },
51
+ TableName: await tableName(),
52
+ UpdateExpression: 'ADD #f :one',
53
+ ConditionExpression: 'attribute_exists(#k)',
54
+ ExpressionAttributeNames: { '#k': hashKey.toString(), '#f': field },
55
+ ExpressionAttributeValues: { ':one': { N: '1' } },
56
+ ReturnValues: 'UPDATED_NEW',
57
+ });
58
+ return dynamodb().send(cmd).then((item) => {
59
+ var _a;
60
+ const result = (_a = item.Attributes) === null || _a === void 0 ? void 0 : _a[field]['N'];
61
+ if (!result) {
62
+ throw new Error(`Item with ${key} "${id}" does not exist`);
63
+ }
64
+ return parseFloat(result);
65
+ });
66
+ },
67
+ /* replaces only specified attributes with the given data */
68
+ patchItem: async (item) => {
69
+ const id = item[hashKey];
70
+ const key = hashKey.toString();
71
+ if (!id) {
72
+ throw new Error(`Key attribute "${key}" is required for patchItem`);
73
+ }
74
+ const entries = Object.entries(item).filter(([field]) => field !== key);
75
+ if (entries.length === 0) {
76
+ throw new Error('No attributes to update');
77
+ }
78
+ const updates = [];
79
+ const expressionAttributeNames = { '#k': key };
80
+ const expressionAttributeValues = {};
81
+ entries.forEach(([field, value], index) => {
82
+ updates.push(`#f${index} = :f${index}`);
83
+ expressionAttributeNames[`#f${index}`] = field;
84
+ expressionAttributeValues[`:f${index}`] = encodeDynamoAttribute(value);
85
+ });
86
+ const cmd = new UpdateItemCommand({
87
+ Key: { [key]: encodeDynamoAttribute(id) },
88
+ TableName: await tableName(),
89
+ UpdateExpression: `SET ${updates.join(', ')}`,
90
+ ConditionExpression: 'attribute_exists(#k)',
91
+ ExpressionAttributeNames: expressionAttributeNames,
92
+ ExpressionAttributeValues: expressionAttributeValues,
93
+ ReturnValues: 'ALL_NEW',
94
+ });
95
+ return dynamodb().send(cmd).then((item) => {
96
+ if (!item.Attributes) {
97
+ throw new Error(`Item with ${key} "${id}" does not exist`);
98
+ }
99
+ return decodeDynamoDocument(item.Attributes);
100
+ });
101
+ },
102
+ /* replaces the entire document with the given data */
46
103
  putItem: async (item) => {
47
104
  const cmd = new PutItemCommand({
48
105
  TableName: await tableName(),
@@ -11,6 +11,8 @@ export declare const fileSystemUnversionedDocumentStore: <C extends string = "fi
11
11
  loadAllDocumentsTheBadWay: () => Promise<T[]>;
12
12
  batchGetItem: (ids: T[K][]) => Promise<Exclude<Awaited<T>, undefined>[]>;
13
13
  getItem: (id: T[K]) => Promise<T | undefined>;
14
+ incrementItemAttribute: (id: T[K], attribute: keyof T) => Promise<number>;
15
+ patchItem: (item: Partial<T>) => Promise<T>;
14
16
  putItem: (item: T) => Promise<T>;
15
17
  };
16
18
  export {};
@@ -44,6 +44,41 @@ export const fileSystemUnversionedDocumentStore = (initializer) => () => (config
44
44
  return items.filter(isDefined);
45
45
  },
46
46
  getItem: (id) => load(hashFilename(id)),
47
+ incrementItemAttribute: async (id, attribute) => {
48
+ const filename = hashFilename(id);
49
+ const path = await filePath(filename);
50
+ await mkTableDir;
51
+ const data = await load(filename);
52
+ if (!data) {
53
+ throw new Error(`Item with ${hashKey.toString()} "${id}" does not exist`);
54
+ }
55
+ const newValue = typeof data[attribute] === 'number' ? data[attribute] + 1 : 1;
56
+ const newItem = { ...data, [hashKey]: id, [attribute]: newValue };
57
+ return new Promise((resolve, reject) => {
58
+ writeFile(path, JSON.stringify(newItem, null, 2), (err) => err ? reject(err) : resolve(newValue));
59
+ });
60
+ },
61
+ patchItem: async (item) => {
62
+ const id = item[hashKey];
63
+ if (!id) {
64
+ throw new Error(`Key attribute "${hashKey.toString()}" is required for patchItem`);
65
+ }
66
+ // This check is just to make this adapter consistent with the dynamo adapter
67
+ if (Object.keys(item).filter((key) => key !== hashKey.toString()).length === 0) {
68
+ throw new Error('No attributes to update');
69
+ }
70
+ const filename = hashFilename(id);
71
+ const path = await filePath(filename);
72
+ await mkTableDir;
73
+ const data = await load(filename);
74
+ if (!data) {
75
+ throw new Error(`Item with ${hashKey.toString()} "${id}" does not exist`);
76
+ }
77
+ const newItem = { ...data, ...item };
78
+ return new Promise((resolve, reject) => {
79
+ writeFile(path, JSON.stringify(newItem, null, 2), (err) => err ? reject(err) : resolve(newItem));
80
+ });
81
+ },
47
82
  putItem: async (item) => {
48
83
  const path = await filePath(hashFilename(item[hashKey]));
49
84
  await mkTableDir;
@@ -1,7 +1,8 @@
1
- import jwt from 'jsonwebtoken';
1
+ import jwt, { TokenExpiredError } from 'jsonwebtoken';
2
2
  import { JwksClient } from 'jwks-rsa';
3
3
  import { memoize } from '../..';
4
4
  import { resolveConfigValue } from '../../config';
5
+ import { SessionExpiredError } from '../../errors';
5
6
  import { ifDefined } from '../../guards';
6
7
  /**
7
8
  * Creates a class that can verify launch params
@@ -34,7 +35,10 @@ export const createLaunchVerifier = ({ configSpace, fetcher }) => (configProvide
34
35
  }
35
36
  };
36
37
  const verify = (token) => new Promise((resolve, reject) => jwt.verify(token, getKey, {}, (err, payload) => {
37
- if (err) {
38
+ if (err && err instanceof TokenExpiredError) {
39
+ reject(new SessionExpiredError());
40
+ }
41
+ else if (err) {
38
42
  reject(err);
39
43
  }
40
44
  else if (typeof payload !== 'object') {