@openstax/ts-utils 1.8.0 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/cjs/misc/hashValue.d.ts +2 -3
  2. package/dist/cjs/services/documentStore/dynamoEncoding.d.ts +10 -0
  3. package/dist/cjs/services/documentStore/dynamoEncoding.js +52 -0
  4. package/dist/cjs/services/documentStore/index.d.ts +14 -0
  5. package/dist/cjs/services/documentStore/unversioned/dynamodb.d.ts +14 -0
  6. package/dist/cjs/services/documentStore/unversioned/dynamodb.js +58 -0
  7. package/dist/cjs/services/documentStore/unversioned/file-system.d.ts +16 -0
  8. package/dist/cjs/services/documentStore/unversioned/file-system.js +85 -0
  9. package/dist/cjs/services/documentStore/unversioned/index.d.ts +2 -0
  10. package/dist/cjs/services/documentStore/unversioned/index.js +2 -0
  11. package/dist/cjs/services/{versionedDocumentStore → documentStore/versioned}/dynamodb.d.ts +5 -7
  12. package/dist/cjs/services/{versionedDocumentStore → documentStore/versioned}/dynamodb.js +14 -57
  13. package/dist/{esm/services/versionedDocumentStore → cjs/services/documentStore/versioned}/file-system.d.ts +5 -7
  14. package/dist/cjs/services/{versionedDocumentStore → documentStore/versioned}/file-system.js +3 -3
  15. package/dist/cjs/services/{versionedDocumentStore → documentStore/versioned}/index.d.ts +2 -8
  16. package/dist/cjs/services/documentStore/versioned/index.js +2 -0
  17. package/dist/cjs/tsconfig.without-specs.cjs.tsbuildinfo +1 -1
  18. package/dist/esm/misc/hashValue.d.ts +2 -3
  19. package/dist/esm/services/documentStore/dynamoEncoding.d.ts +10 -0
  20. package/dist/esm/services/documentStore/dynamoEncoding.js +45 -0
  21. package/dist/esm/services/documentStore/index.d.ts +14 -0
  22. package/dist/esm/services/documentStore/unversioned/dynamodb.d.ts +14 -0
  23. package/dist/esm/services/documentStore/unversioned/dynamodb.js +54 -0
  24. package/dist/esm/services/documentStore/unversioned/file-system.d.ts +16 -0
  25. package/dist/esm/services/documentStore/unversioned/file-system.js +55 -0
  26. package/dist/esm/services/documentStore/unversioned/index.d.ts +2 -0
  27. package/dist/esm/services/documentStore/unversioned/index.js +1 -0
  28. package/dist/esm/services/{versionedDocumentStore → documentStore/versioned}/dynamodb.d.ts +5 -7
  29. package/dist/esm/services/{versionedDocumentStore → documentStore/versioned}/dynamodb.js +4 -47
  30. package/dist/{cjs/services/versionedDocumentStore → esm/services/documentStore/versioned}/file-system.d.ts +5 -7
  31. package/dist/esm/services/{versionedDocumentStore → documentStore/versioned}/file-system.js +3 -3
  32. package/dist/esm/services/{versionedDocumentStore → documentStore/versioned}/index.d.ts +2 -8
  33. package/dist/esm/services/documentStore/versioned/index.js +1 -0
  34. package/dist/esm/tsconfig.without-specs.esm.tsbuildinfo +1 -1
  35. package/package.json +15 -5
  36. /package/dist/cjs/services/{versionedDocumentStore → documentStore}/index.js +0 -0
  37. /package/dist/esm/services/{versionedDocumentStore → documentStore}/index.js +0 -0
