Skip to content

Instantly share code, notes, and snippets.

@ThomasAribart
Last active February 19, 2021 16:56
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ThomasAribart/618a3b097a29cbfb26094ed064c39041 to your computer and use it in GitHub Desktop.
Save ThomasAribart/618a3b097a29cbfb26094ed064c39041 to your computer and use it in GitHub Desktop.
declare module 'dynamodb-toolbox' {
// Imports
import type { DocumentClient } from 'aws-sdk/lib/dynamodb/document_client';
// Helpers
type Writeable<O> = O extends
| Record<string, unknown>
| Record<number, unknown>
? { -readonly [K in keyof O]: Writeable<O[K]> }
: O;
type Replace<
O extends Record<string | number | symbol, any>,
K extends keyof O,
V
> = Omit<O, K> & { [key in K]: V };
type Merge<
A extends Record<string, unknown>,
B extends Record<string, unknown>
// Somehow this double check is necessary for a nice rendering in hover windows
> = A extends Record<string, unknown>
? B extends Record<string, unknown>
? {
[K in keyof (A & B)]: K extends keyof B
? B[K]
: K extends keyof A
? A[K]
: never;
}
: never
: never;
type Filter<
Item extends Record<string, unknown>,
ReqProps extends keyof Item,
OptProps extends keyof Item,
FiltProps extends keyof Item = keyof Item
> = Merge<
{
[Key in FiltProps & OptProps]?: Item[Key];
},
{
[Key in FiltProps & ReqProps]: Item[Key];
}
>;
type If<C, T, F> = C extends true ? T : F;
type DoesExtend<A, B> = A extends B ? true : false;
// Table
type TableAttributes = Record<string, DynamoDBType>;
type Indexes = Record<string, { partitionKey?: string; sortKey?: string }>;
interface TableConstructor {
name: string;
alias?: string;
partitionKey: string;
sortKey?: string;
entityField?: boolean | string;
attributes?: TableAttributes;
indexes?: Indexes;
autoExecute?: boolean;
autoParse?: boolean;
DocumentClient?: DocumentClient;
}
type WriteOperation = Record<string, Record<string, unknown>>;
type GetOperation = Record<string, Record<string, unknown>>;
export class Table {
name: string;
constructor(options: TableConstructor);
// Methods
batchWrite(
operations: WriteOperation[],
options?: Record<string, unknown>,
parameters?: Record<string, unknown>,
): Promise<DocumentClient.BatchWriteItemOutput>;
batchGet(
operations: GetOperation[],
options?: Record<string, unknown>,
parameters?: Record<string, unknown>,
): Promise<DocumentClient.BatchGetItemOutput>;
scan(
options?: Record<string, unknown>,
parameters?: Record<string, unknown>,
): Promise<
DocumentClient.ScanOutput & {
next?: () => Promise<DocumentClient.ScanOutput>;
}
>;
query(
partitionKey: string,
options?: Record<string, unknown>,
parameters?: Record<string, unknown>,
): Promise<DocumentClient.QueryOutput>;
}
// Entity
type EntityPureAttrDef = Partial<{
partitionKey: boolean;
sortKey: boolean;
hidden: boolean;
type: DynamoDBType;
map: string;
save: boolean;
default: unknown;
required: boolean | 'always';
}>;
type EntityMappedAttrDef =
| [string, number]
| [string, number, EntityPureAttrDef];
type EntityAttrDef = DynamoDBType | EntityPureAttrDef | EntityMappedAttrDef;
type EntityAttrDefs = Record<string, EntityAttrDef>;
type EntityReadonlyPureAttrDef = Readonly<EntityPureAttrDef>;
type EntityReadonlyMappedAttrDef = Readonly<
[string, number] | [string, number, EntityReadonlyPureAttrDef]
>;
type EntityReadonlyAttrDef =
| DynamoDBType
| EntityReadonlyPureAttrDef
| EntityReadonlyMappedAttrDef;
type EntityReadonlyAttrDefs = Readonly<Record<string, EntityReadonlyAttrDef>>;
// Attributes parsing
type InferKeyOfType<
A extends EntityAttrDefs,
KeyType extends 'partitionKey' | 'sortKey'
> = {
[key in keyof A]: A[key] extends EntityPureAttrDef
? KeyType extends keyof A[key]
? A[key][KeyType] extends true
? Extract<key, string>
: never
: never
: never;
}[keyof A];
type InferIsKeyMapped<
A extends EntityAttrDefs,
K extends string
> = DoesExtend<
true,
{
[attr in keyof A]: A[attr] extends EntityMappedAttrDef
? DoesExtend<K, A[attr][0]>
: false;
}[keyof A]
>;
type InferMappedAttr<A extends EntityAttrDefs, K extends string> = {
[key in keyof A]: A[key] extends EntityMappedAttrDef
? K extends A[key][0]
? Extract<key, string>
: never
: never;
}[keyof A];
type InferAttrValue<A extends EntityAttrDefs, K extends keyof A> = {
dynamoDbType: A[K] extends DynamoDBType ? InferValueType<A[K]> : never;
entityPureAttrDef: A[K] extends EntityPureAttrDef
? 'type' extends keyof A[K]
? InferValueType<A[K]['type']>
: unknown
: never;
entityMappedAttrDef: A[K] extends EntityMappedAttrDef
? A[K][0] extends keyof A
? InferAttrValue<A, A[K][0]>
: unknown
: never;
}[A[K] extends DynamoDBType
? 'dynamoDbType'
: A[K] extends EntityPureAttrDef
? 'entityPureAttrDef'
: A[K] extends EntityMappedAttrDef
? 'entityMappedAttrDef'
: never];
type InferItem<A extends EntityAttrDefs> = {
[K in keyof A]: InferAttrValue<A, K>;
};
type InferIsAttrAlways<A extends EntityPureAttrDef> = If<
DoesExtend<'default', keyof A>,
false,
DoesExtend<A['required'], 'always'>
>;
type InferAlwaysAttr<A extends EntityAttrDefs> = {
[K in keyof A]: A[K] extends EntityPureAttrDef
? If<InferIsAttrAlways<A[K]>, K, never>
: A[K] extends EntityMappedAttrDef
? 2 extends keyof A[K]
? If<InferIsAttrAlways<A[K][2]>, K, never>
: never
: never;
}[keyof A];
type InferIsAttrReq<A extends EntityPureAttrDef> = If<
DoesExtend<'default', keyof A>,
false,
DoesExtend<A['required'], true>
>;
type InferReqAttr<A extends EntityAttrDefs> = {
[K in keyof A]: A[K] extends EntityPureAttrDef
? If<InferIsAttrReq<A[K]>, K, never>
: A[K] extends EntityMappedAttrDef
? 2 extends keyof A[K]
? If<InferIsAttrReq<A[K][2]>, K, never>
: never
: never;
}[keyof A];
interface GetOptions {
consistent: boolean;
capacity: 'none' | 'total' | 'indexes';
execute: boolean;
parse: boolean;
}
interface QueryOptions {
index: string;
beginsWith: string;
eq: string;
reverse: boolean;
filters: unknown;
between: unknown[];
consistent: boolean;
limit: number;
}
export class Entity<
Name extends string,
ReadonlyAttrDefs extends EntityAttrDefs | EntityReadonlyAttrDefs,
AttrDefs extends EntityAttrDefs = Writeable<ReadonlyAttrDefs>,
Item extends Record<string, unknown> = InferItem<AttrDefs>,
PK extends string = InferKeyOfType<AttrDefs, 'partitionKey'>,
IsPKMapped extends boolean = InferIsKeyMapped<AttrDefs, PK>,
PKMappedAttr extends string = IsPKMapped extends true
? InferMappedAttr<AttrDefs, PK>
: never,
/**
* @debt dynamodb-toolbox-typing "Handle no SK edge case"
*/
SK extends string = InferKeyOfType<AttrDefs, 'sortKey'>,
IsSKMapped extends boolean = InferIsKeyMapped<AttrDefs, SK>,
SKMappedAttr extends string = IsSKMapped extends true
? InferMappedAttr<AttrDefs, SK>
: never,
PrimaryKey extends Record<string, unknown> = (IsPKMapped extends true
? Pick<Item, PK> | Pick<Item, PKMappedAttr>
: Pick<Item, PK>) &
(IsSKMapped extends true
? Pick<Item, SK> | Pick<Item, SKMappedAttr>
: Pick<Item, SK>),
Attr extends keyof Item = keyof Item,
KeyAttr extends Attr = Extract<PK | PKMappedAttr | SK | SKMappedAttr, Attr>,
AlwaysAttr extends Attr = Exclude<
Extract<InferAlwaysAttr<AttrDefs>, Attr>,
KeyAttr
>,
ReqAttr extends Attr = Exclude<
Extract<InferReqAttr<AttrDefs>, Attr>,
KeyAttr
>,
OptAttr extends Attr = Exclude<Attr, KeyAttr | AlwaysAttr | ReqAttr>
> {
constructor(
options: { name: Name; attributes: ReadonlyAttrDefs } & Record<
string,
unknown
>,
);
// Properties
readonly name: Name;
table: Table;
readonly DocumentClient: DocumentClient;
autoExecute: boolean;
autoParse: boolean;
readonly partitionKey: PK;
readonly sortKey: SK;
// Get
getParams<A extends Attr[]>(
primaryKey: PrimaryKey,
options?: Partial<{ attributes: A } & GetOptions>,
parameters?: Record<string, unknown>,
): DocumentClient.GetItemInput;
get<A extends Attr[]>(
primaryKey: PrimaryKey,
options?: Partial<{ attributes: A } & GetOptions>,
parameters?: Record<string, unknown>,
): Promise<
Replace<
DocumentClient.GetItemOutput,
'Item',
/**
* @debt dynamodb-toolbox-typing "Use hidden directive to hide pk/sk from result type ?"
*/
Filter<Item, KeyAttr | AlwaysAttr | ReqAttr, OptAttr, A[number]>
>
>;
// Delete
deleteParams(
primaryKey: PrimaryKey,
options?: Record<string, unknown>,
parameters?: Record<string, unknown>,
): DocumentClient.DeleteItemInput;
delete(
primaryKey: PrimaryKey,
options?: Record<string, unknown>,
parameters?: Record<string, unknown>,
): Promise<DocumentClient.DeleteItemOutput>;
// Put
putParams(
item: PrimaryKey &
{ [reqAttr in AlwaysAttr | ReqAttr]: Item[reqAttr] } &
{ [optAttr in OptAttr]?: Item[optAttr] },
/**
* @debt dynamodb-toolbox-typing "Use Attr in conditions attr"
*/
options?: Record<string, unknown>,
parameters?: Record<string, unknown>,
): DocumentClient.PutItemInput;
put(
item: PrimaryKey &
{ [reqAttr in AlwaysAttr | ReqAttr]: Item[reqAttr] } &
{ [optAttr in OptAttr]?: Item[optAttr] },
/**
* @debt dynamodb-toolbox-typing "Use Attr in conditions attr"
*/
options?: Record<string, unknown>,
parameters?: Record<string, unknown>,
/**
* @debt dynamodb-toolbox-typing "Type put return value"
*/
): Promise<DocumentClient.PutItemOutput>;
// Update
updateParams(
primaryKey: PrimaryKey &
{ [attr in AlwaysAttr]: Item[attr] } &
{
[attr in OptAttr | ReqAttr]?:
| Item[attr]
| { $delete?: string[]; $add?: unknown };
} & { $remove?: OptAttr[] },
options?: Record<string, unknown>,
parameters?: Record<string, unknown>,
): DocumentClient.UpdateItemInput;
update(
primaryKey: PrimaryKey &
{ [attr in AlwaysAttr]: Item[attr] } &
{
[attr in OptAttr | ReqAttr]?:
| Item[attr]
| { $delete?: string[]; $add?: unknown };
} & { $remove?: OptAttr[] },
options?: Record<string, unknown>,
parameters?: Record<string, unknown>,
): Promise<
Replace<
DocumentClient.UpdateItemOutput,
'Attributes',
/**
* @debt dynamodb-toolbox-typing "TODO: Use returnValues argument"
*/
Filter<Item, KeyAttr | AlwaysAttr | ReqAttr, OptAttr>
>
>;
// Query
queryParams<A extends Attr[]>(
partitionKey: unknown,
options?: Partial<{ attributes: A } & QueryOptions>,
parameters?: Record<string, unknown>,
): DocumentClient.QueryInput;
query<A extends Attr[]>(
partitionKey: unknown,
options?: Partial<{ attributes: A } & QueryOptions>,
parameters?: Record<string, unknown>,
): Promise<
Replace<
DocumentClient.QueryOutput,
'Items',
Filter<Item, KeyAttr | AlwaysAttr | ReqAttr, OptAttr, A[number]>[]
>
>;
putBatch(
item: Record<string, unknown>,
options?: Record<string, unknown>,
parameters?: Record<string, unknown>,
): WriteOperation;
deleteBatch(
primaryKey: Record<string, unknown>,
options?: Record<string, unknown>,
parameters?: Record<string, unknown>,
): WriteOperation;
}
// DynamoDB
type DynamoDBStringType = 'string';
type DynamoDBBooleanType = 'boolean';
type DynamoDBNumberType = 'number';
type DynamoDBListType = 'list';
type DynamoDBMapType = 'map';
type DynamoDBBinaryType = 'binary';
type DynamoDBSetType = 'set';
type DynamoDBType =
| DynamoDBStringType
| DynamoDBBooleanType
| DynamoDBNumberType
| DynamoDBListType
| DynamoDBMapType
| DynamoDBBinaryType
| DynamoDBSetType;
type InferValueType<T extends DynamoDBType> = {
string: string;
boolean: boolean;
number: number;
list: any[];
map: any;
binary: unknown;
set: Set<unknown>;
}[T];
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment