@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.
- package/dist/cjs/{fetch.d.ts → fetch/index.d.ts} +1 -1
- package/dist/{esm/middleware.d.ts → cjs/middleware/index.d.ts} +2 -2
- package/dist/cjs/misc/hashValue.d.ts +2 -3
- package/dist/cjs/{pagination.d.ts → pagination/index.d.ts} +1 -1
- package/dist/cjs/{pagination.js → pagination/index.js} +3 -3
- package/dist/cjs/routing/index.d.ts +0 -3
- package/dist/cjs/routing/index.js +7 -9
- package/dist/cjs/services/authProvider/decryption.js +5 -5
- package/dist/cjs/services/authProvider/index.d.ts +0 -2
- package/dist/cjs/services/authProvider/subrequest.js +6 -6
- package/dist/cjs/services/documentStore/dynamoEncoding.d.ts +10 -0
- package/dist/cjs/services/documentStore/dynamoEncoding.js +52 -0
- package/dist/cjs/services/documentStore/index.d.ts +14 -0
- package/dist/cjs/services/documentStore/unversioned/dynamodb.d.ts +13 -0
- package/dist/cjs/services/documentStore/unversioned/dynamodb.js +51 -0
- package/dist/cjs/services/documentStore/unversioned/file-system.d.ts +15 -0
- package/dist/cjs/services/documentStore/unversioned/file-system.js +81 -0
- package/dist/cjs/services/documentStore/unversioned/index.d.ts +2 -0
- package/dist/cjs/services/documentStore/unversioned/index.js +2 -0
- package/dist/{esm/services/versionedDocumentStore → cjs/services/documentStore/versioned}/dynamodb.d.ts +6 -11
- package/dist/cjs/services/{versionedDocumentStore → documentStore/versioned}/dynamodb.js +27 -70
- package/dist/cjs/services/{versionedDocumentStore → documentStore/versioned}/file-system.d.ts +6 -11
- package/dist/cjs/services/{versionedDocumentStore → documentStore/versioned}/file-system.js +16 -16
- package/dist/cjs/services/{versionedDocumentStore → documentStore/versioned}/index.d.ts +2 -8
- package/dist/cjs/services/documentStore/versioned/index.js +2 -0
- package/dist/cjs/services/exercisesGateway/index.d.ts +3 -6
- package/dist/cjs/services/exercisesGateway/index.js +7 -7
- package/dist/cjs/tsconfig.without-specs.cjs.tsbuildinfo +1 -1
- package/dist/cjs/types.d.ts +4 -0
- package/dist/esm/{fetch.d.ts → fetch/index.d.ts} +1 -1
- package/dist/{cjs/middleware.d.ts → esm/middleware/index.d.ts} +2 -2
- package/dist/esm/misc/hashValue.d.ts +2 -3
- package/dist/esm/{pagination.d.ts → pagination/index.d.ts} +1 -1
- package/dist/esm/{pagination.js → pagination/index.js} +3 -3
- package/dist/esm/routing/index.d.ts +0 -3
- package/dist/esm/routing/index.js +7 -9
- package/dist/esm/services/authProvider/decryption.js +5 -5
- package/dist/esm/services/authProvider/index.d.ts +0 -2
- package/dist/esm/services/authProvider/subrequest.js +6 -6
- package/dist/esm/services/documentStore/dynamoEncoding.d.ts +10 -0
- package/dist/esm/services/documentStore/dynamoEncoding.js +45 -0
- package/dist/esm/services/documentStore/index.d.ts +14 -0
- package/dist/esm/services/documentStore/unversioned/dynamodb.d.ts +13 -0
- package/dist/esm/services/documentStore/unversioned/dynamodb.js +47 -0
- package/dist/esm/services/documentStore/unversioned/file-system.d.ts +15 -0
- package/dist/esm/services/documentStore/unversioned/file-system.js +51 -0
- package/dist/esm/services/documentStore/unversioned/index.d.ts +2 -0
- package/dist/esm/services/documentStore/unversioned/index.js +1 -0
- package/dist/{cjs/services/versionedDocumentStore → esm/services/documentStore/versioned}/dynamodb.d.ts +6 -11
- package/dist/esm/services/{versionedDocumentStore → documentStore/versioned}/dynamodb.js +17 -60
- package/dist/esm/services/{versionedDocumentStore → documentStore/versioned}/file-system.d.ts +6 -11
- package/dist/esm/services/{versionedDocumentStore → documentStore/versioned}/file-system.js +16 -16
- package/dist/esm/services/{versionedDocumentStore → documentStore/versioned}/index.d.ts +2 -8
- package/dist/esm/services/documentStore/versioned/index.js +1 -0
- package/dist/esm/services/exercisesGateway/index.d.ts +3 -6
- package/dist/esm/services/exercisesGateway/index.js +7 -7
- package/dist/esm/tsconfig.without-specs.esm.tsbuildinfo +1 -1
- package/dist/esm/types.d.ts +4 -0
- package/package.json +118 -13
- package/dist/cjs/profile.d.ts +0 -61
- package/dist/cjs/profile.js +0 -199
- package/dist/esm/profile.d.ts +0 -61
- package/dist/esm/profile.js +0 -191
- /package/dist/cjs/{assertions.d.ts → assertions/index.d.ts} +0 -0
- /package/dist/cjs/{assertions.js → assertions/index.js} +0 -0
- /package/dist/cjs/{errors.d.ts → errors/index.d.ts} +0 -0
- /package/dist/cjs/{errors.js → errors/index.js} +0 -0
- /package/dist/cjs/{fetch.js → fetch/index.js} +0 -0
- /package/dist/cjs/{guards.d.ts → guards/index.d.ts} +0 -0
- /package/dist/cjs/{guards.js → guards/index.js} +0 -0
- /package/dist/cjs/{middleware.js → middleware/index.js} +0 -0
- /package/dist/cjs/services/{versionedDocumentStore → documentStore}/index.js +0 -0
- /package/dist/esm/{assertions.d.ts → assertions/index.d.ts} +0 -0
- /package/dist/esm/{assertions.js → assertions/index.js} +0 -0
- /package/dist/esm/{errors.d.ts → errors/index.d.ts} +0 -0
- /package/dist/esm/{errors.js → errors/index.js} +0 -0
- /package/dist/esm/{fetch.js → fetch/index.js} +0 -0
- /package/dist/esm/{guards.d.ts → guards/index.d.ts} +0 -0
- /package/dist/esm/{guards.js → guards/index.js} +0 -0
- /package/dist/esm/{middleware.js → middleware/index.js} +0 -0
- /package/dist/esm/services/{versionedDocumentStore → documentStore}/index.js +0 -0
package/dist/cjs/types.d.ts
CHANGED
|
@@ -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,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
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> :
|
|
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 '
|
|
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 '
|
|
2
|
-
import { InvalidRequestError } from '
|
|
3
|
-
import { renderAnyRouteUrl } from '
|
|
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,
|
|
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
|
|
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,
|
|
162
|
+
const executor = mapFind(boundRoutes, (route) => route(request, logger));
|
|
165
163
|
if (executor) {
|
|
166
164
|
const result = boundResponseMiddleware ?
|
|
167
|
-
boundResponseMiddleware(executor(), { request,
|
|
165
|
+
boundResponseMiddleware(executor(), { request, logger }) : executor();
|
|
168
166
|
if (isPromise(result) && errorHandler) {
|
|
169
167
|
const errorHandlerWithMiddleware = (e) => boundResponseMiddleware ?
|
|
170
|
-
boundResponseMiddleware(errorHandler(e, logger), { request,
|
|
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,
|
|
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,
|
|
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
|
|
11
|
+
return ({ request }) => {
|
|
12
12
|
let user;
|
|
13
|
-
const getAuthorizedFetchConfig =
|
|
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 =
|
|
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
|
|
9
|
+
return ({ request }) => {
|
|
10
10
|
let user;
|
|
11
|
-
const getAuthorizedFetchConfig =
|
|
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 =
|
|
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
|
|
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 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,17 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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
|
|
11
|
-
tableName: import("
|
|
12
|
-
}; }) => <K extends keyof T, A extends ((...a: any[]) => Promise<VersionedDocumentAuthor>) | undefined>({
|
|
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 '
|
|
4
|
-
import { ifDefined
|
|
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) => (
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
};
|
package/dist/esm/services/{versionedDocumentStore → documentStore/versioned}/file-system.d.ts
RENAMED
|
@@ -1,19 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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
|
|
13
|
-
tableName: import("
|
|
14
|
-
}; }) => <K extends keyof T, A extends ((...a: any[]) => Promise<VersionedDocumentAuthor>) | undefined>({
|
|
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[];
|