@openstax/ts-utils 1.7.0 → 1.9.0

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 (81) hide show
  1. package/dist/cjs/{fetch.d.ts → fetch/index.d.ts} +1 -1
  2. package/dist/{esm/middleware.d.ts → cjs/middleware/index.d.ts} +2 -2
  3. package/dist/cjs/misc/hashValue.d.ts +2 -3
  4. package/dist/cjs/{pagination.d.ts → pagination/index.d.ts} +1 -1
  5. package/dist/cjs/{pagination.js → pagination/index.js} +3 -3
  6. package/dist/cjs/routing/index.d.ts +0 -3
  7. package/dist/cjs/routing/index.js +7 -9
  8. package/dist/cjs/services/authProvider/decryption.js +5 -5
  9. package/dist/cjs/services/authProvider/index.d.ts +0 -2
  10. package/dist/cjs/services/authProvider/subrequest.js +6 -6
  11. package/dist/cjs/services/documentStore/dynamoEncoding.d.ts +10 -0
  12. package/dist/cjs/services/documentStore/dynamoEncoding.js +52 -0
  13. package/dist/cjs/services/documentStore/index.d.ts +14 -0
  14. package/dist/cjs/services/documentStore/unversioned/dynamodb.d.ts +13 -0
  15. package/dist/cjs/services/documentStore/unversioned/dynamodb.js +51 -0
  16. package/dist/cjs/services/documentStore/unversioned/file-system.d.ts +15 -0
  17. package/dist/cjs/services/documentStore/unversioned/file-system.js +81 -0
  18. package/dist/cjs/services/documentStore/unversioned/index.d.ts +2 -0
  19. package/dist/cjs/services/documentStore/unversioned/index.js +2 -0
  20. package/dist/{esm/services/versionedDocumentStore → cjs/services/documentStore/versioned}/dynamodb.d.ts +6 -11
  21. package/dist/cjs/services/{versionedDocumentStore → documentStore/versioned}/dynamodb.js +27 -70
  22. package/dist/cjs/services/{versionedDocumentStore → documentStore/versioned}/file-system.d.ts +6 -11
  23. package/dist/cjs/services/{versionedDocumentStore → documentStore/versioned}/file-system.js +16 -16
  24. package/dist/cjs/services/{versionedDocumentStore → documentStore/versioned}/index.d.ts +2 -8
  25. package/dist/cjs/services/documentStore/versioned/index.js +2 -0
  26. package/dist/cjs/services/exercisesGateway/index.d.ts +3 -6
  27. package/dist/cjs/services/exercisesGateway/index.js +7 -7
  28. package/dist/cjs/tsconfig.without-specs.cjs.tsbuildinfo +1 -1
  29. package/dist/cjs/types.d.ts +4 -0
  30. package/dist/esm/{fetch.d.ts → fetch/index.d.ts} +1 -1
  31. package/dist/{cjs/middleware.d.ts → esm/middleware/index.d.ts} +2 -2
  32. package/dist/esm/misc/hashValue.d.ts +2 -3
  33. package/dist/esm/{pagination.d.ts → pagination/index.d.ts} +1 -1
  34. package/dist/esm/{pagination.js → pagination/index.js} +3 -3
  35. package/dist/esm/routing/index.d.ts +0 -3
  36. package/dist/esm/routing/index.js +7 -9
  37. package/dist/esm/services/authProvider/decryption.js +5 -5
  38. package/dist/esm/services/authProvider/index.d.ts +0 -2
  39. package/dist/esm/services/authProvider/subrequest.js +6 -6
  40. package/dist/esm/services/documentStore/dynamoEncoding.d.ts +10 -0
  41. package/dist/esm/services/documentStore/dynamoEncoding.js +45 -0
  42. package/dist/esm/services/documentStore/index.d.ts +14 -0
  43. package/dist/esm/services/documentStore/unversioned/dynamodb.d.ts +13 -0
  44. package/dist/esm/services/documentStore/unversioned/dynamodb.js +47 -0
  45. package/dist/esm/services/documentStore/unversioned/file-system.d.ts +15 -0
  46. package/dist/esm/services/documentStore/unversioned/file-system.js +51 -0
  47. package/dist/esm/services/documentStore/unversioned/index.d.ts +2 -0
  48. package/dist/esm/services/documentStore/unversioned/index.js +1 -0
  49. package/dist/{cjs/services/versionedDocumentStore → esm/services/documentStore/versioned}/dynamodb.d.ts +6 -11
  50. package/dist/esm/services/{versionedDocumentStore → documentStore/versioned}/dynamodb.js +17 -60
  51. package/dist/esm/services/{versionedDocumentStore → documentStore/versioned}/file-system.d.ts +6 -11
  52. package/dist/esm/services/{versionedDocumentStore → documentStore/versioned}/file-system.js +16 -16
  53. package/dist/esm/services/{versionedDocumentStore → documentStore/versioned}/index.d.ts +2 -8
  54. package/dist/esm/services/documentStore/versioned/index.js +1 -0
  55. package/dist/esm/services/exercisesGateway/index.d.ts +3 -6
  56. package/dist/esm/services/exercisesGateway/index.js +7 -7
  57. package/dist/esm/tsconfig.without-specs.esm.tsbuildinfo +1 -1
  58. package/dist/esm/types.d.ts +4 -0
  59. package/package.json +118 -13
  60. package/dist/cjs/profile.d.ts +0 -61
  61. package/dist/cjs/profile.js +0 -199
  62. package/dist/esm/profile.d.ts +0 -61
  63. package/dist/esm/profile.js +0 -191
  64. /package/dist/cjs/{assertions.d.ts → assertions/index.d.ts} +0 -0
  65. /package/dist/cjs/{assertions.js → assertions/index.js} +0 -0
  66. /package/dist/cjs/{errors.d.ts → errors/index.d.ts} +0 -0
  67. /package/dist/cjs/{errors.js → errors/index.js} +0 -0
  68. /package/dist/cjs/{fetch.js → fetch/index.js} +0 -0
  69. /package/dist/cjs/{guards.d.ts → guards/index.d.ts} +0 -0
  70. /package/dist/cjs/{guards.js → guards/index.js} +0 -0
  71. /package/dist/cjs/{middleware.js → middleware/index.js} +0 -0
  72. /package/dist/cjs/services/{versionedDocumentStore → documentStore}/index.js +0 -0
  73. /package/dist/esm/{assertions.d.ts → assertions/index.d.ts} +0 -0
  74. /package/dist/esm/{assertions.js → assertions/index.js} +0 -0
  75. /package/dist/esm/{errors.d.ts → errors/index.d.ts} +0 -0
  76. /package/dist/esm/{errors.js → errors/index.js} +0 -0
  77. /package/dist/esm/{fetch.js → fetch/index.js} +0 -0
  78. /package/dist/esm/{guards.d.ts → guards/index.d.ts} +0 -0
  79. /package/dist/esm/{guards.js → guards/index.js} +0 -0
  80. /package/dist/esm/{middleware.js → middleware/index.js} +0 -0
  81. /package/dist/esm/services/{versionedDocumentStore → documentStore}/index.js +0 -0
