From f7b222dde902ee1c552d0636608b8e62c900601f Mon Sep 17 00:00:00 2001 From: Mike Roberts Date: Sat, 16 Sep 2023 19:17:59 -0400 Subject: [PATCH] BREAKING CHANGE - updates to Store setup Breaking changes: * Now only one DynamoDB wrapper / client per store instance * A whole bunch of changes to store setup and config Also documentation for setup The driver for this was that configuration with multiple DynamoDB clients was messy, and I don't think is that useful. For different clients the user can just instantiate different instances of the entity store. --- README.md | 39 ++-- documentation/2-Setup.md | 3 - documentation/{1-Entities.md => Entities.md} | 6 +- documentation/Setup.md | 217 ++++++++++++++++++ .../{3-SimpleUsage.md => SimpleUsage.md} | 0 documentation/manual.md | 6 +- examples/src/example1Sheep.ts | 4 +- examples/src/example2Chickens.ts | 4 +- examples/src/example3Farms.ts | 13 +- src/lib/dynamoDBInterface.ts | 11 +- src/lib/entities.ts | 2 +- src/lib/internal/entityContext.ts | 28 +-- .../tableBackedMultipleEntityOperations.ts | 6 +- ...bleBackedSingleEntityAdvancedOperations.ts | 7 +- .../tableBackedSingleEntityOperations.ts | 4 +- .../tableBackedConfigurationResolver.ts | 89 +++---- .../tableBackedGetTransactionBuilder.ts | 6 +- .../tableBackedWriteTransactionBuilder.ts | 6 +- src/lib/support/configSupport.ts | 162 ------------- src/lib/support/index.ts | 2 +- src/lib/support/setupSupport.ts | 117 ++++++++++ src/lib/tableBackedStore.ts | 10 +- src/lib/tableBackedStoreConfiguration.ts | 45 ++-- .../customTable/farm-table.test.ts | 30 ++- .../multiTable/farmAndAnimals.test.ts | 50 ++-- test/integration/singleTable/sheep.test.ts | 15 +- .../testSupportCode/appEnvironment.ts | 81 ++++--- .../testSupportCode/awsEnvironment.ts | 11 +- test/unit/internal/entityContext.test.ts | 55 +++-- test/unit/internal/operationsCommon.test.ts | 2 +- test/unit/internal/putOperations.test.ts | 8 +- test/unit/internal/updateOperations.test.ts | 2 +- test/unit/{ => support}/entitySupport.test.ts | 4 +- test/unit/{ => support}/querySupport.test.ts | 2 +- test/unit/support/setupSupport.test.ts | 150 ++++++++++++ test/unit/tableBackedEntityStore.test.ts | 21 +- .../testSupportCode/entityContextSupport.ts | 29 ++- .../{ => testSupportCode}/fakes/fakeClock.ts | 2 +- .../fakes/fakeDynamoDBInterface.ts | 2 +- .../{ => testSupportCode}/fakes/fakeLogger.ts | 2 +- .../fakes/fakeSupport.ts | 0 41 files changed, 757 insertions(+), 496 deletions(-) delete mode 100644 documentation/2-Setup.md rename documentation/{1-Entities.md => Entities.md} (95%) create mode 100644 documentation/Setup.md rename documentation/{3-SimpleUsage.md => SimpleUsage.md} (100%) delete mode 100644 src/lib/support/configSupport.ts create mode 100644 src/lib/support/setupSupport.ts rename test/unit/{ => support}/entitySupport.test.ts (81%) rename test/unit/{ => support}/querySupport.test.ts (95%) create mode 100644 test/unit/support/setupSupport.test.ts rename test/unit/{ => testSupportCode}/fakes/fakeClock.ts (79%) rename test/unit/{ => testSupportCode}/fakes/fakeDynamoDBInterface.ts (97%) rename test/unit/{ => testSupportCode}/fakes/fakeLogger.ts (92%) rename test/unit/{ => testSupportCode}/fakes/fakeSupport.ts (100%) diff --git a/README.md b/README.md index fe9009d..1ee74ad 100644 --- a/README.md +++ b/README.md @@ -75,16 +75,18 @@ We are using a ["Single Table Design"](https://www.alexdebrie.com/posts/dynamodb Now we add / install Entity Store in the usual way [from NPM](https://www.npmjs.com/package/@symphoniacloud/dynamodb-entity-store) , e.g. -```% npm install @symphoniacloud/dynamodb-entity-store``` +``` +% npm install @symphoniacloud/dynamodb-entity-store +``` Let's assume that our DynamoDB Table is named `AnimalsTable`. We can create an entity store by using the [`createStore`](https://symphoniacloud.github.io/dynamodb-entity-store/functions/createStore.html) -and [`createStandardSingleTableStoreConfig`](https://symphoniacloud.github.io/dynamodb-entity-store/functions/createStandardSingleTableStoreConfig.html) functions: +and [`createStandardSingleTableConfig`](https://symphoniacloud.github.io/dynamodb-entity-store/functions/createStandardSingleTableConfig.html) functions: ```typescript -const entityStore = createStore(createStandardSingleTableStoreConfig('AnimalsTable')) +const entityStore = createStore(createStandardSingleTableConfig('AnimalsTable')) ``` `entityStore` is an object that implements @@ -116,7 +118,7 @@ We only need to create this object **once per type** of entity in our applicatio * **Optional:** express how to convert an object to a DynamoDB record ("formatting") * **Optional:** Create Global Secondary Index (GSI) key values -> A complete discussion of _Entities_ is available in [the manual, here](./documentation/1-Entities.md). +> A complete discussion of _Entities_ is available in [the manual, here](./documentation/Entities.md). We can now call `.for(...)` on our entity store. This returns an object that implements [`SingleEntityOperations`](https://symphoniacloud.github.io/dynamodb-entity-store/interfaces/SingleEntityOperations.html) - **this is the object that you'll likely work with most when using this library**. @@ -223,9 +225,8 @@ When you're working on setting up your entities and queries you'll often want to doing. You can do this by turning on logging: ```typescript -const config = createStandardSingleTableStoreConfig('AnimalsTable') -config.store.logger = consoleLogger -const entityStore = createStore(config) +const config = createStandardSingleTableConfig('AnimalsTable') +const entityStore = createStore(createStandardSingleTableConfig('AnimalsTable'), { logger: consoleLogger }) ``` With this turned on we can see the output from our last query: @@ -313,7 +314,7 @@ Write operations are no different than before - Entity Store handles generating generator functions. So if we have the following... ```typescript -const entityStore = createStore(createStandardSingleTableStoreConfig('AnimalsTable')) +const entityStore = createStore(createStandardSingleTableConfig('AnimalsTable')) const chickenStore = entityStore.for(CHICKEN_ENTITY) await chickenStore.put({ breed: 'sussex', name: 'ginger', dateOfBirth: '2021-07-01', coop: 'bristol' }) @@ -403,16 +404,11 @@ We create the entity store using a custom configuration: ```typescript const entityStore = createStore( - createSingleTableConfiguration({ - tableName: 'FarmTable', - metaAttributeNames: { pk: 'Name' } - }) + createMinimumSingleTableConfig('FarmTable', { pk: 'Name' }) ) ``` -We're only using one table (here "single table" just means one table, rather than the "standard" configuration), and we -override -the `metaAttributeNames` settings to only store the partition key, with the correct attribute name. +> See [_Setup_ in the manual](documentation/Setup.md) for more on custom table configuration. The `Entity` this time is a bit more complicated: @@ -496,16 +492,13 @@ results in: Error: Scan operations are disabled for this store ``` -However, we can change our store configuration to be the following: +However, we can change our table configuration to be the following: ```typescript -const entityStore = createStore( - createSingleTableConfiguration({ - tableName: 'FarmTable', - metaAttributeNames: { pk: 'Name' }, - allowScans: true - }) -) +const entityStore = createStore({ + ...createMinimumSingleTableConfig('FarmTable', { pk: 'Name' }), + allowScans: true +}) ``` and now we can run our scan: diff --git a/documentation/2-Setup.md b/documentation/2-Setup.md deleted file mode 100644 index 5e6d463..0000000 --- a/documentation/2-Setup.md +++ /dev/null @@ -1,3 +0,0 @@ -# Chapter 2 - Setup - -Coming soon! \ No newline at end of file diff --git a/documentation/1-Entities.md b/documentation/Entities.md similarity index 95% rename from documentation/1-Entities.md rename to documentation/Entities.md index 3f27e58..67cdfc4 100644 --- a/documentation/1-Entities.md +++ b/documentation/Entities.md @@ -136,7 +136,9 @@ function pk({ name }: Pick) { ### `.convertToDynamoFormat()` (optional) -`convertToDynamoFormat()` is an optional function you may choose to implement in order to change how DynamoDB Entity Store writes an object to DynamoDB during `put` operations. Since DynamoDB Entity Store uses the [AWS Document Client](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-lib-dynamodb/) library under the covers, this is more about choosing which fields to save, and any field-level modification, rather than lower-level "marshalling". If you need to change marshalling options at the AWS library level please refer to the [Setup chapter](2-Setup.md). +`convertToDynamoFormat()` is an optional function you may choose to implement in order to change how DynamoDB Entity Store writes an object to DynamoDB during `put` operations. +Since DynamoDB Entity Store uses the [AWS Document Client](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-lib-dynamodb/) library under the covers, this is more about choosing which fields to save, and any field-level modification, rather than lower-level "marshalling". +If you need to change marshalling options at the AWS library level please refer to the [Setup chapter](Setup.md). By default DynamoDB Entity Store will store all the fields of an object, unmanipulated, using the field names of the object. E.g. going back to our `Sheep` example, let's say we're writing the following object: @@ -214,7 +216,7 @@ The `gsis` field defines _generator_ functions for all of the Global Secondary I The type of `gsis` is `Record`, a map from a GSI identifier to a GSI PK generator, and optionally a GSI SK generator. -The **GSI identifier** will typically be the same as, or similar to, the name of your actual DynamoDB GSI. The mapping from _Entity_ GSI ID to DynamoDB GSI Name is configured in [Table Setup](2-Setup.md), but as an example the "standard" configuration uses `gsi` as the _Entity_ GSI ID, and `GSI` for the corresponding index name. +The **GSI identifier** will typically be the same as, or similar to, the name of your actual DynamoDB GSI. The mapping from _Entity_ GSI ID to DynamoDB GSI Name is configured in [Table Setup](Setup.md), but as an example the "standard" configuration uses `gsi` as the _Entity_ GSI ID, and `GSI` for the corresponding index name. If you understand the table `pk()` and `sk()` generators then you'll understand the GSI Generators too. See the [example in the project README](https://github.com/symphoniacloud/dynamodb-entity-store/blob/main/README.md#example-2-adding-a-global-secondary-index) for an example. diff --git a/documentation/Setup.md b/documentation/Setup.md new file mode 100644 index 0000000..28b705d --- /dev/null +++ b/documentation/Setup.md @@ -0,0 +1,217 @@ +# Chapter 2 - Setup + +## Library and Module + +[_DynamoDB Entity Store_](https://github.com/symphoniacloud/dynamodb-entity-store) is available [from NPM](https://www.npmjs.com/package/@symphoniacloud/dynamodb-entity-store), and can be installed in the usual way, e.g.: + +``` +% npm install @symphoniacloud/dynamodb-entity-store +``` + +The library is provided in both CommonJS and ESModule form. All entrypoints are available from the root _index.js_ file. + +> _I tried using package.json [exports](https://nodejs.org/api/packages.html#exports) but IDE support seems flakey, so I've reverted for now to just supporting a root "barrel" file_ + +## Instantiating Entity Store + +The main entry point for Entity Store is the function [`createStore(config)`](https://symphoniacloud.github.io/dynamodb-entity-store/functions/createStore.html). This function returns an instance of the [`AllEntitiesStore`](https://symphoniacloud.github.io/dynamodb-entity-store/interfaces/AllEntitiesStore.html) type which you can use to perform operations on your DynamoDB table(s). + +`createStore(config)` takes one required argument and one optional argument: +* `tablesConfig` defines the names and configuration of all the tables you want to access through an instance of the Store. +* `context` provides implementations of various behaviors. If you don't use it then defaults are used. + +For some scenarios using all the default values of DynamoDB Entity Store will be sufficient. In such a case you can instantiate your store as follows: + +```typescript +const entityStore = createStore(createStandardSingleTableConfig('AnimalsTable')) // "AnimalsTable" is an example +``` + +Typically though you'll need to change behavior in some form. I'll start with describing how to update `context`. + +### Overriding `context` + +`context` is an object of type [`TableBackedStoreContext`](https://symphoniacloud.github.io/dynamodb-entity-store/interfaces/TableBackedStoreContext.html), defined as follows: + +```typescript +{ + logger: EntityStoreLogger + dynamoDB: DynamoDBInterface + clock: Clock +} +``` + +If you don't specify a context when calling `createStore()` then the default values are used, as follows: + +* `logger` : No-op logger (Don't log) +* `dynamoDB` : Wrapper using default DynamoDB document client. (See below for details) +* `clock` : Real clock based on system time (it can be useful to override this in tests) + +Use the [`createStoreContext()`](https://symphoniacloud.github.io/dynamodb-entity-store/functions/createStoreContext.html) function to create a context with different values. +With no arguments it provides precisely the same default values, but you can provide overrides as necessary. Here are a few such scenarios. + +#### Overriding the DynamoDB Document Client or DynamoDB wrapper + +By default DynamoDB Entity Store uses the default [DynamoDB Document Client](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-lib-dynamodb/) object, which uses the AWS account and region in the current context (e.g. from environment variables) and default marshalling / unmarshalling (see the official [AWS Documentation](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-lib-dynamodb/) for more details). + +If you want to override any of this behavior you can provide your own Document Client object as the second argument to `createStoreContext()`. + +For example to override the region you might call the following: + +```typescript +const storeContext = createStoreContext({}, DynamoDBDocumentClient.from(new DynamoDBClient({ region: 'us-east-1' }))) +``` + +DynamoDB Entity Store uses a "wrapper" around the Document Client, which is the `dynamoDB` property on the Store Context. You can also override this, but typically you'd only do so for unit / in-process tests. + +#### Specifying a logger + +DynamoDB Entity Store will log various behavior at **debug level**. You can override the library's logger when calling `createStoreContext()`, e.g. `createStoreContext({ logger: consoleLogger })`. + +The default implementation is a "no-op" logger, i.e. don't actually log anywhere. +However you can instead use the [`consoleLogger`](https://symphoniacloud.github.io/dynamodb-entity-store/variables/consoleLogger.html), or you can provide your own implementation of [`EntityStoreLogger`](https://symphoniacloud.github.io/dynamodb-entity-store/interfaces/EntityStoreLogger.html). +E.g. here's an implementation that uses the [AWS Powertools Logger](https://docs.powertools.aws.dev/lambda/typescript/latest/core/logger/) : + +```typescript +// Create an implementation of DynamoDB Entity Store Logger using an underlying AWS Powertools Logger +function createPowertoolsEntityStoreLogger(logger: Logger): EntityStoreLogger { + return { + getLevelName() { + return logger.getLevelName() + }, + debug(input: LogItemMessage, ...extraInput) { + logger.debug(input, ...extraInput) + } + } +} +``` + +#### Overriding the Clock + +DynamoDB Entity Store uses a clock when generating the Last Updated field on items. By default this is the system clock, but you +can override this - typically you'd only want to do so in tests. For an example see [`FakeClock`](https://github.com/symphoniacloud/dynamodb-entity-store/blob/main/test/unit/testSupportCode/fakes/fakeClock.ts) in the project's own test code. + +### Configuring Tables + +DynamoDB entity store can use one or more tables when performing operations. +You specify your entire table configuration as the first argument of `createStore(config)`. +I'll first explain how to configure Entity Store when using one table, and then will expand this to why and how you might want to configure multiple tables. + +#### Single-table Configuration + +A single table is configured using the `TableConfig` interface: + +```typescript +export interface TableConfig { + tableName: string + metaAttributeNames: { + pk: string + sk?: string + gsisById?: Record + ttl?: string + entityType?: string + lastUpdated?: string + } + allowScans?: boolean + gsiNames?: Record +} +``` + +If you want you can "hand-roll" this object, however there are support functions in [_setupSupport.ts_](../src/lib/support/setupSupport.ts) to help out. + +For example, say you want to use a "standard single table" configuration. To create one of these you can call `createStandardSingleTableConfig()`, just passing your underlying table name. The resulting configuration will be as follows: + +```typescript +{ + tableName: 'testTable', + allowScans: false, + metaAttributeNames: { + pk: 'PK', + sk: 'SK', + ttl: 'ttl', + entityType: '_et', + lastUpdated: '_lastUpdated', + gsisById: { + gsi: { + pk: 'GSIPK', + sk: 'GSISK' + } + } + }, + gsiNames: { + gsi: 'GSI' + } +} +``` + +This configuration is valid when: + +* Your table partition key attribute is named `PK` +* You have a table sort key and the attribute is named `SK` +* You have one GSI (Global Secondary Index) which is named `GSI`. It has a string partition key named `GSIPK` and a string sort key named `GSISK`. You reference this in entities using the "logical" ID `gsi`. +* You want to automatically create `_et` and `_lastUpdated` attributes for each item. +* If you specify a TTL (Time-To-Live) value when writing an object then it will be stored in an attributed named `ttl` +* You don't want to allow scans + +If any of your configuration is different from this you can do the following: + +* Use the `createMinimumSingleTableConfig()` function, providing the table name and meta attribute names, and then add any other necessary properties +* Use the `createStandardSingleTableConfig()` function above, and replace properties +* Build your own implementation of `TableConfig` + +The particular behaviors of this configuration will be explained in later parts of this manual. + +#### When to use multi-table configuration + +Some projects will use multiple DynamoDB tables. While I'm a fan of DynamoDB "single table design", I think there's often a place to use different tables for different operational reasons. And sometimes you'll be working in a project that doesn't use single table design. + +When your project has multiple tables you can choose one of the following: + +* Create an `AllEntitiesStore` per table, each store using single-table configuration +* Create one or several `AllEntitiesStore`(s) that have a multi-table configuration + +The way that multi-table configuration works with DynamoDB Entity Store is that each entity can only be stored in one table, and in the setup configuration each table includes the list of entities contained within it. +To be clear - **one table can store multiple entities, but each entity can only be stored in one table**, for each Entity Store instance. + +So if you want to store the same entity in multiple tables that immediately drives to using multiple `AllEntitiesStore` instances. + +DynamoDB Entity Store uses one underlying Document Client per instance. +Another reason to use multiple instances therefore is if the different tables have different DynamoDB document client configuration. For example: + +* Different tables are in different accounts / regions / have different credentials +* Different tables use different marshalling / unmarshalling options + +However it's often the case that the constraint of one-table-per-entity, and common-document-client-per-table, is absolutely fine, and in such a case using a multi-table configuration of DynamoDB Entity Store can be used. This has the following advantages: + +* Less code / state in your application +* Ability to perform transactions across multiple entities in different tables + +#### How to use multi-table configuration + +To use a multi-table configuration call `createStore(config)` just as you would do for single-table, but +the config object needs to be of type `MultiTableConfig`, as follows: + +```typescript +export interface MultiTableConfig { + entityTables: MultiEntityTableConfig[] + defaultTableName?: string +} + +export interface MultiEntityTableConfig extends TableConfig { + entityTypes?: string[] +} +``` + +In other words a multi-table config consists of: + +* An array of regular `TableConfig` objects, each having the addition of array of entity type names stored in the table +* An optional default table name + +The entity type names must be precisely the same as those specified in the `type` field of the _Entities_ you'll be using when performing operations. +When you make calls to the operations functions in Entity Store the library will first find the table configuration used for that _Entity_. + +The `defaultTableName` property is useful if you have a situation where _most_ entities are in one table, but you have a few "special cases" of other entities being in different tables. + +You have a few options of how to create a `MultiTableConfig` object: + +* Use the `createStandardMultiTableConfig()` function if all of your tables use the same "standard" configuration described earlier +* Build your own configuration, optionally using the other support functions in [_setupSupport.ts_](../src/lib/support/setupSupport.ts). \ No newline at end of file diff --git a/documentation/3-SimpleUsage.md b/documentation/SimpleUsage.md similarity index 100% rename from documentation/3-SimpleUsage.md rename to documentation/SimpleUsage.md diff --git a/documentation/manual.md b/documentation/manual.md index 8bbfa32..ff7298f 100644 --- a/documentation/manual.md +++ b/documentation/manual.md @@ -2,6 +2,6 @@ For an overview of using DynamoDB Entity Store, please see the [README](../README.md). -1. [Entities](1-Entities.md) -2. [Setup](2-Setup.md) -3. [Simple Usage](3-SimpleUsage.md) \ No newline at end of file +1. [Entities](Entities.md) +2. [Setup](Setup.md) +3. [Simple Usage](SimpleUsage.md) \ No newline at end of file diff --git a/examples/src/example1Sheep.ts b/examples/src/example1Sheep.ts index b9af62e..4e5e9ef 100644 --- a/examples/src/example1Sheep.ts +++ b/examples/src/example1Sheep.ts @@ -1,6 +1,6 @@ import { createEntity, - createStandardSingleTableStoreConfig, + createStandardSingleTableConfig, createStore, DynamoDBValues, rangeWhereSkBetween @@ -29,7 +29,7 @@ export const SHEEP_ENTITY = createEntity( async function run() { // Create entity store using default configuration - const config = createStandardSingleTableStoreConfig('AnimalsTable') + const config = createStandardSingleTableConfig('AnimalsTable') // config.store.logger = consoleLogger const entityStore = createStore(config) diff --git a/examples/src/example2Chickens.ts b/examples/src/example2Chickens.ts index 5d54c66..3f5a882 100644 --- a/examples/src/example2Chickens.ts +++ b/examples/src/example2Chickens.ts @@ -1,5 +1,5 @@ import { - createStandardSingleTableStoreConfig, + createStandardSingleTableConfig, createStore, Entity, rangeWhereSkBeginsWith, @@ -68,7 +68,7 @@ export function gsiBreed(breed: string) { async function run() { // Create entity store using default configuration - const entityStore = createStore(createStandardSingleTableStoreConfig('AnimalsTable')) + const entityStore = createStore(createStandardSingleTableConfig('AnimalsTable')) const chickenStore = entityStore.for(CHICKEN_ENTITY) await chickenStore.put({ breed: 'sussex', name: 'ginger', dateOfBirth: '2021-07-01', coop: 'bristol' }) diff --git a/examples/src/example3Farms.ts b/examples/src/example3Farms.ts index 8dd3fe1..04c6652 100644 --- a/examples/src/example3Farms.ts +++ b/examples/src/example3Farms.ts @@ -1,5 +1,5 @@ import { - createSingleTableConfiguration, + createMinimumSingleTableConfig, createStore, DynamoDBValues, entityFromPkOnlyEntity, @@ -43,13 +43,10 @@ export const FARM_ENTITY = entityFromPkOnlyEntity({ async function run() { // Custom configuration - use one table where the partition key attribute name is 'Name', and there is no SK // or any other metadata - const entityStore = createStore( - createSingleTableConfiguration({ - tableName: 'FarmTable', - metaAttributeNames: { pk: 'Name' }, - allowScans: true - }) - ) + const entityStore = createStore({ + ...createMinimumSingleTableConfig('FarmTable', { pk: 'Name' }), + allowScans: true + }) const farmStore = entityStore.for(FARM_ENTITY) await farmStore.put({ name: 'Sunflower Farm', address: 'Green Shoots Road, Honesdale, PA' }) diff --git a/src/lib/dynamoDBInterface.ts b/src/lib/dynamoDBInterface.ts index b384639..72e91a4 100644 --- a/src/lib/dynamoDBInterface.ts +++ b/src/lib/dynamoDBInterface.ts @@ -34,7 +34,7 @@ import { UpdateCommandOutput } from '@aws-sdk/lib-dynamodb' import { DynamoDBClient } from '@aws-sdk/client-dynamodb' -import { EntityStoreLogger, isDebugLoggingEnabled, noopLogger } from './util' +import { EntityStoreLogger, isDebugLoggingEnabled } from './util' export interface DynamoDBInterface { put(params: PutCommandInput): Promise @@ -65,13 +65,10 @@ export interface DynamoDBInterface { } export function documentClientBackedInterface( - options: { - documentClient?: DynamoDBDocumentClient - logger?: EntityStoreLogger - } = {} + logger: EntityStoreLogger, + documentClient?: DynamoDBDocumentClient ): DynamoDBInterface { - const docClient = options.documentClient ?? DynamoDBDocumentClient.from(new DynamoDBClient({})) - const logger = options.logger ?? noopLogger + const docClient = documentClient ?? DynamoDBDocumentClient.from(new DynamoDBClient({})) return { async put(params: PutCommandInput) { diff --git a/src/lib/entities.ts b/src/lib/entities.ts index 788e83d..af1565e 100644 --- a/src/lib/entities.ts +++ b/src/lib/entities.ts @@ -8,7 +8,7 @@ export type DynamoDBValues = Record * These values can change for multi-table stores. E.g. one table may be a multi-entity "standard" configuration, and another * might be more customized for one specific entity * This type is included in this file since MetaAttributeNames are parsed to an Entity's parser function - * "Standard" configuration for a multi-entity / single-table configuration, is defined in configSupport.ts, but in brief is: + * "Standard" configuration for a multi-entity / single-table configuration, is defined in setupSupport.ts, but in brief is: * pk: 'PK' * sk: 'SK' * gsisById: { gsi: { pk: 'GSIPK', sk: 'GSISK' } } diff --git a/src/lib/internal/entityContext.ts b/src/lib/internal/entityContext.ts index 1832a8f..5f59513 100644 --- a/src/lib/internal/entityContext.ts +++ b/src/lib/internal/entityContext.ts @@ -1,28 +1,25 @@ import { Entity, MetaAttributeNames } from '../entities' -import { DynamoDBInterface } from '../dynamoDBInterface' -import { Clock } from '../util/dateAndTime' import { Mandatory } from '../util/types' -import { StoreConfiguration, Table } from '../tableBackedStoreConfiguration' -import { EntityStoreLogger } from '../util/logger' +import { TableBackedStoreContext, TableConfig } from '../tableBackedStoreConfiguration' export type ContextMetaAttributeNames = Mandatory -export interface EntityContext { - dynamoDB: DynamoDBInterface - clock: Clock - logger: EntityStoreLogger +export interface EntityContext + extends TableBackedStoreContext, + Pick { entity: Entity - tableName: string tableGsiNames: Record metaAttributeNames: ContextMetaAttributeNames allMetaAttributeNames: string[] } -export type CompleteTableParams = Mandatory & - Required> +export interface EntityContextParams { + table: TableConfig + storeContext: TableBackedStoreContext +} export function createEntityContext( - table: CompleteTableParams, + { storeContext, table }: EntityContextParams, entity: Entity ): EntityContext { const metaAttributeNames = { @@ -30,12 +27,11 @@ export function createEntityContext[] ): MultipleEntityOperations { const contextsByEntityType: Record> = Object.fromEntries( - entities.map((e) => [e.type, createEntityContext(tableConfigResolver(e.type), e)]) + entities.map((e) => [e.type, createEntityContext(entityContextResolver(e.type), e)]) ) if (new Set(Object.values(contextsByEntityType).map((c) => c.tableName)).size > 1) throw new Error( diff --git a/src/lib/internal/singleEntity/tableBackedSingleEntityAdvancedOperations.ts b/src/lib/internal/singleEntity/tableBackedSingleEntityAdvancedOperations.ts index 8bcfdfc..e9e2714 100644 --- a/src/lib/internal/singleEntity/tableBackedSingleEntityAdvancedOperations.ts +++ b/src/lib/internal/singleEntity/tableBackedSingleEntityAdvancedOperations.ts @@ -24,7 +24,7 @@ import { BatchPutOptions, SingleEntityAdvancedOperations } from '../../singleEntityAdvancedOperations' -import { CompleteTableParams, EntityContext } from '../entityContext' +import { EntityContextParams, EntityContext } from '../entityContext' import { Entity } from '../../entities' import { putItem } from './putItem' import { getItem } from './getItem' @@ -49,12 +49,13 @@ export function tableBackedSingleEntityAdvancedOperations< TPKSource, TSKSource >( - table: CompleteTableParams, + context: EntityContextParams, entity: Entity, entityContext: EntityContext ): SingleEntityAdvancedOperations { function checkAllowScans() { - if (!table.allowScans) throw new Error('Scan operations are disabled for this store') + if (context.table.allowScans === undefined || !context.table.allowScans) + throw new Error('Scan operations are disabled for this store') } return { diff --git a/src/lib/internal/singleEntity/tableBackedSingleEntityOperations.ts b/src/lib/internal/singleEntity/tableBackedSingleEntityOperations.ts index b6d3744..4aeb9a7 100644 --- a/src/lib/internal/singleEntity/tableBackedSingleEntityOperations.ts +++ b/src/lib/internal/singleEntity/tableBackedSingleEntityOperations.ts @@ -18,10 +18,10 @@ import { UpdateOptions } from '../../singleEntityOperations' import { tableBackedSingleEntityAdvancedOperations } from './tableBackedSingleEntityAdvancedOperations' -import { TableResolver } from '../tableBackedConfigurationResolver' +import { EntityContextResolver } from '../tableBackedConfigurationResolver' export function tableBackedSingleEntityOperations( - tableConfigResolver: TableResolver, + tableConfigResolver: EntityContextResolver, entity: Entity ): SingleEntityOperations { const table = tableConfigResolver(entity.type), diff --git a/src/lib/internal/tableBackedConfigurationResolver.ts b/src/lib/internal/tableBackedConfigurationResolver.ts index 54b1180..c138fe1 100644 --- a/src/lib/internal/tableBackedConfigurationResolver.ts +++ b/src/lib/internal/tableBackedConfigurationResolver.ts @@ -1,67 +1,50 @@ import { isSingleTableConfig, - StoreConfiguration, - Table, - TableBackedStoreConfiguration + MultiEntityTableConfig, + TableBackedStoreContext, + TableConfig, + TablesConfig } from '../tableBackedStoreConfiguration' -import { CompleteTableParams } from './entityContext' +import { EntityContextParams } from './entityContext' import { throwError } from '../util/errors' -export type TableResolver = (entityType: string) => CompleteTableParams +export type EntityContextResolver = (entityType: string) => EntityContextParams -export function resolverFor(config: TableBackedStoreConfiguration): TableResolver { - if (isSingleTableConfig(config.tables)) { - return singleTableResolver(completeConfiguration(config.store, config.tables.table)) - } - // Else MultiTable - const { entityTables, defaultTableName } = config.tables - - const completeConfigsByEntityType: Record = Object.fromEntries( - entityTables - .map((table) => { - const completeConfig = completeConfiguration(config.store, table) - return (table.entityTypes ?? []).map((entityType) => [entityType, completeConfig]) - }) - .flat() - ) - - const completeDefaultTableConfig = defaultTableName - ? completeConfiguration( - config.store, - config.tables.entityTables.find((t) => t.tableName === defaultTableName) ?? - throwError(`Unable to find table configuration for default table name ${defaultTableName}`)() - ) - : undefined - - return multiTableResolver(completeConfigsByEntityType, completeDefaultTableConfig) +export function resolverFor( + storeContext: TableBackedStoreContext, + config: TablesConfig +): EntityContextResolver { + return isSingleTableConfig(config) + ? singleTableResolver(storeContext, config) + : multiTableResolver(storeContext, config.entityTables, config.defaultTableName) } -function singleTableResolver(config: CompleteTableParams): TableResolver { - return () => config +function singleTableResolver(storeContext: TableBackedStoreContext, table: TableConfig) { + const entityContext = { storeContext, table } + return () => entityContext } function multiTableResolver( - completeConfigsByEntityType: Record, - completeDefaultTableConfig?: CompleteTableParams -): TableResolver { - return (entityType: string) => { - return ( - completeConfigsByEntityType[entityType] ?? - completeDefaultTableConfig ?? - throwError(`Unable to locate table that supports entity type ${entityType}`)() - ) - } -} + storeContext: TableBackedStoreContext, + entityTables: MultiEntityTableConfig[], + defaultTableName: string | undefined +) { + const tablesByEt: Record = Object.fromEntries( + entityTables.map((table) => (table.entityTypes ?? []).map((entityType) => [entityType, table])).flat() + ) -function completeConfiguration(store: StoreConfiguration, table: Table): CompleteTableParams { - return { - clock: store.clock, - logger: store.logger, - allowScans: table.allowScans !== undefined && table.allowScans, - dynamoDB: - table.dynamoDB ?? - store.globalDynamoDB ?? - throwError(`DynamoDB wrapper is not available at either the table or global scope`)(), - ...table + const defaultTable = defaultTableName + ? entityTables.find((t) => t.tableName === defaultTableName) ?? + throwError(`Unable to find table configuration for default table name ${defaultTableName}`)() + : undefined + + return (entityType: string) => { + return { + storeContext, + table: + tablesByEt[entityType] ?? + defaultTable ?? + throwError(`Unable to locate table that supports entity type ${entityType}`)() + } } } diff --git a/src/lib/internal/transactions/tableBackedGetTransactionBuilder.ts b/src/lib/internal/transactions/tableBackedGetTransactionBuilder.ts index e7df7a7..1beb871 100644 --- a/src/lib/internal/transactions/tableBackedGetTransactionBuilder.ts +++ b/src/lib/internal/transactions/tableBackedGetTransactionBuilder.ts @@ -1,5 +1,5 @@ import { DynamoDBValues, Entity } from '../../entities' -import { CompleteTableParams, createEntityContext, EntityContext } from '../entityContext' +import { EntityContextParams, createEntityContext, EntityContext } from '../entityContext' import { keyParamFromSource, parseItem, @@ -29,11 +29,11 @@ export class TableBackedGetTransactionBuilder[] - private readonly tableConfigResolver: (entityType: string) => CompleteTableParams + private readonly tableConfigResolver: (entityType: string) => EntityContextParams private readonly context: EntityContext constructor( - tableConfigResolver: (entityType: string) => CompleteTableParams, + tableConfigResolver: (entityType: string) => EntityContextParams, currentEntity: Entity, { contexts, diff --git a/src/lib/internal/transactions/tableBackedWriteTransactionBuilder.ts b/src/lib/internal/transactions/tableBackedWriteTransactionBuilder.ts index 5329c64..0438e78 100644 --- a/src/lib/internal/transactions/tableBackedWriteTransactionBuilder.ts +++ b/src/lib/internal/transactions/tableBackedWriteTransactionBuilder.ts @@ -1,5 +1,5 @@ import { Entity } from '../../entities' -import { CompleteTableParams, createEntityContext, EntityContext } from '../entityContext' +import { EntityContextParams, createEntityContext, EntityContext } from '../entityContext' import { DeleteCommandInput, PutCommandInput, @@ -41,11 +41,11 @@ export class TableBackedWriteTransactionBuilder { private readonly requests: WriteTransactionRequest[] - private readonly tableConfigResolver: (entityType: string) => CompleteTableParams + private readonly tableConfigResolver: (entityType: string) => EntityContextParams private readonly context: EntityContext constructor( - tableConfigResolver: (entityType: string) => CompleteTableParams, + tableConfigResolver: (entityType: string) => EntityContextParams, currentEntity: Entity, requests?: WriteTransactionRequest[] ) { diff --git a/src/lib/support/configSupport.ts b/src/lib/support/configSupport.ts deleted file mode 100644 index df09bcc..0000000 --- a/src/lib/support/configSupport.ts +++ /dev/null @@ -1,162 +0,0 @@ -import { documentClientBackedInterface } from '../dynamoDBInterface' -import { realClock } from '../util/dateAndTime' -import { - StoreConfiguration, - Table, - TableBackedStoreConfiguration, - TableConfiguration -} from '../tableBackedStoreConfiguration' -import { EntityStoreLogger, noopLogger } from '../util/logger' -import { MetaAttributeNames } from '../entities' - -export const MinimumMetaAttributeNamesWithStandardPK: MetaAttributeNames = { - pk: 'PK' -} - -export const PKOnlyStandardMetaAttributeNames = { - ...MinimumMetaAttributeNamesWithStandardPK, - ttl: 'ttl', - entityType: '_et', - lastUpdated: '_lastUpdated' -} - -export const NoGSIStandardMetaAttributeNames = { - ...PKOnlyStandardMetaAttributeNames, - sk: 'SK' -} - -export const SingleGSIStandardMetaAttributeNames = { - ...NoGSIStandardMetaAttributeNames, - gsisById: { - gsi: { - pk: 'GSIPK', - sk: 'GSISK' - } - } -} - -export const TwoGSIStandardMetaAttributeNames = { - ...NoGSIStandardMetaAttributeNames, - gsisById: { - gsi: { - pk: 'GSIPK', - sk: 'GSISK' - }, - gsi2: { - pk: 'GSI2PK', - sk: 'GSI2SK' - } - } -} - -export function createMinimumTableConfiguration(pkAttributeName: string): TableConfiguration { - return { - metaAttributeNames: { - ...MinimumMetaAttributeNamesWithStandardPK, - pk: pkAttributeName - } - } -} - -export function createMinimumTable(tableName: string, pkAttributeName: string): Table { - return { - tableName, - ...createMinimumTableConfiguration(pkAttributeName) - } -} - -export const standardTableConfiguration: TableConfiguration = { - metaAttributeNames: SingleGSIStandardMetaAttributeNames, - gsiNames: { gsi: 'GSI' }, - allowScans: false -} - -export function createStandardTable(tableName: string): Table { - return { - tableName, - ...standardTableConfiguration - } -} - -/** - * Configuration using single table for all entities, and PK / SK as partition key / sort key attribute names - * Also enables ttl attribute ('ttl'), entity type attribute ('_et'), and last updated attribute ('_lastUpdated') - * If not specified in overrides, a DynamoDB client is instantiated using the default configuration - * Debug logging is disabled - * @param tableName - the underlying DynamoDB actual table name - * @param storeOverrides - * @param tableOverrides - */ -export function createStandardSingleTableStoreConfig( - tableName: string, - storeOverrides: Partial = {}, - tableOverrides: Partial = {} -): TableBackedStoreConfiguration { - const dynamoDBSpecified = storeOverrides.globalDynamoDB || tableOverrides.dynamoDB - - return { - store: storeConfig(storeOverrides, !dynamoDBSpecified), - tables: { - table: { - ...createStandardTable(tableName), - ...tableOverrides - } - } - } -} - -/** - * Same configuration as standardSingleTableConfiguration but for multiple entities - */ -export function standardMultiTableConfiguration( - tablesToEntityTypes: Record, - storeOverrides: Partial = {}, - tableOverrides: Partial = {}, - defaultTableName?: string -): TableBackedStoreConfiguration { - const dynamoDBSpecified = storeOverrides.globalDynamoDB || tableOverrides.dynamoDB - - const tableConfig: TableConfiguration = { ...standardTableConfiguration, ...tableOverrides } - - return { - store: storeConfig(storeOverrides, !dynamoDBSpecified), - tables: { - entityTables: Object.entries(tablesToEntityTypes).map(([tableName, entityTypes]) => { - return { - tableName, - entityTypes, - ...tableConfig - } - }), - ...(defaultTableName ? { defaultTableName } : {}) - } - } -} - -export function createSingleTableConfiguration( - table: Table, - storeConfiguration: Partial = {} -): TableBackedStoreConfiguration { - return { - store: storeConfig(storeConfiguration, !(table.dynamoDB || storeConfiguration.globalDynamoDB)), - tables: { - table - } - } -} - -function storeConfig( - partialStoreConfig: Partial, - defineDynamoDb: boolean -): StoreConfiguration { - const logger: EntityStoreLogger = partialStoreConfig.logger ?? noopLogger - return { - logger, - clock: partialStoreConfig.clock ?? realClock(), - ...(partialStoreConfig.globalDynamoDB - ? { globalDynamoDB: partialStoreConfig.globalDynamoDB } - : defineDynamoDb - ? { globalDynamoDB: documentClientBackedInterface({ logger }) } - : {}) - } -} diff --git a/src/lib/support/index.ts b/src/lib/support/index.ts index 89f7694..9e1e04a 100644 --- a/src/lib/support/index.ts +++ b/src/lib/support/index.ts @@ -1,3 +1,3 @@ -export * from './configSupport' +export * from './setupSupport' export * from './entitySupport' export * from './querySupport' diff --git a/src/lib/support/setupSupport.ts b/src/lib/support/setupSupport.ts new file mode 100644 index 0000000..27c9f82 --- /dev/null +++ b/src/lib/support/setupSupport.ts @@ -0,0 +1,117 @@ +import { documentClientBackedInterface } from '../dynamoDBInterface' +import { realClock } from '../util/dateAndTime' +import { MultiTableConfig, TableBackedStoreContext, TableConfig } from '../tableBackedStoreConfiguration' +import { noopLogger } from '../util/logger' +import { MetaAttributeNames } from '../entities' +import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb' + +/** + * Create store context, optionally passing overrides to defaults. + * By default the following are used: + * * `logger` : No-op logger (Don't log) + * * `dynamoDB` : Wrapper using default DynamoDB document client. Typically you'd only override this in unit / in-process tests. To use non default DynamoDB Document Client behavior then specify the documentClient parameter instead. + * * `clock` : Real clock based on system time (it can be useful to override this in tests) + * @param options - override any of the default context values. Can be explicitly set to `{}` to use default values. + * @param documentClient - override the DynamoDB document client used in the default DynamoDB wrapper. **IGNORED** if `dynamoDB` is provided in `options` + */ +export function createStoreContext( + options: Partial = {}, + documentClient?: DynamoDBDocumentClient +): TableBackedStoreContext { + const logger = options.logger ?? noopLogger + return { + clock: options.clock ?? realClock(), + logger, + dynamoDB: options.dynamoDB ?? documentClientBackedInterface(logger, documentClient) + } +} + +/** + * Create the minimum table config when using a single table. Useful as a base if you're using "non-standard" config + * @param tableName - the underlying DynamoDB table name + * @param metaAttributeNames - Attribute names for meta values. At least Partition Key must be specified + */ +export function createMinimumSingleTableConfig( + tableName: string, + metaAttributeNames: MetaAttributeNames +): TableConfig { + return { + tableName, + metaAttributeNames + } +} + +/** + * Create "standard single table" config. + * * Partition Key attribute name = `PK` + * * Sort Key attribute name = `SK` + * * TTL attribute name = `ttl` + * * Entity Type attribute name = `_et` + * * Last Updated attribute name = `_lastUpdated` + * * One GSI, with Table GSI name = `GSI`, logical GSI name = `gsi`, GSI Partition Key = `GSIPK`, GSI Sort Key = `GSISK` + * * Scans not allowed + * @param tableName - the underlying DynamoDB table name + */ +export function createStandardSingleTableConfig(tableName: string): TableConfig { + return { + ...createMinimumSingleTableConfig(tableName, SingleGSIStandardMetaAttributeNames), + gsiNames: { gsi: 'GSI' }, + allowScans: false + } +} + +/** + * Same configuration as createStandardSingleTableConfig but for multiple entities + * @param tablesToEntityTypes Map of underlying Dynamo Table Names to the entities stored in each table + * @param defaultTableName Which table to use if an operation is performed on an entity not explicitly configured in tablesToEntityTypes. Default - no default table is used, and all entities must be explicitly configured. + * @throws if `defaultTableName` isn't included in keys of `tablesToEntityTypes` + */ +export function createStandardMultiTableConfig( + tablesToEntityTypes: Record, + defaultTableName?: string +): MultiTableConfig { + if (defaultTableName && !Object.keys(tablesToEntityTypes).includes(defaultTableName)) + throw new Error(`Default table ${defaultTableName} is not included in list of tables`) + + return { + ...(defaultTableName ? { defaultTableName } : {}), + entityTables: Object.entries(tablesToEntityTypes).map(([tableName, entityTypes]) => { + return { + ...createStandardSingleTableConfig(tableName), + entityTypes + } + }) + } +} + +export const NoGSIStandardMetaAttributeNames = { + pk: 'PK', + sk: 'SK', + ttl: 'ttl', + entityType: '_et', + lastUpdated: '_lastUpdated' +} + +export const SingleGSIStandardMetaAttributeNames = { + ...NoGSIStandardMetaAttributeNames, + gsisById: { + gsi: { + pk: 'GSIPK', + sk: 'GSISK' + } + } +} + +export const TwoGSIStandardMetaAttributeNames = { + ...NoGSIStandardMetaAttributeNames, + gsisById: { + gsi: { + pk: 'GSIPK', + sk: 'GSISK' + }, + gsi2: { + pk: 'GSI2PK', + sk: 'GSI2SK' + } + } +} diff --git a/src/lib/tableBackedStore.ts b/src/lib/tableBackedStore.ts index c1eef6a..c81af2d 100644 --- a/src/lib/tableBackedStore.ts +++ b/src/lib/tableBackedStore.ts @@ -1,6 +1,6 @@ import { AllEntitiesStore } from './entityStore' import { Entity } from './entities' -import { TableBackedStoreConfiguration } from './tableBackedStoreConfiguration' +import { TablesConfig, TableBackedStoreContext } from './tableBackedStoreConfiguration' import { tableBackedSingleEntityOperations } from './internal/singleEntity/tableBackedSingleEntityOperations' import { resolverFor } from './internal/tableBackedConfigurationResolver' import { TableBackedWriteTransactionBuilder } from './internal/transactions/tableBackedWriteTransactionBuilder' @@ -9,14 +9,16 @@ import { tableBackedMultipleEntityOperations } from './internal/multipleEntities import { SingleEntityOperations } from './singleEntityOperations' import { TableBackedGetTransactionBuilder } from './internal/transactions/tableBackedGetTransactionBuilder' import { GetTransactionBuilder, WriteTransactionBuilder } from './transactionOperations' +import { createStoreContext } from './support' /** * Entry point to dynamodb-entity-store. A Table Backed Store can use either one DynamoDB backing table, * or several; and can be used to persist one entity type, or several. - * @param config - either using objects created from configSupport.ts, (e.g. `createStandardSingleTableStoreConfig`) or you can fully customize + * @param tablesConfig - either using objects created from setupSupport.ts, (e.g. `createStandardSingleTableConfig`) or you can fully customize + * @param context - override the default store context. To see what those defaults are, see `createStoreContext` in setupSupport.ts */ -export function createStore(config: TableBackedStoreConfiguration): AllEntitiesStore { - const tableConfigResolver = resolverFor(config) +export function createStore(tablesConfig: TablesConfig, context?: TableBackedStoreContext): AllEntitiesStore { + const tableConfigResolver = resolverFor(context ?? createStoreContext(), tablesConfig) return { for( entity: Entity diff --git a/src/lib/tableBackedStoreConfiguration.ts b/src/lib/tableBackedStoreConfiguration.ts index 5c0f41b..22e6393 100644 --- a/src/lib/tableBackedStoreConfiguration.ts +++ b/src/lib/tableBackedStoreConfiguration.ts @@ -1,44 +1,33 @@ -import { Clock } from './util' +import { Clock, EntityStoreLogger } from './util' import { DynamoDBInterface } from './dynamoDBInterface' -import { EntityStoreLogger } from './util' import { MetaAttributeNames } from './entities' -export interface TableBackedStoreConfiguration { - store: StoreConfiguration - tables: SingleTableConfiguration | MultiTableConfiguration -} +// See functions in _setupSupport.ts_ for assistance in creating these objects -export interface StoreConfiguration { - clock: Clock // Defaults to clock based off system time. Overridable for testing - globalDynamoDB?: DynamoDBInterface // TODO - test this - not used in standard config +export interface TableBackedStoreContext { logger: EntityStoreLogger + dynamoDB: DynamoDBInterface + clock: Clock } -export interface SingleTableConfiguration { - table: Table +export type TablesConfig = TableConfig | MultiTableConfig + +export interface TableConfig { + tableName: string + metaAttributeNames: MetaAttributeNames + allowScans?: boolean + gsiNames?: Record } -export interface MultiTableConfiguration { +export interface MultiTableConfig { + entityTables: MultiEntityTableConfig[] defaultTableName?: string - entityTables: MultiEntityTable[] } -export interface MultiEntityTable extends Table { +export interface MultiEntityTableConfig extends TableConfig { entityTypes?: string[] } -export interface Table extends TableConfiguration { - tableName: string -} - -export interface TableConfiguration { - metaAttributeNames: MetaAttributeNames - dynamoDB?: DynamoDBInterface // TODO - check using global by default - allowScans?: boolean - gsiNames?: Record -} -export function isSingleTableConfig( - x: SingleTableConfiguration | MultiTableConfiguration -): x is SingleTableConfiguration { - return (x as SingleTableConfiguration).table !== undefined +export function isSingleTableConfig(x: TablesConfig): x is TableConfig { + return (x as TableConfig).tableName !== undefined } diff --git a/test/integration/customTable/farm-table.test.ts b/test/integration/customTable/farm-table.test.ts index e225ffe..47e1f7a 100644 --- a/test/integration/customTable/farm-table.test.ts +++ b/test/integration/customTable/farm-table.test.ts @@ -5,13 +5,15 @@ import { farmTableName, findFarmTableName } from '../testSupportCode/awsEnvironment' -import { documentClientBackedInterface } from '../../../src/lib' -import { FakeClock } from '../../unit/fakes/fakeClock' +import { + consoleLogger, + createMinimumSingleTableConfig, + createStore, + createStoreContext +} from '../../../src/lib' +import { FakeClock } from '../../unit/testSupportCode/fakes/fakeClock' import { Farm, FARM_ENTITY } from '../../examples/farmTypeAndEntity' import { DeleteCommand } from '@aws-sdk/lib-dynamodb' -import { createStore } from '../../../src/lib' -import { createMinimumTable } from '../../../src/lib' -import { consoleLogger } from '../../../src/lib' const docClient = createDocumentClient() @@ -20,19 +22,13 @@ async function initializeStoreAndTable(options: { emptyTable?: boolean; allowSca expect(farmTableName).toBeDefined() const logger = consoleLogger - const store = createStore({ - store: { - globalDynamoDB: documentClientBackedInterface({ documentClient: docClient, logger }), - clock: new FakeClock(), - logger + const store = createStore( + { + ...createMinimumSingleTableConfig(farmTableName, { pk: 'Name' }), + allowScans: true }, - tables: { - table: { - ...createMinimumTable(farmTableName, 'Name'), - allowScans: true - } - } - }) + createStoreContext({ clock: new FakeClock(), logger }, docClient) + ) if (options.emptyTable === undefined || options.emptyTable) { const allRecords = await store.for(FARM_ENTITY).scanAll() diff --git a/test/integration/multiTable/farmAndAnimals.test.ts b/test/integration/multiTable/farmAndAnimals.test.ts index be1bd92..760889e 100644 --- a/test/integration/multiTable/farmAndAnimals.test.ts +++ b/test/integration/multiTable/farmAndAnimals.test.ts @@ -8,14 +8,17 @@ import { testTableName } from '../testSupportCode/awsEnvironment' import { describe, expect, test } from 'vitest' -import { noopLogger } from '../../../src/lib' -import { createStore } from '../../../src/lib' -import { documentClientBackedInterface } from '../../../src/lib' -import { FakeClock } from '../../unit/fakes/fakeClock' -import { createMinimumTable, createStandardTable } from '../../../src/lib' +import { + createMinimumSingleTableConfig, + createStandardSingleTableConfig, + createStore, + createStoreContext, + noopLogger, + TablesConfig +} from '../../../src/lib' +import { FakeClock } from '../../unit/testSupportCode/fakes/fakeClock' import { Farm, FARM_ENTITY } from '../../examples/farmTypeAndEntity' import { DeleteCommand } from '@aws-sdk/lib-dynamodb' -import { TableBackedStoreConfiguration } from '../../../src/lib' import { DOG_ENTITY } from '../../examples/dogTypeAndEntity' import { chesterDog, peggyCat } from '../../examples/testData' import { CAT_ENTITY } from '../../examples/catTypeAndEntity' @@ -28,29 +31,22 @@ async function initializeStoreAndTable(options: { emptyTable?: boolean } = {}) { expect(farmTableName).toBeDefined() const logger = noopLogger - const config: TableBackedStoreConfiguration = { - store: { - globalDynamoDB: documentClientBackedInterface({ documentClient: docClient, logger }), - clock: new FakeClock(), - logger - }, - tables: { - defaultTableName: testTableName, - entityTables: [ - { - ...createStandardTable(testTableName), - allowScans: true - }, - { - ...createMinimumTable(farmTableName, 'Name'), - allowScans: true, - entityTypes: [FARM_ENTITY.type] - } - ] - } + const config: TablesConfig = { + defaultTableName: testTableName, + entityTables: [ + { + ...createStandardSingleTableConfig(testTableName), + allowScans: true + }, + { + ...createMinimumSingleTableConfig(farmTableName, { pk: 'Name' }), + allowScans: true, + entityTypes: [FARM_ENTITY.type] + } + ] } - const store = createStore(config) + const store = createStore(config, createStoreContext({ clock: new FakeClock(), logger }, docClient)) if (options.emptyTable === undefined || options.emptyTable) { await dynamoDbEmptyTable(testTableName, docClient) diff --git a/test/integration/singleTable/sheep.test.ts b/test/integration/singleTable/sheep.test.ts index e8520ac..39c526b 100644 --- a/test/integration/singleTable/sheep.test.ts +++ b/test/integration/singleTable/sheep.test.ts @@ -94,17 +94,10 @@ describe('basic operations', () => { describe('with table customizations', () => { async function customSheepStore() { return ( - await initialize( - {}, - { - tableName: customTableName, - allowScans: true, - metaAttributeNames: { - pk: 'CustomPK', - sk: 'CustomSK' - } - } - ) + await initialize({ + allowScans: true, + useCustomTable: true + }) ).for(SHEEP_ENTITY) } diff --git a/test/integration/testSupportCode/appEnvironment.ts b/test/integration/testSupportCode/appEnvironment.ts index af99f60..2d8e8f5 100644 --- a/test/integration/testSupportCode/appEnvironment.ts +++ b/test/integration/testSupportCode/appEnvironment.ts @@ -1,57 +1,62 @@ import { expect } from 'vitest' -import { - StoreConfiguration, - TableBackedStoreConfiguration, - TableConfiguration -} from '../../../src/lib/tableBackedStoreConfiguration' -import { FakeClock } from '../../unit/fakes/fakeClock' +import { FakeClock } from '../../unit/testSupportCode/fakes/fakeClock' import { createDocumentClient, + customTableName, dynamoDbEmptyTable, dynamoDbScanTable, findCustomTableName, findTestTableName, testTableName } from './awsEnvironment' -import { documentClientBackedInterface } from '../../../src/lib/dynamoDBInterface' -import { noopLogger } from '../../../src/lib/util/logger' -import { createStandardSingleTableStoreConfig } from '../../../src/lib/support/configSupport' -import { createStore } from '../../../src/lib/tableBackedStore' +import { + createStandardSingleTableConfig, + createStore, + createStoreContext, + noopLogger +} from '../../../src/lib' export const docClient = createDocumentClient() export const clock = new FakeClock() +export const logger = noopLogger -export async function initialize( - options: { emptyTable?: boolean; allowScans?: boolean } = {}, - customTableConfig?: TableConfiguration & { tableName?: string } -) { - await findTestTableName() - await findCustomTableName() - - expect(testTableName).toBeDefined() - clock.fakeNowIso = '2023-07-01T19:00:00.000Z' - - if (options.emptyTable === undefined || options.emptyTable) { - await dynamoDbEmptyTable(testTableName, docClient) - expect((await dynamoDbScanTable(testTableName, docClient)).length).toEqual(0) +export async function initialize({ + allowScans, + emptyTable, + useCustomTable +}: { + emptyTable?: boolean + allowScans?: boolean + useCustomTable?: boolean +} = {}) { + if (useCustomTable) { + await findCustomTableName() + } else { + await findTestTableName() + } + const tableName = useCustomTable ? customTableName : testTableName + if (emptyTable === undefined || emptyTable) { + if (useCustomTable) { + await dynamoDbEmptyTable(tableName, docClient, 'CustomPK', 'CustomSK') + } else { + await dynamoDbEmptyTable(tableName, docClient) + } + await dynamoDbEmptyTable(tableName, docClient) + expect((await dynamoDbScanTable(tableName, docClient)).length).toEqual(0) } - const storeConfig: StoreConfiguration = { - globalDynamoDB: documentClientBackedInterface({ documentClient: docClient }), - clock: clock, - logger: noopLogger + const config = { + ...createStandardSingleTableConfig(tableName), + allowScans + } + if (useCustomTable) { + config.metaAttributeNames = { + pk: 'CustomPK', + sk: 'CustomSK' + } } - const config: TableBackedStoreConfiguration = customTableConfig - ? { - store: storeConfig, - tables: { - table: { tableName: testTableName, ...customTableConfig } - } - } - : createStandardSingleTableStoreConfig(testTableName, storeConfig, { - allowScans: options.allowScans - }) + clock.fakeNowIso = '2023-07-01T19:00:00.000Z' - return createStore(config) + return createStore(config, createStoreContext({ clock, logger }, docClient)) } diff --git a/test/integration/testSupportCode/awsEnvironment.ts b/test/integration/testSupportCode/awsEnvironment.ts index ab0c53e..b5353d4 100644 --- a/test/integration/testSupportCode/awsEnvironment.ts +++ b/test/integration/testSupportCode/awsEnvironment.ts @@ -55,7 +55,12 @@ export function createDocumentClient() { return DynamoDBDocumentClient.from(new DynamoDBClient({})) } -export async function dynamoDbEmptyTable(tableName: string, dynamoDbDocumentClient?: DynamoDBDocumentClient) { +export async function dynamoDbEmptyTable( + tableName: string, + dynamoDbDocumentClient?: DynamoDBDocumentClient, + pk = 'PK', + sk = 'SK' +) { const docClient = dynamoDbDocumentClient ?? createDocumentClient() const items = await dynamoDbScanTable(tableName, docClient) for (const item of items) { @@ -63,8 +68,8 @@ export async function dynamoDbEmptyTable(tableName: string, dynamoDbDocumentClie new DeleteCommand({ TableName: tableName, Key: { - PK: item.PK, - SK: item.SK + [pk]: item[pk], + [sk]: item[sk] } }) ) diff --git a/test/unit/internal/entityContext.test.ts b/test/unit/internal/entityContext.test.ts index c3a9fd6..289d61b 100644 --- a/test/unit/internal/entityContext.test.ts +++ b/test/unit/internal/entityContext.test.ts @@ -4,46 +4,39 @@ import { createEntityContext, EntityContext } from '../../../src/lib/internal/entityContext' -import { FakeClock } from '../fakes/fakeClock' -import { fakeDynamoDBInterface } from '../fakes/fakeDynamoDBInterface' +import { FakeClock } from '../testSupportCode/fakes/fakeClock' +import { fakeDynamoDBInterface } from '../testSupportCode/fakes/fakeDynamoDBInterface' import { Sheep, SHEEP_ENTITY } from '../../examples/sheepTypeAndEntity' -import { noopLogger } from '../../../src/lib/util/logger' import { NoGSIStandardMetaAttributeNames, - PKOnlyStandardMetaAttributeNames, + noopLogger, SingleGSIStandardMetaAttributeNames, TwoGSIStandardMetaAttributeNames -} from '../../../src/lib/support/configSupport' +} from '../../../src/lib' test('calcAllMetaDataAttributeNames', () => { - expect(calcAllMetaAttributeNames(PKOnlyStandardMetaAttributeNames)).toEqual([ - 'PK', - 'ttl', - '_et', - '_lastUpdated' - ]) expect(calcAllMetaAttributeNames(NoGSIStandardMetaAttributeNames)).toEqual([ 'PK', + 'SK', 'ttl', '_et', - '_lastUpdated', - 'SK' + '_lastUpdated' ]) expect(calcAllMetaAttributeNames(SingleGSIStandardMetaAttributeNames)).toEqual([ 'PK', + 'SK', 'ttl', '_et', '_lastUpdated', - 'SK', 'GSIPK', 'GSISK' ]) expect(calcAllMetaAttributeNames(TwoGSIStandardMetaAttributeNames)).toEqual([ 'PK', + 'SK', 'ttl', '_et', '_lastUpdated', - 'SK', 'GSIPK', 'GSISK', 'GSI2PK', @@ -59,13 +52,12 @@ const entity = SHEEP_ENTITY test('createContextForStandardEntity', () => { const actual = createEntityContext( { - tableName: 'testTable', - clock, - logger, - dynamoDB, - allowScans: false, - metaAttributeNames: SingleGSIStandardMetaAttributeNames, - gsiNames: { gsi: 'GSI' } + storeContext: { clock, logger, dynamoDB }, + table: { + tableName: 'testTable', + metaAttributeNames: SingleGSIStandardMetaAttributeNames, + gsiNames: { gsi: 'GSI' } + } }, entity ) @@ -79,7 +71,7 @@ test('createContextForStandardEntity', () => { gsi: 'GSI' }, metaAttributeNames: SingleGSIStandardMetaAttributeNames, - allMetaAttributeNames: ['PK', 'ttl', '_et', '_lastUpdated', 'SK', 'GSIPK', 'GSISK'] + allMetaAttributeNames: ['PK', 'SK', 'ttl', '_et', '_lastUpdated', 'GSIPK', 'GSISK'] } expect(actual).toEqual(expected) }) @@ -90,6 +82,7 @@ test('createContextForCustomEntity', () => { clock, logger, entity, + allowScans: true, tableName: 'testTable', tableGsiNames: {}, metaAttributeNames: { @@ -102,12 +95,16 @@ test('createContextForCustomEntity', () => { expect( createEntityContext( { - tableName: 'testTable', - clock, - logger, - dynamoDB: dynamoDB, - allowScans: false, - metaAttributeNames: { pk: 'PK', lastUpdated: '_lastUpdated' } + storeContext: { + clock, + logger, + dynamoDB + }, + table: { + tableName: 'testTable', + allowScans: true, + metaAttributeNames: { pk: 'PK', lastUpdated: '_lastUpdated' } + } }, entity ) diff --git a/test/unit/internal/operationsCommon.test.ts b/test/unit/internal/operationsCommon.test.ts index 0dc5d5f..b97f557 100644 --- a/test/unit/internal/operationsCommon.test.ts +++ b/test/unit/internal/operationsCommon.test.ts @@ -1,5 +1,5 @@ import { expect, test } from 'vitest' -import { FakeClock } from '../fakes/fakeClock' +import { FakeClock } from '../testSupportCode/fakes/fakeClock' import { determineTTL } from '../../../src/lib/internal/common/operationsCommon' test('determineTTL', () => { diff --git a/test/unit/internal/putOperations.test.ts b/test/unit/internal/putOperations.test.ts index ab00c6c..4f70c04 100644 --- a/test/unit/internal/putOperations.test.ts +++ b/test/unit/internal/putOperations.test.ts @@ -1,11 +1,11 @@ import { expect, test } from 'vitest' import { SHEEP_ENTITY } from '../../examples/sheepTypeAndEntity' import { Chicken, CHICKEN_ENTITY } from '../../examples/chickenTypeAndEntity' -import { FakeClock } from '../fakes/fakeClock' -import { METADATA } from '../fakes/fakeDynamoDBInterface' +import { FakeClock } from '../testSupportCode/fakes/fakeClock' +import { METADATA } from '../testSupportCode/fakes/fakeDynamoDBInterface' import { FARM_ENTITY } from '../../examples/farmTypeAndEntity' -import { fakeLogger } from '../fakes/fakeLogger' -import { SingleGSIStandardMetaAttributeNames } from '../../../src/lib/support/configSupport' +import { fakeLogger } from '../testSupportCode/fakes/fakeLogger' +import { SingleGSIStandardMetaAttributeNames } from '../../../src/lib/support/setupSupport' import { bareBonesContext, contextFor, fakeDynamoDBFrom } from '../testSupportCode/entityContextSupport' import { gsiAttributes, putParams, ttlAttribute } from '../../../src/lib/internal/common/putCommon' import { putItem } from '../../../src/lib/internal/singleEntity/putItem' diff --git a/test/unit/internal/updateOperations.test.ts b/test/unit/internal/updateOperations.test.ts index 629c822..09e4087 100644 --- a/test/unit/internal/updateOperations.test.ts +++ b/test/unit/internal/updateOperations.test.ts @@ -1,7 +1,7 @@ import { expect, test, describe } from 'vitest' import { SHEEP_ENTITY } from '../../examples/sheepTypeAndEntity' import { excludeKeys } from '../../../src/lib/util/collections' -import { SingleGSIStandardMetaAttributeNames } from '../../../src/lib/support/configSupport' +import { SingleGSIStandardMetaAttributeNames } from '../../../src/lib/support/setupSupport' import { contextFor } from '../testSupportCode/entityContextSupport' import { createUpdateParams } from '../../../src/lib/internal/common/updateCommon' diff --git a/test/unit/entitySupport.test.ts b/test/unit/support/entitySupport.test.ts similarity index 81% rename from test/unit/entitySupport.test.ts rename to test/unit/support/entitySupport.test.ts index 4bd36da..aa15ac3 100644 --- a/test/unit/entitySupport.test.ts +++ b/test/unit/support/entitySupport.test.ts @@ -1,6 +1,6 @@ import { expect, test } from 'vitest' -import { typePredicateParser } from '../../src/lib/support/entitySupport' -import { isSheep } from '../examples/sheepTypeAndEntity' +import { typePredicateParser } from '../../../src/lib/support/entitySupport' +import { isSheep } from '../../examples/sheepTypeAndEntity' test('typePredicateParser', () => { const parser = typePredicateParser(isSheep, 'sheep') diff --git a/test/unit/querySupport.test.ts b/test/unit/support/querySupport.test.ts similarity index 95% rename from test/unit/querySupport.test.ts rename to test/unit/support/querySupport.test.ts index d7e3b00..c939f62 100644 --- a/test/unit/querySupport.test.ts +++ b/test/unit/support/querySupport.test.ts @@ -4,7 +4,7 @@ import { rangeWhereSkBetween, rangeWhereSkGreaterThan, rangeWhereSkLessThan -} from '../../src/lib/support/querySupport' +} from '../../../src/lib/support/querySupport' test('skGreaterThan', () => { expect(rangeWhereSkGreaterThan('FIELD#aaa')).toEqual({ diff --git a/test/unit/support/setupSupport.test.ts b/test/unit/support/setupSupport.test.ts new file mode 100644 index 0000000..d7ad8e6 --- /dev/null +++ b/test/unit/support/setupSupport.test.ts @@ -0,0 +1,150 @@ +import { expect, test } from 'vitest' +import { + createMinimumSingleTableConfig, + createStandardMultiTableConfig, + createStandardSingleTableConfig +} from '../../../src/lib' + +test('createMinimumSingleTableConfig', () => { + expect(createMinimumSingleTableConfig('testTable', { pk: 'TESTPK' })).toEqual({ + tableName: 'testTable', + metaAttributeNames: { + pk: 'TESTPK' + } + }) +}) + +test('createStandardSingleTableConfig', () => { + expect(createStandardSingleTableConfig('testTable')).toEqual({ + tableName: 'testTable', + allowScans: false, + metaAttributeNames: { + pk: 'PK', + sk: 'SK', + ttl: 'ttl', + entityType: '_et', + lastUpdated: '_lastUpdated', + gsisById: { + gsi: { + pk: 'GSIPK', + sk: 'GSISK' + } + } + }, + gsiNames: { + gsi: 'GSI' + } + }) +}) + +test('createStandardMultiTableConfigWithDefaultTable', () => { + expect( + createStandardMultiTableConfig( + { table1: ['table1Entity1', 'table1Entity2'], table2: ['table2Entity'] }, + 'table1' + ) + ).toEqual({ + defaultTableName: 'table1', + entityTables: [ + { + entityTypes: ['table1Entity1', 'table1Entity2'], + tableName: 'table1', + allowScans: false, + metaAttributeNames: { + pk: 'PK', + sk: 'SK', + ttl: 'ttl', + entityType: '_et', + lastUpdated: '_lastUpdated', + gsisById: { + gsi: { + pk: 'GSIPK', + sk: 'GSISK' + } + } + }, + gsiNames: { + gsi: 'GSI' + } + }, + { + entityTypes: ['table2Entity'], + tableName: 'table2', + allowScans: false, + metaAttributeNames: { + pk: 'PK', + sk: 'SK', + ttl: 'ttl', + entityType: '_et', + lastUpdated: '_lastUpdated', + gsisById: { + gsi: { + pk: 'GSIPK', + sk: 'GSISK' + } + } + }, + gsiNames: { + gsi: 'GSI' + } + } + ] + }) +}) + +test('createStandardMultiTableConfigWithoutDefaultTable', () => { + expect( + createStandardMultiTableConfig({ table1: ['table1Entity1', 'table1Entity2'], table2: ['table2Entity'] }) + ).toEqual({ + entityTables: [ + { + entityTypes: ['table1Entity1', 'table1Entity2'], + tableName: 'table1', + allowScans: false, + metaAttributeNames: { + pk: 'PK', + sk: 'SK', + ttl: 'ttl', + entityType: '_et', + lastUpdated: '_lastUpdated', + gsisById: { + gsi: { + pk: 'GSIPK', + sk: 'GSISK' + } + } + }, + gsiNames: { + gsi: 'GSI' + } + }, + { + entityTypes: ['table2Entity'], + tableName: 'table2', + allowScans: false, + metaAttributeNames: { + pk: 'PK', + sk: 'SK', + ttl: 'ttl', + entityType: '_et', + lastUpdated: '_lastUpdated', + gsisById: { + gsi: { + pk: 'GSIPK', + sk: 'GSISK' + } + } + }, + gsiNames: { + gsi: 'GSI' + } + } + ] + }) +}) + +test('createStandardMultiTableConfigWithoutBadDefaultTable', () => { + expect(() => createStandardMultiTableConfig({ table1: ['table1Entity1'] }, 'badDefault')).toThrowError( + 'Default table badDefault is not included in list of tables' + ) +}) diff --git a/test/unit/tableBackedEntityStore.test.ts b/test/unit/tableBackedEntityStore.test.ts index 7ab51fb..d294500 100644 --- a/test/unit/tableBackedEntityStore.test.ts +++ b/test/unit/tableBackedEntityStore.test.ts @@ -1,9 +1,8 @@ import { expect, test } from 'vitest' import { SHEEP_ENTITY } from '../examples/sheepTypeAndEntity' -import { fakeDynamoDBInterface } from './fakes/fakeDynamoDBInterface' -import { FakeClock } from './fakes/fakeClock' -import { createStandardSingleTableStoreConfig } from '../../src/lib/support/configSupport' -import { createStore } from '../../src/lib/tableBackedStore' +import { fakeDynamoDBInterface } from './testSupportCode/fakes/fakeDynamoDBInterface' +import { FakeClock } from './testSupportCode/fakes/fakeClock' +import { createStoreContext, createStandardSingleTableConfig, createStore } from '../../src/lib' const METADATA = { $metadata: {} } @@ -12,18 +11,10 @@ const UNIT_TEST_TABLE = 'unit-test-table' function wrapperAndStore({ allowScans = false }: { allowScans?: boolean } = {}) { const wrapper = fakeDynamoDBInterface() - const storeConfig = createStandardSingleTableStoreConfig( - UNIT_TEST_TABLE, - { - globalDynamoDB: wrapper, - clock: new FakeClock() - }, - { - allowScans - } - ) + const config = createStandardSingleTableConfig(UNIT_TEST_TABLE) + config.allowScans = allowScans - const store = createStore(storeConfig) + const store = createStore(config, createStoreContext({ dynamoDB: wrapper, clock: new FakeClock() })) return { wrapper, store diff --git a/test/unit/testSupportCode/entityContextSupport.ts b/test/unit/testSupportCode/entityContextSupport.ts index 130a1a0..737f228 100644 --- a/test/unit/testSupportCode/entityContextSupport.ts +++ b/test/unit/testSupportCode/entityContextSupport.ts @@ -1,12 +1,7 @@ -import { Entity, MetaAttributeNames } from '../../../src/lib/entities' +import { createStandardSingleTableConfig, Entity, MetaAttributeNames, noopLogger } from '../../../src/lib' import { createEntityContext, EntityContext } from '../../../src/lib/internal/entityContext' -import { - createStandardTable, - MinimumMetaAttributeNamesWithStandardPK -} from '../../../src/lib/support/configSupport' -import { FakeDynamoDBInterface, fakeDynamoDBInterface } from '../fakes/fakeDynamoDBInterface' -import { FakeClock } from '../fakes/fakeClock' -import { noopLogger } from '../../../src/lib/util/logger' +import { FakeDynamoDBInterface, fakeDynamoDBInterface } from './fakes/fakeDynamoDBInterface' +import { FakeClock } from './fakes/fakeClock' export function contextFor( entity: Entity, @@ -14,12 +9,16 @@ export function contextFor { return createEntityContext( { - ...createStandardTable('testTable'), - dynamoDB: fakeDynamoDBInterface(), - clock: new FakeClock(), - logger: noopLogger, - allowScans: false, - ...(customMetaAttributeNames ? { metaAttributeNames: customMetaAttributeNames } : {}) + storeContext: { + dynamoDB: fakeDynamoDBInterface(), + clock: new FakeClock(), + logger: noopLogger + }, + table: { + ...createStandardSingleTableConfig('testTable'), + allowScans: false, + ...(customMetaAttributeNames ? { metaAttributeNames: customMetaAttributeNames } : {}) + } }, entity ) @@ -35,5 +34,5 @@ export function fakeDynamoDBFrom(context: EntityContext) { export function bareBonesContext( entity: Entity ): EntityContext { - return contextFor(entity, MinimumMetaAttributeNamesWithStandardPK) + return contextFor(entity, { pk: 'PK' }) } diff --git a/test/unit/fakes/fakeClock.ts b/test/unit/testSupportCode/fakes/fakeClock.ts similarity index 79% rename from test/unit/fakes/fakeClock.ts rename to test/unit/testSupportCode/fakes/fakeClock.ts index 6d07c24..b48017e 100644 --- a/test/unit/fakes/fakeClock.ts +++ b/test/unit/testSupportCode/fakes/fakeClock.ts @@ -1,4 +1,4 @@ -import { Clock } from '../../../src/lib/util/dateAndTime' +import { Clock } from '../../../../src/lib/util/dateAndTime' export class FakeClock implements Clock { public fakeNowIso: string diff --git a/test/unit/fakes/fakeDynamoDBInterface.ts b/test/unit/testSupportCode/fakes/fakeDynamoDBInterface.ts similarity index 97% rename from test/unit/fakes/fakeDynamoDBInterface.ts rename to test/unit/testSupportCode/fakes/fakeDynamoDBInterface.ts index e2ee151..8eccf3e 100644 --- a/test/unit/fakes/fakeDynamoDBInterface.ts +++ b/test/unit/testSupportCode/fakes/fakeDynamoDBInterface.ts @@ -17,7 +17,7 @@ import { UpdateCommandInput, UpdateCommandOutput } from '@aws-sdk/lib-dynamodb' -import { DynamoDBInterface } from '../../../src/lib/dynamoDBInterface' +import { DynamoDBInterface } from '../../../../src/lib/dynamoDBInterface' import { arrayStubResponse } from './fakeSupport' export const METADATA = { $metadata: {} } diff --git a/test/unit/fakes/fakeLogger.ts b/test/unit/testSupportCode/fakes/fakeLogger.ts similarity index 92% rename from test/unit/fakes/fakeLogger.ts rename to test/unit/testSupportCode/fakes/fakeLogger.ts index 4961ecc..0f9d59e 100644 --- a/test/unit/fakes/fakeLogger.ts +++ b/test/unit/testSupportCode/fakes/fakeLogger.ts @@ -2,7 +2,7 @@ import { EntityStoreLogger, EntityStoreLogItemExtraInput, EntityStoreLogItemMessage -} from '../../../src/lib/util/logger' +} from '../../../../src/lib/util/logger' export function fakeLogger(levelName: Uppercase): EntityStoreLogger & { debugs: [EntityStoreLogItemMessage, EntityStoreLogItemExtraInput][] diff --git a/test/unit/fakes/fakeSupport.ts b/test/unit/testSupportCode/fakes/fakeSupport.ts similarity index 100% rename from test/unit/fakes/fakeSupport.ts rename to test/unit/testSupportCode/fakes/fakeSupport.ts