@openstax/ts-utils 1.1.20 → 1.1.22

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/index.d.ts CHANGED
@@ -2,6 +2,7 @@ import { UnionToIntersection } from './types';
2
2
  export declare const getKeyValue: <K extends string>(key: K) => <O extends { [key in K]?: any; }>(obj: O) => O[K];
3
3
  export declare const getKeyValueOr: <K extends string, V, Od extends { [key in K]?: any; } = { [key_1 in K]?: any; }>(key: K, defaultValue: V) => <O extends Od extends undefined ? { [key_2 in K]?: any; } : Od>(obj: O) => V | NonNullable<O[K]>;
4
4
  export declare const putKeyValue: <K extends string>(key: K) => <O extends { [key in K]?: any; }>(obj: O, value: O[K]) => O;
5
+ export declare const coerceArray: <T>(thing: T | T[] | undefined) => T[];
5
6
  declare type FlowFn<A, R> = (...args: [A]) => R;
6
7
  declare type AnyFlowFn = FlowFn<any, any>;
7
8
  declare type FlowFnResult<F, A> = F extends FlowFn<A, infer R> ? R : never;
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.tuple = exports.merge = exports.getCommonProperties = exports.roundToPrecision = exports.memoize = exports.partitionSequence = exports.once = exports.hashValue = exports.mapFind = exports.fnIf = exports.flow = exports.putKeyValue = exports.getKeyValueOr = exports.getKeyValue = void 0;
6
+ exports.tuple = exports.merge = exports.getCommonProperties = exports.roundToPrecision = exports.memoize = exports.partitionSequence = exports.once = exports.hashValue = exports.mapFind = exports.fnIf = exports.flow = exports.coerceArray = exports.putKeyValue = exports.getKeyValueOr = exports.getKeyValue = void 0;
7
7
  const crypto_1 = require("crypto");
8
8
  const deep_equal_1 = __importDefault(require("deep-equal"));
9
9
  const guards_1 = require("./guards");
@@ -26,6 +26,8 @@ const getKeyValueOr = (key, defaultValue) => (obj) => obj[key] || defaultValue;
26
26
  exports.getKeyValueOr = getKeyValueOr;
27
27
  const putKeyValue = (key) => (obj, value) => ({ ...obj, [key]: value });
28
28
  exports.putKeyValue = putKeyValue;
29
+ const coerceArray = (thing) => thing instanceof Array ? thing : thing !== undefined ? [thing] : [];
30
+ exports.coerceArray = coerceArray;
29
31
  /*
30
32
  * this is like lodash/flow but it uses a recursive type instead of hard-coding parameters
31
33
  */
@@ -83,8 +85,9 @@ exports.hashValue = hashValue;
83
85
  * butOnlyOnce() // returns `hello`;
84
86
  */
85
87
  const once = (fn) => {
86
- let result;
87
- return ((...args) => result || (result = fn(...args)));
88
+ const initialValue = {};
89
+ let result = initialValue;
90
+ return ((...args) => result === initialValue ? (result = fn(...args)) : result);
88
91
  };
89
92
  exports.once = once;
90
93
  /*
@@ -7,7 +7,7 @@ const guards_1 = require("../../guards");
7
7
  const embeddedAuthProvider_1 = require("./utils/embeddedAuthProvider");
8
8
  const browserAuthProvider = ({ window, configSpace }) => (configProvider) => {
9
9
  const config = configProvider[(0, guards_1.ifDefined)(configSpace, 'auth')];
10
- const accountsUrl = (0, config_1.resolveConfigValue)(config.accountsUrl);
10
+ const accountsUrl = (0, __1.once)(() => (0, config_1.resolveConfigValue)(config.accountsUrl));
11
11
  const queryString = window.location.search;
12
12
  const queryKey = 'auth';
13
13
  const authQuery = new URLSearchParams(queryString).get(queryKey);
@@ -71,7 +71,7 @@ const browserAuthProvider = ({ window, configSpace }) => (configProvider) => {
71
71
  * requests user identity from accounts api using given token or cookie
72
72
  */