@@ -7,6 +7,10 @@
7
7
  * There may be a better way to do this; `T1 extends T2` doesn't work
8
8
  */
9
9
  export declare type TupleExtends<T1, T2> = T1 extends [infer T1Head, ...infer T1Tail] ? T2 extends [infer T2Head, ...infer T2Tail] ? T1Head extends T2Head ? TupleExtends<T1Tail, T2Tail> extends 'yes' ? 'yes' : 'no' : 'no' : T2 extends [] ? 'yes' : 'no' : T1 extends [] ? T2 extends [] ? 'yes' : 'no' : 'no';
10
+ /**
11
+ * unions each member of the tuple
12
+ */
13
+ export declare type TupleZip<T1, T2> = T1 extends [infer T1Head, ...infer T1Tail] ? T2 extends [infer T2Head, ...infer T2Tail] ? [T1Head & T2Head, ...TupleZip<T1Tail, T2Tail>] : T2 extends [] ? T1 : never : T1 extends [] ? T2 extends any[] ? T2 : never : never;
10
14
  /**
11
15
  * If `R` is `Promise<I>`, returns `I`, otherwise returns `R`
12
16
  * @deprecated use TypeScript builtin Awaited instead:
@@ -1,4 +1,4 @@
1
- import { METHOD } from './routing';
1
+ import { METHOD } from '../routing';
2
2
  export declare enum FetchStateType {
3
3
  SUCCESS = "success",
4
4
  ERROR = "error",
@@ -1,11 +1,11 @@
1
- import { TupleExtends } from './types';
1
+ import { TupleZip } from '../types';
2
2
  export declare type MiddlewareProvider<Sa, M, A extends any[], R> = (app: Sa, appBinder?: ((app: Sa, provider: MiddlewareProvider<Sa, M, A, R>) => (middleware: M, ...args: A) => R)) => (middleware: M, ...args: A) => R;
3
3
  export declare type MiddlewareTransformProvider<Sa, M, A extends any[], R> = (app: Sa) => (middleware: M, ...args: A) => Omit<M, keyof R> & R;
4
4
  export declare type MiddlewareInput<S> = S extends MiddlewareProvider<any, infer M, any, any> ? M : never;
5
5
  export declare type ServiceMiddlewareProviderResult<M, S> = [M] extends [never] ? never : [M] extends [MiddlewareInput<S>] ? S extends MiddlewareTransformProvider<any, M, any, infer R> ? (Omit<R, keyof M> & M) extends R ? Omit<R, keyof M> & M : R extends M ? R : Omit<M, keyof R> & R : S extends MiddlewareProvider<any, M, any, infer R> ? R : never : never;
6
6
  export declare type ServiceMiddlewareProviderArgs<S> = S extends MiddlewareProvider<any, any, infer A, any> ? A : never;
7
7
  export declare type ServiceMiddlewareProviderChainResult<C, M> = C extends [infer S1, ...infer S] ? S extends never[] ? ServiceMiddlewareProviderResult<M, S1> : ServiceMiddlewareProviderChainResult<S, ServiceMiddlewareProviderResult<M, S1>> : C extends unknown ? M : never;
8
- export declare type ServiceMiddlewareProviderChainArgs<C> = C extends [infer S1, ...infer S] ? S extends never[] ? ServiceMiddlewareProviderArgs<S1> : TupleExtends<ServiceMiddlewareProviderChainArgs<S>, ServiceMiddlewareProviderArgs<S1>> extends 'yes' ? ServiceMiddlewareProviderChainArgs<S> : TupleExtends<ServiceMiddlewareProviderArgs<S1>, ServiceMiddlewareProviderChainArgs<S>> extends 'yes' ? ServiceMiddlewareProviderArgs<S1> : never : C extends unknown ? [] : never;
8
+ export declare type ServiceMiddlewareProviderChainArgs<C> = C extends [infer S1, ...infer S] ? S extends never[] ? ServiceMiddlewareProviderArgs<S1> : TupleZip<ServiceMiddlewareProviderChainArgs<S>, ServiceMiddlewareProviderArgs<S1>> : C extends unknown ? [] : never;
9
9
  /**
10
10
  * Creates a middleware composer for given AppServices and input value types. The composer accepts a list of middleware
11
11
  * that it assembles into a chain. The function returned by the composer takes a value of the given input value type,
@@ -1,5 +1,5 @@
1
- declare type HashValue = string | number | boolean | null | HashCompoundValue;
2
- declare type HashCompoundValue = Array<HashValue> | {
1
+ export declare type HashValue = string | number | boolean | null | HashCompoundValue;
2
+ export declare type HashCompoundValue = Array<HashValue> | {
3
3
  [key: string]: HashValue;
4
4
  };
5
5
  /**
@@ -8,4 +8,3 @@ declare type HashCompoundValue = Array<HashValue> | {
8
8
  * @example hashValue({someKey: 'someValue'})
9
9
  */
