@pwrdrvr/dynamodb-session-store
Version:
express-session library for DynamoDB with configurable `touch` write reductions and configurable eventually consistent reads for higher reliability/throughput and lower costs
283 lines • 10.5 kB
TypeScript
import { Store, SessionData } from 'express-session';
import { DynamoDBClient, CreateTableCommandInput } from '@aws-sdk/client-dynamodb';
/**
* DynamoDBStoreOptions are the options for creating a { @link DynamoDBStore }
*/
export interface DynamoDBStoreOptions {
/**
* AWS v3 SDK DynamoDB client, optionally wrapped with XRay, etc.
*
* @default new DynamoDBClient({})
*/
readonly dynamoDBClient?: DynamoDBClient;
/**
* Name of the DynamoDB table to use (and optionally create)
*
* @defaultValue 'sessions'
*/
readonly tableName?: string;
/**
* Only update the session TTL on `touch` events if `touchAfter` seconds has passed
* since the last time the session TTL was updated.
*
* Set to `0` to always update the session TTL. - This is not suggested.
*
* @remarks
*
* Writes on DynamoDB cost 5x as much as reads for sessions < 1 KB.
*
* Writes on DynamoDB cost 20x as much as reads for sessions >= 3 KB and < 4 KB
* - Reading a 3.5 KB session takes 1 RCUs
* - Writing that same 3.5 KB session takes 4 WCUs
*
* ### Calculating Write Capacity Units - from AWS Docs
*
* [Managing settings on DynamoDB provisioned capacity tables](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ProvisionedThroughput.html#ItemSizeCalculations.Writes)
*
* `UpdateItem` — Modifies a single item in the table. DynamoDB considers the size of the item as
* it appears before and after the update. The provisioned throughput consumed reflects the
* larger of these item sizes. Even if you update just a subset of the item's attributes,
* `UpdateItem` will still consume the full amount of provisioned throughput (the larger of the
* "before" and "after" item sizes).
*
* @see {@link https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ProvisionedThroughput.html#ItemSizeCalculations.Writes }
*
* @defaultValue 3600 (1 hour) or 10% of the `ttl` if `ttl` is less than 36,000 (10 hours)
*/
readonly touchAfter?: number;
/**
* Hash key name of the existing DynamoDB table or name of the hash key
* to create if the table does not exist and the `createTableOptions`
* do not provide a hash key name.
*
* @defaultValue 'id'
*/
readonly hashKey?: string;
/**
* Prefix to add to the `sid` in the `hashKey` written to the DynamoDB table.
*
* @defaultValue 'session#'
*/
readonly prefix?: string;
/**
* Create the DynamoDB table if it does not exist with OnDemand capacity
*
* @remarks
*
* ⛔️ NOT SUGGESTED ⛔️: this can create the table in many accounts and regions
* for any developer running the app locally with AWS credentials that
* have permission to create tables. This is also a bad idea
* because the least expensive option for relatively stable loads
* is to use ProvisionedCapacity with Application Auto Scaling
* configured to adjust the Read and Write capacity.
*
* Set to `{}` enable creation of the table with default parameters, or
* specify additional parameters.
*
* @default undefined - table will not be created
*/
readonly createTableOptions?: Partial<CreateTableCommandInput>;
/**
* Use Strongly Consistent Reads for session reads
*
* @remarks
* Strongly Consistent Reads should rarely be needed for a session store unless
* the values in the session are updated frequently and they must absolutely
* be the most recent version (which is very unliley as the most recent
* write could fail, in which case the session would not be the most
* recent version...).
*
* Reasons not to use Strongly Consistent Reads:
* - They cost 2x more than Eventually Consistent Reads
* - They can return a 500 if there is a network error or outage
* - They can have higher latency than Eventually Consistent Reads
*
* @see { @link https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadConsistency.html }
* @see { @link https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadWriteCapacityMode.html}
*
* @defaultValue false
*/
readonly useStronglyConsistentReads?: boolean;
}
/**
* DynamoDBStore is an [express-session](https://www.npmjs.com/package/express-session) store that uses
* [DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Introduction.html)
* as the backing store.
*
* @remarks
*
* DynamoDB is an excellent choice for session stores because it is
* a fully managed service that is highly available, durable, and
* can scale automatically (to nearly unlimited levels) to meet demand.
*
* DynamoDB reads will typically return in 1-3 ms if capacity is set
* correctly and the caller is located in the same region as the `Table`.
*
* ### Example of Pricing
*
* Disclaimer: perform your own pricing calculation, monitor your costs
* while launching, and setup cost alerts to avoid unexpected charges.
*
* [Saved AWS Pricing Calculation](https://calculator.aws/#/estimate?id=fb2f0d461ab2acd6c98a107059f75a4325918bda)
*
* Assumptions:
* - Using Provisioned Capacity with auto-scaling
* - Using Eventually Consistent Reads
* - 2 KB average session size
* - 100k RPM (requests per minute) average load
* - 1 million new sessions per month (~0.4 new sessions / second)
* - 8 million existing sessions
* - 2 million session updates / expirations per month (~0.8 updates / second)
*
* Pricing:
* - Storage
* - 2 KB * 8 million = 16 GB of storage
* - 16 GB * $0.25 / GB / month = $4 / month for storage
* - Reads
* - 100k RPM / 60 seconds = ~1,700 RPS (requests per second)
* - 1 RCU (read capacity unit) per item * 0.5 (eventually consistent reads) = 0.5 RCU per read
* - 1,700 RPS * 0.5 RCU per read = 850 RCUs
* - 850 RCUs / read * 720 hours / month * $0.00013 / RCU / hour = ~$80 / month for reads
* - Writes
* - 0.4 new sessions / second + 0.8 updates / second = 1.2 WPS (writes per second)
* - 1.2 WPS * 2 WCU (write capacity unit) per item = 2.4 WCUs
* - Allocate more WCUs to handle bursts
* - 100 WCUs * 720 hours / month * $0.00065 / WCU / hour = ~$50 / month for writes
* - Total
* - $4 / month for storage
* - $80 / month for reads
* - $50 / month for writes
* - $134 / month total
*/
export declare class DynamoDBStore extends Store {
private _dynamoDBClient;
private _ddbDocClient;
private _createTableOptions?;
private _tableName;
/**
* { @inheritDoc DynamoDBStoreOptions.tableName }
*/
get tableName(): string;
private _touchAfter;
/**
* { @inheritDoc DynamoDBStoreOptions.touchAfter }
*/
get touchAfter(): number;
private _useStronglyConsistentReads;
/**
* { @inheritDoc DynamoDBStoreOptions.useStronglyConsistentReads }
*/
get useStronglyConsistentReads(): boolean;
set useStronglyConsistentReads(value: boolean);
private _hashKey;
/**
* { @inheritDoc DynamoDBStoreOptions.hashKey }
*/
get hashKey(): string;
private _prefix;
/**
* { @inheritDoc DynamoDBStoreOptions.prefix }
*/
get prefix(): string;
/**
* Create the table if it does not exist
* Enable TTL field on the table if configured
*
* @remarks
* ⛔️ NOT SUGGESTED ⛔️: This is not recommended for production use.
*
* For production the table should be created with IaaC (infrastructure as code)
* such as AWS CDK, SAM, CloudFormation, Terraform, etc.
*/
private createTableIfNotExists;
/**
* Create a DynamoDB Table-based [express-session](https://www.npmjs.com/package/express-session) store.
*
* @remarks
* ⛔️ NOT SUGGESTED ⛔️: `createTableOptions` is not recommended for production use.
*
* Note: This does not await creation of a table if `createTableOptions` is passed (which should only
* be used in quick and dirty tests). Use `DynamoDBStore.create()` instead to await
* creation of the table in testing scenarios.
*/
constructor(options: DynamoDBStoreOptions);
/**
* Create the store and optionally await creation of the table.
*
* Note: Store-created tables is not advised for production use.
*
* @param options DynamoDBStore options
*/
static create(options: DynamoDBStoreOptions): Promise<DynamoDBStore>;
get(
/**
* Session ID
*/
sid: string,
/**
* Callback to return the session data
* @param err Error
* @param session Session data
* @returns void
*/
callback: (err: unknown, session?: SessionData | null) => void): void;
set(
/**
* Session ID
*/
sid: string,
/**
* Session data
* @remarks
* The `expires` field is set by the session middleware and is used
* by DynamoDB to automatically expire the session.
* @see {@link https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TTL.html}
* @see {@link https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/howitworks-ttl.html}
* @see {@link https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/time-to-live-ttl-how-to.html}
*/
session: SessionData,
/**
* Callback to return an error if the session was not saved
* @param err Error
* @returns void
*/
callback?: (err?: unknown) => void): void;
/**
* Reset the TTL on the DynamoDB record to 100% of the original TTL
*
* @remarks
* This is called by the session middleware on every single `get` request.
*/
touch(
/**
* Session ID
*/
sid: string,
/**
* Session data
*/
session: SessionData & {
lastModified?: string;
},
/**
* Callback to return an error if the session TTL was not updated
*/
callback?: (err?: unknown) => void): void;
/**
* Destroy the session in DynamoDB
*/
destroy(
/**
* Session ID
*/
sid: string,
/**
* Callback to return an error if the session was not destroyed
* @param err Error
* @returns void
*/
callback?: (err?: unknown) => void): void;
private newExpireSecondsSinceEpochUTC;
private getTTLSeconds;
}
//# sourceMappingURL=dynamodb-store.d.ts.map