@@ -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 {};
@@ -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,14 @@
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
+ batchGetItem: (ids: T[K][]) => Promise<T[]>;
11
+ getItem: (id: T[K]) => Promise<T | undefined>;
12
+ putItem: (item: T) => Promise<T>;
13
+ };
14
+ export {};
@@ -0,0 +1,54 @@
1
+ import { BatchGetItemCommand, DynamoDB, GetItemCommand, PutItemCommand, 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, _b;
14
+ const cmd = new ScanCommand({ TableName: await tableName(), ExclusiveStartKey });
15
+ const result = await dynamodb().send(cmd);
16
+ const resultItems = (_b = (_a = result.Items) === null || _a === void 0 ? void 0 : _a.map((item) => decodeDynamoDocument(item))) !== null && _b !== void 0 ? _b : [];
17
+ if (result.LastEvaluatedKey) {
18
+ return [...resultItems, ...await loadAllResults(result.LastEvaluatedKey)];
19
+ }
20
+ return resultItems;
21
+ };
22
+ return loadAllResults();
23
+ },
24
+ batchGetItem: async (ids) => {
25
+ const table = await tableName();
26
+ const key = hashKey.toString();
27
+ const getBatches = async (requestItems) => {
28
+ const cmd = new BatchGetItemCommand({
29
+ RequestItems: requestItems !== null && requestItems !== void 0 ? requestItems : { [table]: { Keys: ids.map((id) => ({ [key]: encodeDynamoAttribute(id) })) } },
30
+ });
31
+ const response = await dynamodb().send(cmd);
32
+ const currentResponses = response.Responses ?
33
+ response.Responses[table].map(response => decodeDynamoDocument(response)) : [];
34
+ return currentResponses.concat(response.UnprocessedKeys ? await getBatches(response.UnprocessedKeys) : []);
35
+ };
36
+ return getBatches();
37
+ },
38
+ getItem: async (id) => {
39
+ const cmd = new GetItemCommand({
40
+ Key: { [hashKey.toString()]: encodeDynamoAttribute(id) },
41
+ TableName: await tableName(),
42
+ });
43
+ return dynamodb().send(cmd).then(result => result.Item ? decodeDynamoDocument(result.Item) : undefined);
44
+ },
45
+ /* saves a new version of a document with the given data */
46
+ putItem: async (item) => {
47
+ const cmd = new PutItemCommand({
48
+ TableName: await tableName(),
49
+ Item: encodeDynamoDocument(item),
50
+ });
51
+ return dynamodb().send(cmd).then(() => item);
52
+ },
53
+ };
54
+ };
@@ -0,0 +1,16 @@
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
+ batchGetItem: (ids: T[K][]) => Promise<Exclude<Awaited<T>, undefined>[]>;
13
+ getItem: (id: T[K]) => Promise<T | undefined>;
14
+ putItem: (item: T) => Promise<T>;
15
+ };
16
+ export {};
@@ -0,0 +1,55 @@
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
+ batchGetItem: async (ids) => {
43
+ const items = await Promise.all(ids.map((id) => load(hashFilename(id))));
44
+ return items.filter(isDefined);
45
+ },
46
+ getItem: (id) => load(hashFilename(id)),
47
+ putItem: async (item) => {
48
+ const path = await filePath(hashFilename(item[hashKey]));
49
+ await mkTableDir;
50
+ return new Promise((resolve, reject) => {
51
+ writeFile(path, JSON.stringify(item, null, 2), (err) => err ? reject(err) : resolve(item));
52
+ });
53
+ },
54
+ };
55
+ };
@@ -0,0 +1,2 @@
1
+ import { dynamoUnversionedDocumentStore } from './dynamodb';
2
+ export declare type UnversionedDocumentStoreCreator = typeof dynamoUnversionedDocumentStore;
@@ -1,13 +1,11 @@
1
- import { ConfigProviderForConfig } from '../../config';
2
- import { TDocument, VersionedDocumentAuthor } from '.';
3
- declare type DynamoConfig = {
4
- tableName: string;
5
- };
1
+ import { Config } from '..';
2
+ import { ConfigProviderForConfig } from '../../../config';
3
+ import { VersionedDocumentAuthor, VersionedTDocument } from '.';
6
4
  interface Initializer<C> {
7
5
  configSpace?: C;
8
6
  }