10
10
  export declare const hashValue: (value: HashValue) => string;
11
- export {};
@@ -1,4 +1,4 @@
1
- import { QueryParams, RouteMatchRecord } from './routing';
1
+ import { QueryParams, RouteMatchRecord } from '../routing';
2
2
  export declare type PaginationHandler<Pa, Q extends QueryParams> = <R>(queryParams: Q, match: RouteMatchRecord<R>) => Pa & {
3
3
  getUnusedQueryParams: () => Q;
4
4
  };
@@ -1,6 +1,6 @@
1
- import { notNaN } from './assertions';
2
- import { InvalidRequestError } from './errors';
3
- import { renderAnyRouteUrl } from './routing';
1
+ import { notNaN } from '../assertions';
2
+ import { InvalidRequestError } from '../errors';
3
+ import { renderAnyRouteUrl } from '../routing';
4
4
  /**
5
5
  * helper to create middleware with the given paginator. aside from taking care of annoying to write pagination logic, these helpers also make
6
6
  * sure that all item list responses have the same formatting.
@@ -1,4 +1,3 @@
1
- import { Track } from '../profile';
2
1
  import { Logger } from '../services/logger';
3
2
  /** HTTP query parameters */
4
3
  export declare type QueryParams = Record<string, string | undefined | string[] | null>;
@@ -35,7 +34,6 @@ export declare type PayloadForRoute<R> = RequestServicesForRoute<R> extends {
35
34
  } ? RequestServicesForRoute<R>['payload'] : undefined;
36
35
  declare type RequestServiceProvider<Sa, Sr, Ri> = (app: Sa) => <R>(middleware: {
37
36
  request: Ri;
38
- profile: Track;
39
37
  logger: Logger;
40
38
  }, match: RouteMatchRecord<R>) => Sr;
41
39
  declare type RouteHandler<P, Sr, Ro> = (params: P, request: Sr) => Ro;
@@ -168,7 +166,6 @@ declare type RequestResponder<Sa, Ri, Ro> = {
168
166
  (services: CompatibleServices<Sa>): (request: Ri) => Ro | undefined;
169
167
  <RoF>(services: CompatibleServices<Sa>, responseMiddleware: (app: Sa) => (response: Ro | undefined, request: {
170
168
  request: Ri;
171
- profile: Track;
172
169
  logger: Logger;
173
170
  }) => RoF): (request: Ri) => RoF;
174
171
  };
@@ -1,7 +1,6 @@
1
1
  import * as pathToRegexp from 'path-to-regexp';
2
2
  import queryString from 'query-string';
3
3
  import { mapFind, memoize } from '../misc/helpers';
4
- import { createProfile } from '../profile';
5
4
  import { createConsoleLogger } from '../services/logger/console';