73
73
  const getFetchUser = async () => {
74
- const response = await window.fetch((await accountsUrl).replace(/\/+$/, '') + '/accounts/api/user', getAuthorizedFetchConfigFromData(userData));
74
+ const response = await window.fetch((await accountsUrl()).replace(/\/+$/, '') + '/accounts/api/user', getAuthorizedFetchConfigFromData(userData));
75
75
  if (response.status === 200) {
76
76
  return { ...userData, user: await response.json() };
77
77
  }
@@ -2,21 +2,22 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.decryptionAuthProvider = void 0;
4
4
  const jose_1 = require("jose");
5
+ const __1 = require("../..");
5
6
  const config_1 = require("../../config");
6
7
  const guards_1 = require("../../guards");
7
8
  const _1 = require(".");
8
9
  const decryptionAuthProvider = (initializer) => (configProvider) => {
9
10
  const config = configProvider[(0, guards_1.ifDefined)(initializer.configSpace, 'decryption')];
10
- const cookieName = (0, config_1.resolveConfigValue)(config.cookieName);
11
- const encryptionPrivateKey = (0, config_1.resolveConfigValue)(config.encryptionPrivateKey);
12
- const signaturePublicKey = (0, config_1.resolveConfigValue)(config.signaturePublicKey);
11
+ const cookieName = (0, __1.once)(() => (0, config_1.resolveConfigValue)(config.cookieName));
12
+ const encryptionPrivateKey = (0, __1.once)(() => (0, config_1.resolveConfigValue)(config.encryptionPrivateKey));
13
+ const signaturePublicKey = (0, __1.once)(() => (0, config_1.resolveConfigValue)(config.signaturePublicKey));
13
14
  const decryptAndVerify = async (jwt) => {
14
15
  try {
15
16
  // Decrypt SSO cookie
16
- const { plaintext } = await (0, jose_1.compactDecrypt)(jwt, Buffer.from(await encryptionPrivateKey), // Note: Buffer.from() is node-js only
17
+ const { plaintext } = await (0, jose_1.compactDecrypt)(jwt, Buffer.from(await encryptionPrivateKey()), // Note: Buffer.from() is node-js only
17
18
  { contentEncryptionAlgorithms: ['A256GCM'], keyManagementAlgorithms: ['dir'] });
18
19
  // Verify SSO cookie signature
19
- const { payload } = await (0, jose_1.compactVerify)(plaintext, await (0, jose_1.importSPKI)(await signaturePublicKey, 'RS256'), { algorithms: ['RS256'] });
20
+ const { payload } = await (0, jose_1.compactVerify)(plaintext, await (0, jose_1.importSPKI)(await signaturePublicKey(), 'RS256'), { algorithms: ['RS256'] });
20
21
  return payload;
21
22
  }
22
23
  catch {
@@ -26,14 +27,14 @@ const decryptionAuthProvider = (initializer) => (configProvider) => {
26
27
  return ({ request, profile }) => {
27
28
  let user;
28
29
  const getAuthorizedFetchConfig = profile.track('getAuthorizedFetchConfig', () => async () => {
29
- const [token, headers] = (0, _1.getAuthTokenOrCookie)(request, await cookieName);
30
+ const [token, headers] = (0, _1.getAuthTokenOrCookie)(request, await cookieName());
30
31
  if (!token) {
31
32
  return {};
32
33
  }
33
34
  return { headers };
34
35
  });
35
36
  const loadUser = profile.track('loadUser', () => async () => {
36
- const [token] = (0, _1.getAuthTokenOrCookie)(request, await cookieName);
37
+ const [token] = (0, _1.getAuthTokenOrCookie)(request, await cookieName());
37
38
  if (!token) {
38
39
  return undefined;
39
40
  }
@@ -1,28 +1,29 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.subrequestAuthProvider = void 0;
4
+ const __1 = require("../..");
4
5
  const config_1 = require("../../config");
5
6
  const guards_1 = require("../../guards");
6
7
  const _1 = require(".");
7
8
  const subrequestAuthProvider = (initializer) => (configProvider) => {
8
9
  const config = configProvider[(0, guards_1.ifDefined)(initializer.configSpace, 'subrequest')];
9
- const cookieName = (0, config_1.resolveConfigValue)(config.cookieName);
10
- const accountsUrl = (0, config_1.resolveConfigValue)(config.accountsUrl);
10
+ const cookieName = (0, __1.once)(() => (0, config_1.resolveConfigValue)(config.cookieName));
11
+ const accountsUrl = (0, __1.once)(() => (0, config_1.resolveConfigValue)(config.accountsUrl));
11
12
  return ({ request, profile }) => {
12
13
  let user;
13
14
  const getAuthorizedFetchConfig = profile.track('getAuthorizedFetchConfig', () => async () => {
14
- const [token, headers] = (0, _1.getAuthTokenOrCookie)(request, await cookieName);
15
+ const [token, headers] = (0, _1.getAuthTokenOrCookie)(request, await cookieName());
15
16
  if (!token) {
16
17
  return {};
17
18
  }
18
19
  return { headers };
19
20
  });
20
21
  const loadUser = profile.track('loadUser', p => async () => {
21
- const [token, headers] = (0, _1.getAuthTokenOrCookie)(request, await cookieName);
22
+ const [token, headers] = (0, _1.getAuthTokenOrCookie)(request, await cookieName());
22
23
  if (!token) {
23
24
  return undefined;
24
25
  }
25
- return p.trackFetch(initializer.fetch)(await accountsUrl, { headers })
26
+ return p.trackFetch(initializer.fetch)(await accountsUrl(), { headers })
26
27
  .then(response => response.json());
27
28
  });
28
29
  return {
@@ -25,17 +25,18 @@ var __importStar = (this && this.__importStar) || function (mod) {
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
26
  exports.exercisesGateway = void 0;
27
27
  const queryString = __importStar(require("query-string"));
28
+ const __1 = require("../..");
28
29
  const assertions_1 = require("../../assertions");
29
30
  const config_1 = require("../../config");
30
31
  const guards_1 = require("../../guards");
31
32
  const routing_1 = require("../../routing");
32
33
  const exercisesGateway = (initializer) => (configProvider) => {
33
34
  const config = configProvider[(0, guards_1.ifDefined)(initializer.configSpace, 'exercises')];
34
- const exercisesHost = (0, config_1.resolveConfigValue)(config.exercisesHost);
35
- const exercisesAuthToken = (0, config_1.resolveConfigValue)(config.exercisesAuthToken);
36
- const defaultCorrectness = (0, config_1.resolveConfigValue)(config.defaultCorrectness || '');
35
+ const exercisesHost = (0, __1.once)(() => (0, config_1.resolveConfigValue)(config.exercisesHost));
36
+ const exercisesAuthToken = (0, __1.once)(() => (0, config_1.resolveConfigValue)(config.exercisesAuthToken));
37
+ const defaultCorrectness = (0, __1.once)(() => (0, config_1.resolveConfigValue)(config.defaultCorrectness || ''));
37
38
  const doDefaultCorrectness = async (exercise) => {
38
- if (await defaultCorrectness !== 'true') {
39
+ if (await defaultCorrectness() !== 'true') {
39
40
  return exercise;
40
41
  }
41
42
  for (const question of exercise.questions) {
@@ -58,12 +59,12 @@ const exercisesGateway = (initializer) => (configProvider) => {
58
59
  return exercise;
59
60
  };
60
61
  const request = async (method, path, query = undefined) => {
61
- const host = (await exercisesHost).replace(/\/+$/, '');
62
+ const host = (await exercisesHost()).replace(/\/+$/, '');
62
63
  const baseUrl = `${host}/api/${path}`;
63
64
  const url = query ? `${baseUrl}?${queryString.stringify(query)}` : baseUrl;
64
65
  return initializer.fetch(url, {
65
66
  headers: {
66
- Authorization: `Bearer ${await exercisesAuthToken}`,
67
+ Authorization: `Bearer ${await exercisesAuthToken()}`,
67
68
  },
68
69
  method,
69
70
  });
@@ -29,6 +29,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
29
29
  exports.lrsGateway = void 0;
30
30
  const formatISO_1 = __importDefault(require("date-fns/formatISO"));
31
31
  const queryString = __importStar(require("query-string"));
32
+ const __1 = require("../..");
32
33
  const assertions_1 = require("../../assertions");
33
34
  const config_1 = require("../../config");
34
35
  const errors_1 = require("../../errors");
@@ -36,8 +37,8 @@ const guards_1 = require("../../guards");
36
37
  const routing_1 = require("../../routing");
37
38
  const lrsGateway = (initializer) => (configProvider) => {
38
39
  const config = configProvider[(0, guards_1.ifDefined)(initializer.configSpace, 'lrs')];
39
- const lrsHost = (0, config_1.resolveConfigValue)(config.lrsHost);
40
- const lrsAuthorization = (0, config_1.resolveConfigValue)(config.lrsAuthorization);
40
+ const lrsHost = (0, __1.once)(() => (0, config_1.resolveConfigValue)(config.lrsHost));
41
+ const lrsAuthorization = (0, __1.once)(() => (0, config_1.resolveConfigValue)(config.lrsAuthorization));
41
42
  return (authProvider) => {
42
43
  const putXapiStatements = async (statements) => {
43
44
  const user = (0, assertions_1.assertDefined)(await authProvider.getUser(), new errors_1.UnauthorizedError);
@@ -52,10 +53,10 @@ const lrsGateway = (initializer) => (configProvider) => {
52
53
  },
53
54
  timestamp: (0, formatISO_1.default)(new Date())
54
55
  }));
55
- return initializer.fetch((await lrsHost).replace(/\/+$/, '') + '/data/xAPI/statements', {
56
+ return initializer.fetch((await lrsHost()).replace(/\/+$/, '') + '/data/xAPI/statements', {
56
57
  body: JSON.stringify(statementsWithDefaults),
57
58
  headers: {
58
- Authorization: await lrsAuthorization,
59
+ Authorization: await lrsAuthorization(),
59
60
  'Content-Type': 'application/json',
60
61
  'X-Experience-API-Version': '1.0.0',
61
62
  },
@@ -65,9 +66,9 @@ const lrsGateway = (initializer) => (configProvider) => {
65
66
  .then(ids => ids.map((id, index) => ({ id, ...statementsWithDefaults[index] })));
66
67
  };
67
68
  const getMoreXapiStatements = async (more) => {
68
- return initializer.fetch((await lrsHost).replace(/\/+$/, '') + more, {
69
+ return initializer.fetch((await lrsHost()).replace(/\/+$/, '') + more, {
69
70
  headers: {
70
- Authorization: await lrsAuthorization,
71
+ Authorization: await lrsAuthorization(),
71
72
  'X-Experience-API-Version': '1.0.0',
72
73
  },
73
74
  })
@@ -75,7 +76,7 @@ const lrsGateway = (initializer) => (configProvider) => {
75
76
  .then(json => json);
76
77
  };
77
78
  const getXapiStatements = async ({ user, anyUser, ...options }) => {
78
- return initializer.fetch((await lrsHost).replace(/\/+$/, '') + '/data/xAPI/statements?' + queryString.stringify({
79
+ return initializer.fetch((await lrsHost()).replace(/\/+$/, '') + '/data/xAPI/statements?' + queryString.stringify({
79
80
  ...options,
80
81
  ...(anyUser === true ? {} : {
81
82
  agent: JSON.stringify({
@@ -88,7 +89,7 @@ const lrsGateway = (initializer) => (configProvider) => {
88
89
  })
89
90
  }), {
90
91
  headers: {
91
- Authorization: await lrsAuthorization,
92
+ Authorization: await lrsAuthorization(),
92
93
  'X-Experience-API-Version': '1.0.0',
93
94
  },
94
95
  })
@@ -1,25 +1,19 @@
1
- declare type KeysMatching<T, V> = {
2
- [K in keyof T]-?: T[K] extends V ? K : never;
3
- }[keyof T];
4
- declare type UnArray<T> = T extends Array<infer I> ? I : T;
5
- declare type FilterFieldTypes = string | string[];
6
- declare type Filter<F extends keyof T, T> = F extends KeysMatching<T, FilterFieldTypes> ? {
7
- key: F;
8
- value: UnArray<T[F]> | UnArray<T[F]>[];
9
- } : never;
10
- declare type QueryFieldTypes = string | string[] | undefined | null;
11
- declare type Field<F extends keyof T, T> = F extends KeysMatching<T, QueryFieldTypes> ? ({
12
- key: F;
1
+ declare type Filter = {
2
+ key: string;
3
+ value: string | string[];
4
+ };
5
+ declare type Field = {
6
+ key: string;
13
7
  type?: 'text';
14
8
  weight: number;
15
9
  } | {
16
- key: F;
10
+ key: string;
17
11
  type: 'keyword';
18
- }) : never;
19
- export interface SearchOptions<T> {
12
+ };
13
+ export interface SearchOptions {
20
14
  page?: number;
21
15
  query: string | undefined;
22
- fields: Field<keyof T, T>[];
23
- filter?: Filter<keyof T, T>[];
16
+ fields: Field[];
17
+ filter?: Filter[];
24
18
  }
25
19
  export {};
@@ -2,7 +2,7 @@ import { SearchOptions } from '.';
2
2
  export declare const memorySearchTheBadWay: <T>({ loadAllDocumentsTheBadWay }: {
3
3
  loadAllDocumentsTheBadWay: () => Promise<T[]>;
4
4
  }) => {
5
- search: (options: SearchOptions<T>) => Promise<{
5
+ search: (options: SearchOptions) => Promise<{
6
6
  items: T[];
7
7
  pageSize: number;
8
8
  currentPage: number;
@@ -1,9 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.memorySearchTheBadWay = void 0;
4
+ const __1 = require("../..");
4
5
  const guards_1 = require("../../guards");
5
6
  const MIN_MATCH = 0.5;
6
7
  const MAX_RESULTS = 10;
8
+ const resolveField = (document, field) => field.key.toString().split('.').reduce((result, key) => result[key], document);
7
9
  const memorySearchTheBadWay = ({ loadAllDocumentsTheBadWay }) => {
8
10
  return {
9
11
  search: async (options) => {
@@ -15,7 +17,7 @@ const memorySearchTheBadWay = ({ loadAllDocumentsTheBadWay }) => {
15
17
  if (field.type !== undefined && field.type !== 'text') {
16
18
  continue;
17
19
  }
18
- const value = document[field.key];
20
+ const value = resolveField(document, field);
19
21
  if (value === undefined || value === null) {
20
22
  continue;
21
23
  }
@@ -30,14 +32,8 @@ const memorySearchTheBadWay = ({ loadAllDocumentsTheBadWay }) => {
30
32
  }
31
33
  }
32
34
  for (const field of options.filter || []) {
33
- const value = document[field.key];
34
- if (value instanceof Array) {
35
- if (!(field.value instanceof Array && field.value.some(v => value.includes(v)))
36
- && !(!(field.value instanceof Array) && value.includes(field.value))) {
37
- return undefined;
38
- }
39
- }
40
- else if (value !== field.value) {
35
+ const value = resolveField(document, field);
36
+ if (!(0, __1.coerceArray)(field.value).some(v => (0, __1.coerceArray)(value).includes(v))) {
41
37
  return undefined;
42
38
  }
43
39
  }
@@ -53,12 +53,12 @@ const decodeDynamoDocument = (document) => Object.fromEntries(Object.entries(doc
53
53
  // i'm not really excited about getAuthor being required, but ts is getting confused about the type when unspecified
54
54
  const dynamoVersionedDocumentStore = (initializer) => () => (configProvider) => ({ profile }, hashKey, getAuthor) => {
55
55
  const options = (0, guards_1.ifDefined)(initializer, {});
56
- const tableName = (0, config_1.resolveConfigValue)(configProvider[(0, guards_1.ifDefined)(options.configSpace, 'dynamodb')].tableName);
56
+ const tableName = (0, __1.once)(() => (0, config_1.resolveConfigValue)(configProvider[(0, guards_1.ifDefined)(options.configSpace, 'dynamodb')].tableName));
57
57
  return {
58
58
  loadAllDocumentsTheBadWay: profile.track('versionedDocumentStore.loadAllDocumentsTheBadWay', () => async () => {
59
59
  const loadAllResults = async (ExclusiveStartKey) => {
60
60
  var _a;
61
- const cmd = new client_dynamodb_1.ScanCommand({ TableName: await tableName, ExclusiveStartKey });
61
+ const cmd = new client_dynamodb_1.ScanCommand({ TableName: await tableName(), ExclusiveStartKey });
62
62
  const result = await dynamodb().send(cmd);
63
63
  const resultItems = ((_a = result.Items) === null || _a === void 0 ? void 0 : _a.map(decodeDynamoDocument)) || [];
64
64
  if (result.LastEvaluatedKey) {
@@ -77,7 +77,7 @@ const dynamoVersionedDocumentStore = (initializer) => () => (configProvider) =>
77
77
  }),
78
78
  getVersions: profile.track('versionedDocumentStore.getVersions', () => async (id, startVersion) => {
79
79
  const cmd = new client_dynamodb_1.QueryCommand({
80
- TableName: await tableName,
80
+ TableName: await tableName(),
81
81
  KeyConditionExpression: '#hk = :hkv',
82
82
  ExpressionAttributeValues: {
83
83
  ':hkv': encodeDynamoAttribute(id)
@@ -119,7 +119,7 @@ const dynamoVersionedDocumentStore = (initializer) => () => (configProvider) =>
119
119
  expressionAttributeValues[':tsv'] = encodeDynamoAttribute(timestamp);
120
120
  }
121
121
  const cmd = new client_dynamodb_1.QueryCommand({
122
- TableName: await tableName,
122
+ TableName: await tableName(),
123
123
  KeyConditionExpression: keyConditionExpression,
124
124
  ExpressionAttributeNames: expressionAttributeNames,
125
125
  ExpressionAttributeValues: expressionAttributeValues,
@@ -140,7 +140,7 @@ const dynamoVersionedDocumentStore = (initializer) => () => (configProvider) =>
140
140
  author
141
141
  };
142
142
  const cmd = new client_dynamodb_1.PutItemCommand({
143
- TableName: await tableName,
143
+ TableName: await tableName(),
144
144
  Item: encodeDynamoDocument(document),
145
145
  });
146
146
  return dynamodb().send(cmd)