9
- export declare const dynamoVersionedDocumentStore: <C extends string = "dynamodb">(initializer?: Initializer<C> | undefined) => <T extends TDocument<T>>() => (configProvider: { [key in C]: {
10
- tableName: import("../../config").ConfigValueProvider<string>;
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>;
11
9
  }; }) => <K extends keyof T, A extends ((...a: any[]) => Promise<VersionedDocumentAuthor>) | undefined>(_: {}, hashKey: K, getAuthor: A) => {
12
10
  loadAllDocumentsTheBadWay: () => Promise<T[]>;
13
11
  getVersions: (id: T[K], startVersion?: number | undefined) => Promise<{
@@ -1,52 +1,9 @@
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
8
  export const dynamoVersionedDocumentStore = (initializer) => () => (configProvider) => (_, hashKey, getAuthor) => {
52
9
  const options = ifDefined(initializer, {});
@@ -1,15 +1,13 @@
1
- import { ConfigProviderForConfig } from '../../config';
2
- import { TDocument, VersionedDocumentAuthor } from '.';
3
- declare type Config = {
4
- tableName: string;
5
- };
1
+ import { Config } from '..';
2
+ import { ConfigProviderForConfig } from '../../../config';
3
+ import { VersionedDocumentAuthor, VersionedTDocument } from '.';
6
4
  interface Initializer<C> {
7
5
  dataDir: string;
8
6
  fs?: Pick<typeof import('fs'), 'readFile' | 'writeFile'>;
9
7
  configSpace?: C;
10
8
  }
11
- export declare const fileSystemVersionedDocumentStore: <C extends string = "fileSystem">(initializer: Initializer<C>) => <T extends TDocument<T>>() => (configProvider: { [key in C]: {
12
- tableName: import("../../config").ConfigValueProvider<string>;
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>;
13
11
  }; }) => <K extends keyof T, A extends ((...a: any[]) => Promise<VersionedDocumentAuthor>) | undefined>(_: {}, hashKey: K, getAuthor: A) => {
14
12
  loadAllDocumentsTheBadWay: () => Promise<T[]>;
15
13
  getVersions: (id: T[K], startVersion?: number | undefined) => Promise<{
@@ -1,8 +1,8 @@
1
1
  import * as fsModule from 'fs';
2
2
  import path from 'path';
3
- import { hashValue } from '../..';
4
- import { resolveConfigValue } from '../../config';
5
- import { ifDefined } from '../../guards';
3
+ import { hashValue } from '../../..';
4
+ import { resolveConfigValue } from '../../../config';
5
+ import { ifDefined } from '../../../guards';
6
6
  const PAGE_LIMIT = 5;
7
7
  export const fileSystemVersionedDocumentStore = (initializer) => () => (configProvider) => (_, hashKey, getAuthor) => {
8
8
  const tableName = resolveConfigValue(configProvider[initializer.configSpace || 'fileSystem'].tableName);
@@ -1,9 +1,5 @@
1
+ import { TDocument } from '..';
1
2
  import { dynamoVersionedDocumentStore } from './dynamodb';
2
- export declare type DocumentBaseValueTypes = number | boolean | string | null | DocumentBaseMapType | DocumentBaseListType;
3
- export declare type DocumentBaseMapType = {
4
- [key: string]: DocumentBaseValueTypes;
5
- };
6
- export declare type DocumentBaseListType = DocumentBaseValueTypes[];
7
3
  export declare type VersionedDocumentAuthor = {
8
4
  type: 'user';
9
5
  uuid: string;
@@ -17,7 +13,5 @@ export declare type VersionedDocumentRequiredFields = {
17
13
  timestamp: number;
18
14
  author: VersionedDocumentAuthor;
19
15
  };
20
- export declare type TDocument<T> = {
21
- [k in keyof T]: DocumentBaseValueTypes;
22
- } & VersionedDocumentRequiredFields;
16
+ export declare type VersionedTDocument<T> = TDocument<T> & VersionedDocumentRequiredFields;
23
17
  export declare type VersionedDocumentStoreCreator = typeof dynamoVersionedDocumentStore;
@@ -0,0 +1 @@
1
+ export {};