6
5
  /**
7
6
  * Makes a createRoute function that can be used to create routes (this is a factory factory). The
@@ -102,11 +101,11 @@ export const renderAnyRouteUrl = makeRenderRouteUrl();
102
101
  const bindRoute = (services, appBinder, pathExtractor, matcher) => (route) => {
103
102
  const getParamsFromPath = pathToRegexp.match(route.path, { decode: decodeURIComponent });
104
103
  const boundServiceProvider = route.requestServiceProvider && appBinder(services, route.requestServiceProvider);
105
- return (request, profile, logger) => {
104
+ return (request, logger) => {
106
105
  const path = pathExtractor(request);
107
106
  const match = getParamsFromPath(path);
108
107
  if ((!matcher || matcher(request, route)) && match) {
109
- return profile.track(route.name, routeProfile => () => route.handler(match.params, boundServiceProvider ? boundServiceProvider({ request, profile: routeProfile, logger }, { route, params: match.params }) : undefined));
108
+ return () => route.handler(match.params, boundServiceProvider ? boundServiceProvider({ request, logger }, { route, params: match.params }) : undefined);
110
109
  }
111
110
  };
112
111
  };
@@ -155,19 +154,18 @@ export const makeGetRequestResponder = () => ({ routes, pathExtractor, routeMatc
155
154
  // types are getting complicated quickly here.
156
155
  const isPromise = (thing) => thing instanceof Promise;
157
156
  return (request) => {
158
- const { end, ...profile } = createProfile(new Date().toISOString()).start();
159
157
  const logger = appLogger.createSubContext();
160
158
  if (logExtractor) {
161
159
  logger.setContext(logExtractor(request));
162
160
  }
163
161
  try {
164
- const executor = mapFind(boundRoutes, (route) => route(request, profile, logger));
162
+ const executor = mapFind(boundRoutes, (route) => route(request, logger));
165
163
  if (executor) {
166
164
  const result = boundResponseMiddleware ?
167
- boundResponseMiddleware(executor(), { request, profile, logger }) : executor();
165
+ boundResponseMiddleware(executor(), { request, logger }) : executor();
168
166
  if (isPromise(result) && errorHandler) {
169
167
  const errorHandlerWithMiddleware = (e) => boundResponseMiddleware ?
170
- boundResponseMiddleware(errorHandler(e, logger), { request, profile, logger }) : errorHandler(e, logger);
168
+ boundResponseMiddleware(errorHandler(e, logger), { request, logger }) : errorHandler(e, logger);
171
169
  return result.catch(errorHandlerWithMiddleware);
172
170
  }
173
171
  else {
@@ -175,13 +173,13 @@ export const makeGetRequestResponder = () => ({ routes, pathExtractor, routeMatc
175
173
  }
176
174
  }
177
175
  else if (boundResponseMiddleware) {
178
- return boundResponseMiddleware(undefined, { request, profile, logger });
176
+ return boundResponseMiddleware(undefined, { request, logger });
179
177
  }
180
178
  }
181
179
  catch (e) {
182
180
  if (errorHandler && e instanceof Error) {
183
181
  return boundResponseMiddleware
184
- ? boundResponseMiddleware(errorHandler(e, logger), { request, profile, logger })
182
+ ? boundResponseMiddleware(errorHandler(e, logger), { request, logger })
185
183
  : errorHandler(e, logger);
186
184
  }
187
185
  throw e;
@@ -8,22 +8,22 @@ export const decryptionAuthProvider = (initializer) => (configProvider) => {
8
8
  const cookieName = once(() => resolveConfigValue(config.cookieName));
9
9
  const encryptionPrivateKey = once(() => resolveConfigValue(config.encryptionPrivateKey));
10
10
  const signaturePublicKey = once(() => resolveConfigValue(config.signaturePublicKey));
11
- return ({ request, profile }) => {
11
+ return ({ request }) => {
12
12
  let user;
13
- const getAuthorizedFetchConfig = profile.track('getAuthorizedFetchConfig', () => async () => {
13
+ const getAuthorizedFetchConfig = async () => {
14
14
  const [token, headers] = getAuthTokenOrCookie(request, await cookieName());
15
15
  if (!token) {
16
16
  return {};
17
17
  }
18
18
  return { headers };
19
- });
20
- const loadUser = profile.track('loadUser', () => async () => {
19
+ };
20
+ const loadUser = async () => {
21
21
  const [token] = getAuthTokenOrCookie(request, await cookieName());
22
22
  if (!token) {
23
23
  return undefined;
24
24
  }
25
25
  return decryptAndVerify(token, await encryptionPrivateKey(), await signaturePublicKey());
26
- });
26
+ };
27
27
  return {
28
28
  getAuthorizedFetchConfig,
29
29
  getUser: async () => {
@@ -1,5 +1,4 @@
1
1
  import type { FetchConfig } from '../../fetch';
2
- import type { Track } from '../../profile';
3
2
  import type { HttpHeaders } from '../../routing';
4
3
  export interface TokenUser {
5
4
  id: number;
@@ -41,7 +40,6 @@ export declare type CookieAuthProviderRequest = {
41
40
  };
42
41
  export declare type CookieAuthProvider = (inputs: {
43
42
  request: CookieAuthProviderRequest;
44
- profile: Track;
45
43
  }) => AuthProvider;
46
44
  export declare type StubAuthProvider = (user: User | undefined) => AuthProvider;
47
45
  export declare const stubAuthProvider: (user?: User | undefined) => AuthProvider;
@@ -6,23 +6,23 @@ export const subrequestAuthProvider = (initializer) => (configProvider) => {
6
6
  const config = configProvider[ifDefined(initializer.configSpace, 'subrequest')];
7
7
  const cookieName = once(() => resolveConfigValue(config.cookieName));
8
8
  const accountsBase = once(() => resolveConfigValue(config.accountsBase));
9
- return ({ request, profile }) => {
9
+ return ({ request }) => {
10
10
  let user;
11
- const getAuthorizedFetchConfig = profile.track('getAuthorizedFetchConfig', () => async () => {
11
+ const getAuthorizedFetchConfig = async () => {
12
12
  const [token, headers] = getAuthTokenOrCookie(request, await cookieName());
13
13
  if (!token) {
14
14
  return {};
15
15
  }
16
16
  return { headers };
17
- });
18
- const loadUser = profile.track('loadUser', p => async () => {
17
+ };
18
+ const loadUser = async () => {
19
19
  const [token, headers] = getAuthTokenOrCookie(request, await cookieName());
20
20
  if (!token) {
21
21
  return undefined;
22
22
  }
23
- return p.trackFetch(initializer.fetch)((await accountsBase()).replace(/\/+$/, '') + '/api/user', { headers })
23
+ return initializer.fetch((await accountsBase()).replace(/\/+$/, '') + '/api/user', { headers })
24
24
  .then(response => response.json());
25
- });
25
+ };
26
26
  return {
27
27
  getAuthorizedFetchConfig,
28
28
  getUser: async () => {
@@ -0,0 +1,10 @@
1
+ import { AttributeValue } from '@aws-sdk/client-dynamodb';
2
+ import { DocumentBaseType, DocumentBaseValueTypes } from '.';
3
+ export declare const encodeDynamoAttribute: (value: DocumentBaseValueTypes) => AttributeValue;
4
+ export declare const encodeDynamoDocument: (base: DocumentBaseType) => {
5
+ [k: string]: AttributeValue;
6
+ };
7
+ export declare const decodeDynamoAttribute: (value: AttributeValue) => DocumentBaseValueTypes;
8
+ export declare const decodeDynamoDocument: <T extends DocumentBaseType>(document: {
9
+ [key: string]: AttributeValue;
10
+ }) => T;
@@ -0,0 +1,45 @@
1
+ import { isPlainObject } from '../../guards';
2
+ export const encodeDynamoAttribute = (value) => {
3
+ if (typeof value === 'string') {
4
+ return { S: value };
5
+ }
6
+ if (typeof value === 'number') {
7
+ return { N: value.toString() };
8
+ }
9
+ if (typeof value === 'boolean') {
10
+ return { BOOL: value };
11
+ }
12
+ if (value === null) {
13
+ return { NULL: true };
14
+ }
15
+ if (value instanceof Array) {
16
+ return { L: value.map(encodeDynamoAttribute) };
17
+ }
18
+ if (isPlainObject(value)) {
19
+ return { M: encodeDynamoDocument(value) };
20
+ }
21
+ throw new Error(`unknown attribute type ${typeof value} with value ${value}.`);
22
+ };
23
+ export const encodeDynamoDocument = (base) => Object.fromEntries(Object.entries(base).map(([key, value]) => ([key, encodeDynamoAttribute(value)])));
24
+ export const decodeDynamoAttribute = (value) => {
25
+ if (value.S !== undefined) {
26
+ return value.S;
27
+ }
28
+ if (value.N !== undefined) {
29
+ return parseFloat(value.N);
30
+ }
31
+ if (value.BOOL !== undefined) {
32
+ return value.BOOL;
33
+ }
34
+ if (value.NULL !== undefined) {
35
+ return null;
36
+ }
37
+ if (value.L !== undefined) {
38
+ return value.L.map(decodeDynamoAttribute);
39
+ }
40
+ if (value.M !== undefined) {
41
+ return decodeDynamoDocument(value.M);
42
+ }
43
+ throw new Error(`unknown attribute type: ${JSON.stringify(value)}.`);
44
+ };
45
+ export const decodeDynamoDocument = (document) => Object.fromEntries(Object.entries(document).map(([key, value]) => ([key, decodeDynamoAttribute(value)])));
@@ -0,0 +1,14 @@
1
+ export declare type Config = {
2
+ tableName: string;
3
+ };
4
+ export declare type DocumentBaseMapType = {
5
+ [key: string]: DocumentBaseValueTypes;
6
+ };
7
+ export declare type DocumentBaseListType = DocumentBaseValueTypes[];
8
+ export declare type DocumentBaseValueTypes = number | boolean | string | null | DocumentBaseMapType | DocumentBaseListType;
9
+ export declare type DocumentBaseType = {
10
+ [key: string]: DocumentBaseValueTypes;
11
+ };
12
+ export declare type TDocument<T> = {
13
+ [k in keyof T]: DocumentBaseValueTypes;
14
+ };
@@ -0,0 +1,13 @@
1
+ import { Config, TDocument } from '..';
2
+ import { ConfigProviderForConfig } from '../../../config';
3
+ interface Initializer<C> {
4
+ configSpace?: C;
5
+ }
6
+ export declare const dynamoUnversionedDocumentStore: <C extends string = "dynamodb">(initializer?: Initializer<C> | undefined) => <T extends TDocument<T>>() => (configProvider: { [key in C]: {
7
+ tableName: import("../../../config").ConfigValueProvider<string>;
8
+ }; }) => <K extends keyof T>(_: {}, hashKey: K) => {
9
+ loadAllDocumentsTheBadWay: () => Promise<T[]>;
10
+ getItem: (id: T[K]) => Promise<T | undefined>;
11
+ putItem: (item: T) => Promise<T>;
12
+ };
13
+ export {};
@@ -0,0 +1,47 @@
1
+ import { DynamoDB, PutItemCommand, QueryCommand, ScanCommand } from '@aws-sdk/client-dynamodb';
2
+ import { once } from '../../..';
3
+ import { resolveConfigValue } from '../../../config';
4
+ import { ifDefined } from '../../../guards';
5
+ import { decodeDynamoDocument, encodeDynamoAttribute, encodeDynamoDocument } from '../dynamoEncoding';
6
+ const dynamodb = once(() => new DynamoDB({ apiVersion: '2012-08-10' }));
7
+ export const dynamoUnversionedDocumentStore = (initializer) => () => (configProvider) => (_, hashKey) => {
8
+ const options = ifDefined(initializer, {});
9
+ const tableName = once(() => resolveConfigValue(configProvider[ifDefined(options.configSpace, 'dynamodb')].tableName));
10
+ return {
11
+ loadAllDocumentsTheBadWay: async () => {
12
+ const loadAllResults = async (ExclusiveStartKey) => {
13
+ var _a;
14
+ const cmd = new ScanCommand({ TableName: await tableName(), ExclusiveStartKey });
15
+ const result = await dynamodb().send(cmd);
16
+ const resultItems = ((_a = result.Items) === null || _a === void 0 ? void 0 : _a.map(decodeDynamoDocument)) || [];
17
+ if (result.LastEvaluatedKey) {
18
+ return [...resultItems, ...await loadAllResults(result.LastEvaluatedKey)];
19
+ }
20
+ return resultItems;
21
+ };
22
+ return loadAllResults();
23
+ },
24
+ getItem: async (id) => {
25
+ const cmd = new QueryCommand({
26
+ TableName: await tableName(),
27
+ KeyConditionExpression: '#hk = :hkv',
28
+ ExpressionAttributeNames: { '#hk': hashKey.toString() },
29
+ ExpressionAttributeValues: { ':hkv': encodeDynamoAttribute(id) },
30
+ ScanIndexForward: false,
31
+ Limit: 1
32
+ });
33
+ return dynamodb().send(cmd).then(result => {
34
+ var _a;
35
+ return (_a = result.Items) === null || _a === void 0 ? void 0 : _a.map(decodeDynamoDocument)[0];
36
+ });
37
+ },
38
+ /* saves a new version of a document with the given data */
39
+ putItem: async (item) => {
40
+ const cmd = new PutItemCommand({
41
+ TableName: await tableName(),
42
+ Item: encodeDynamoDocument(item),
43
+ });
44
+ return dynamodb().send(cmd).then(() => item);
45
+ },
46
+ };
47
+ };
@@ -0,0 +1,15 @@
1
+ import { Config, TDocument } from '..';
2
+ import { ConfigProviderForConfig } from '../../../config';
3
+ interface Initializer<C> {
4
+ dataDir: string;
5
+ fs?: Pick<typeof import('fs'), 'mkdir' | 'readdir' | 'readFile' | 'writeFile'>;
6
+ configSpace?: C;
7
+ }
8
+ export declare const fileSystemUnversionedDocumentStore: <C extends string = "fileSystem">(initializer: Initializer<C>) => <T extends TDocument<T>>() => (configProvider: { [key in C]: {
9
+ tableName: import("../../../config").ConfigValueProvider<string>;
10
+ }; }) => <K extends keyof T>(_: {}, hashKey: K) => {
11
+ loadAllDocumentsTheBadWay: () => Promise<T[]>;
12
+ getItem: (id: T[K]) => Promise<T | undefined>;
13
+ putItem: (item: T) => Promise<T>;
14
+ };
15
+ export {};
@@ -0,0 +1,51 @@
1
+ import * as fsModule from 'fs';
2
+ import path from 'path';
3
+ import { hashValue } from '../../..';
4
+ import { resolveConfigValue } from '../../../config';
5
+ import { ifDefined, isDefined } from '../../../guards';
6
+ export const fileSystemUnversionedDocumentStore = (initializer) => () => (configProvider) => (_, hashKey) => {
7
+ const tableName = resolveConfigValue(configProvider[initializer.configSpace || 'fileSystem'].tableName);
8
+ const tablePath = tableName.then((table) => path.join(initializer.dataDir, table));
9
+ const { mkdir, readdir, readFile, writeFile } = ifDefined(initializer.fs, fsModule);
10
+ const mkTableDir = new Promise((resolve, reject) => tablePath.then((path) => mkdir(path, { recursive: true }, (err) => err ? reject(err) : resolve())));
11
+ const hashFilename = (value) => `${hashValue(value)}.json`;
12
+ const filePath = async (filename) => path.join(await tablePath, filename);
13
+ const load = async (filename) => {
14
+ const path = await filePath(filename);
15
+ await mkTableDir;
16
+ return new Promise((resolve, reject) => {
17
+ readFile(path, (err, readData) => {
18
+ if (err) {
19
+ err.code === 'ENOENT' ? resolve(undefined) : reject(err);
20
+ }
21
+ else {
22
+ try {
23
+ const data = JSON.parse(readData.toString());
24
+ typeof data === 'object' ? resolve(data) : reject(new Error('unexpected non-object JSON'));
25
+ }
26
+ catch (err) {
27
+ reject(err);
28
+ }
29
+ }
30
+ });
31
+ });
32
+ };
33
+ return {
34
+ 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
+ },
42
+ getItem: (id) => load(hashFilename(id)),
43
+ putItem: async (item) => {
44
+ const path = await filePath(hashFilename(item[hashKey]));
45
+ await mkTableDir;
46
+ return new Promise((resolve, reject) => {
47
+ writeFile(path, JSON.stringify(item, null, 2), (err) => err ? reject(err) : resolve(item));
48
+ });
49
+ },
50
+ };
51
+ };
@@ -0,0 +1,2 @@
1
+ import { dynamoUnversionedDocumentStore } from './dynamodb';
2
+ export declare type UnversionedDocumentStoreCreator = typeof dynamoUnversionedDocumentStore;
@@ -1,17 +1,12 @@
1
- import { ConfigProviderForConfig } from '../../config';
2
- import { Track } from '../../profile';
3
- import { TDocument, VersionedDocumentAuthor } from '.';
4
- declare type DynamoConfig = {
5
- tableName: string;
6
- };
1
+ import { Config } from '..';
2
+ import { ConfigProviderForConfig } from '../../../config';
3
+ import { VersionedDocumentAuthor, VersionedTDocument } from '.';
7
4
  interface Initializer<C> {
8
5
  configSpace?: C;
9
6
  }
10
- export declare const dynamoVersionedDocumentStore: <C extends string = "dynamodb">(initializer?: Initializer<C> | undefined) => <T extends TDocument<T>>() => (configProvider: { [key in C]: {
11
- tableName: import("../../config").ConfigValueProvider<string>;
12
- }; }) => <K extends keyof T, A extends ((...a: any[]) => Promise<VersionedDocumentAuthor>) | undefined>({ profile }: {
13
- profile: Track;
14
- }, hashKey: K, getAuthor: A) => {
7
+ export declare const dynamoVersionedDocumentStore: <C extends string = "dynamodb">(initializer?: Initializer<C> | undefined) => <T extends VersionedTDocument<T>>() => (configProvider: { [key in C]: {
8
+ tableName: import("../../../config").ConfigValueProvider<string>;
9
+ }; }) => <K extends keyof T, A extends ((...a: any[]) => Promise<VersionedDocumentAuthor>) | undefined>(_: {}, hashKey: K, getAuthor: A) => {
15
10
  loadAllDocumentsTheBadWay: () => Promise<T[]>;
16
11
  getVersions: (id: T[K], startVersion?: number | undefined) => Promise<{
17
12
  items: T[];
@@ -1,58 +1,15 @@
1
1
  import { DynamoDB, PutItemCommand, QueryCommand, ScanCommand } from '@aws-sdk/client-dynamodb';
2
- import { once } from '../..';
3
- import { resolveConfigValue } from '../../config';
4
- import { ifDefined, isPlainObject } from '../../guards';
2
+ import { once } from '../../..';
3
+ import { resolveConfigValue } from '../../../config';
4
+ import { ifDefined } from '../../../guards';
5
+ import { decodeDynamoDocument, encodeDynamoAttribute, encodeDynamoDocument } from '../dynamoEncoding';
5
6
  const dynamodb = once(() => new DynamoDB({ apiVersion: '2012-08-10' }));
6
- const encodeDynamoAttribute = (value) => {
7
- if (typeof value === 'string') {
8
- return { S: value };
9
- }
10
- if (typeof value === 'number') {
11
- return { N: value.toString() };
12
- }
13
- if (typeof value === 'boolean') {
14
- return { BOOL: value };
15
- }
16
- if (value === null) {
17
- return { NULL: true };
18
- }
19
- if (value instanceof Array) {
20
- return { L: value.map(encodeDynamoAttribute) };
21
- }
22
- if (isPlainObject(value)) {
23
- return { M: encodeDynamoDocument(value) };
24
- }
25
- throw new Error(`unknown attribute type ${typeof value} with value ${value}.`);
26
- };
27
- const encodeDynamoDocument = (base) => Object.fromEntries(Object.entries(base).map(([key, value]) => ([key, encodeDynamoAttribute(value)])));
28
- const decodeDynamoAttribute = (value) => {
29
- if (value.S !== undefined) {
30
- return value.S;
31
- }
32
- if (value.N !== undefined) {
33
- return parseFloat(value.N);
34
- }
35
- if (value.BOOL !== undefined) {
36
- return value.BOOL;
37
- }
38
- if (value.NULL !== undefined) {
39
- return null;
40
- }
41
- if (value.L !== undefined) {
42
- return value.L.map(decodeDynamoAttribute);
43
- }
44
- if (value.M !== undefined) {
45
- return decodeDynamoDocument(value.M);
46
- }
47
- throw new Error(`unknown attribute type: ${JSON.stringify(value)}.`);
48
- };
49
- const decodeDynamoDocument = (document) => Object.fromEntries(Object.entries(document).map(([key, value]) => ([key, decodeDynamoAttribute(value)])));
50
7
  // i'm not really excited about getAuthor being required, but ts is getting confused about the type when unspecified
51
- export const dynamoVersionedDocumentStore = (initializer) => () => (configProvider) => ({ profile }, hashKey, getAuthor) => {
8
+ export const dynamoVersionedDocumentStore = (initializer) => () => (configProvider) => (_, hashKey, getAuthor) => {
52
9
  const options = ifDefined(initializer, {});
53
10
  const tableName = once(() => resolveConfigValue(configProvider[ifDefined(options.configSpace, 'dynamodb')].tableName));
54
11
  return {
55
- loadAllDocumentsTheBadWay: profile.track('versionedDocumentStore.loadAllDocumentsTheBadWay', () => async () => {
12
+ loadAllDocumentsTheBadWay: async () => {
56
13
  const loadAllResults = async (ExclusiveStartKey) => {
57
14
  var _a;
58
15
  const cmd = new ScanCommand({ TableName: await tableName(), ExclusiveStartKey });
@@ -71,8 +28,8 @@ export const dynamoVersionedDocumentStore = (initializer) => () => (configProvid
71
28
  return result;
72
29
  }, new Map()));
73
30
  return Array.from(allResults.values());
74
- }),
75
- getVersions: profile.track('versionedDocumentStore.getVersions', () => async (id, startVersion) => {
31
+ },
32
+ getVersions: async (id, startVersion) => {
76
33
  const cmd = new QueryCommand({
77
34
  TableName: await tableName(),
78
35
  KeyConditionExpression: '#hk = :hkv',
@@ -101,8 +58,8 @@ export const dynamoVersionedDocumentStore = (initializer) => () => (configProvid
101
58
  nextPageToken: result.LastEvaluatedKey ? decodeDynamoDocument(result.LastEvaluatedKey).timestamp : undefined
102
59
  };
103
60
  });
104
- }),
105
- getItem: profile.track('versionedDocumentStore.getItem', () => async (id, timestamp) => {
61
+ },
62
+ getItem: async (id, timestamp) => {
106
63
  let keyConditionExpression = '#hk = :hkv';
107
64
  const expressionAttributeNames = {
108
65
  '#hk': hashKey.toString()
@@ -127,18 +84,18 @@ export const dynamoVersionedDocumentStore = (initializer) => () => (configProvid
127
84
  var _a;
128
85
  return (_a = result.Items) === null || _a === void 0 ? void 0 : _a.map(decodeDynamoDocument)[0];
129
86
  });
130
- }),
87
+ },
131
88
  /* prepares a new version of a document with the given data, then allows some additional
132
89
  * changes to be input to a `save` function that actually saves it. useful for additional
133
90
  * changes based on the new document version or author. the document version and author
134
91
  * cannot be modified */
135
- prepareItem: profile.track('versionedDocumentStore.prepareItem', () => async (item, ...authorArgs) => {
92
+ prepareItem: async (item, ...authorArgs) => {
136
93
  // this getAuthor type is terrible
137
94
  const author = getAuthor ? await getAuthor(...authorArgs) : authorArgs[0];
138
95
  const timestamp = new Date().getTime();
139
96
  return {
140
97
  document: { ...item, timestamp, author },
141
- save: profile.track('versionedDocumentStore.prepareItem.save', () => async (changes) => {
98
+ save: async (changes) => {
142
99
  const document = {
143
100
  ...item,
144
101
  ...changes,
@@ -151,11 +108,11 @@ export const dynamoVersionedDocumentStore = (initializer) => () => (configProvid
151
108
  });
152
109
  return dynamodb().send(cmd)
153
110
  .then(() => document);
154
- })
111
+ }
155
112
  };
156
- }),
113
+ },
157
114
  /* saves a new version of a document with the given data */
158
- putItem: profile.track('versionedDocumentStore.putItem', () => async (item, ...authorArgs) => {
115
+ putItem: async (item, ...authorArgs) => {
159
116
  // this getAuthor type is terrible
160
117
  const author = getAuthor ? await getAuthor(...authorArgs) : authorArgs[0];
161
118
  const document = {
@@ -169,6 +126,6 @@ export const dynamoVersionedDocumentStore = (initializer) => () => (configProvid
169
126
  });
170
127
  return dynamodb().send(cmd)
171
128
  .then(() => document);
172
- }),
129
+ },
173
130
  };
174
131
  };
@@ -1,19 +1,14 @@
1
- import { ConfigProviderForConfig } from '../../config';
2
- import { Track } from '../../profile';
3
- import { TDocument, VersionedDocumentAuthor } from '.';
4
- declare type Config = {
5
- tableName: string;
6
- };
1
+ import { Config } from '..';
2
+ import { ConfigProviderForConfig } from '../../../config';
3
+ import { VersionedDocumentAuthor, VersionedTDocument } from '.';
7
4
  interface Initializer<C> {
8
5
  dataDir: string;
9
6
  fs?: Pick<typeof import('fs'), 'readFile' | 'writeFile'>;
10
7
  configSpace?: C;
11
8
  }
12
- export declare const fileSystemVersionedDocumentStore: <C extends string = "fileSystem">(initializer: Initializer<C>) => <T extends TDocument<T>>() => (configProvider: { [key in C]: {
13
- tableName: import("../../config").ConfigValueProvider<string>;
14
- }; }) => <K extends keyof T, A extends ((...a: any[]) => Promise<VersionedDocumentAuthor>) | undefined>({ profile }: {
15
- profile: Track;
16
- }, hashKey: K, getAuthor: A) => {
9
+ export declare const fileSystemVersionedDocumentStore: <C extends string = "fileSystem">(initializer: Initializer<C>) => <T extends VersionedTDocument<T>>() => (configProvider: { [key in C]: {
10
+ tableName: import("../../../config").ConfigValueProvider<string>;
11
+ }; }) => <K extends keyof T, A extends ((...a: any[]) => Promise<VersionedDocumentAuthor>) | undefined>(_: {}, hashKey: K, getAuthor: A) => {
17
12
  loadAllDocumentsTheBadWay: () => Promise<T[]>;
18
13
  getVersions: (id: T[K], startVersion?: number | undefined) => Promise<{
19
14
  items: T[];