Skip to content

Commit

Permalink
Release/2.7.0 (tywalch#260)
Browse files Browse the repository at this point in the history
* Enables set/list usage with contains filer

* Adds change log

* Adds change log

* Adds if_not_exists upsert condition

* Working tests

* Updates documentation

* Cleaning up unused upsert code
  • Loading branch information
tywalch authored Jul 2, 2023
1 parent 2d890ee commit 3ce5403
Show file tree
Hide file tree
Showing 23 changed files with 678 additions and 133 deletions.
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -333,4 +333,13 @@ All notable changes to this project will be documented in this file. Breaking ch

## [2.6.1] - 2023-06-09
### Added
- For queries, ElectroDB now trims the ExclusiveStartKey object to only include the keys associated with the index provided. DynamoDB currently rejects queries when properties not associated with the keys of the queried index are provided on the ExclusiveStartKey. By removing irrelevant properties, ElectroDB offers users more flexibility and opportunities for dynamic querying.
- For queries, ElectroDB now trims the ExclusiveStartKey object to only include the keys associated with the index provided. DynamoDB currently rejects queries when properties not associated with the keys of the queried index are provided on the ExclusiveStartKey. By removing irrelevant properties, ElectroDB offers users more flexibility and opportunities for dynamic querying.

## [2.7.0] - 2023-07-01
### Fixed
- Fixes return typing for `delete`, `remove`, `update` and `upsert` operations. These types were incorrect and did not reflect the real values returned. Instead of breaking the APIs, changing response types to `T | null`, the new response type is now the Entity's key composite values by default. You can also now use the Execution Option `response` to get back the item as it exists in the table. This is the closed I could get to a non-breaking change that also fixes the incorrect return typing for these methods.
- Fixes typing for `contains` where conditions to accept collection element values (e.g., `set` and `list` type attributes).
- The exported type `UpdateEntityItem` was incorrectly typed, it now includes the correct typing as the values that can be passed to the `set` method

### Changed
- Upsert operations now take into consideration `readOnly` attributes when applying changes. If an attribute is configured as `readOnly` ElectroDB will apply the property with an `if_not_exists` set operation to prevent overwriting the existing value if one is set.
87 changes: 65 additions & 22 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,8 @@ export interface CollectionWhereOperations {
begins: <T, A extends WhereAttributeSymbol<T>>(attr: A, value: T) => string;
exists: <T, A extends WhereAttributeSymbol<T>>(attr: A) => string;
notExists: <T, A extends WhereAttributeSymbol<T>>(attr: A) => string;
contains: <T, A extends WhereAttributeSymbol<T>>(attr: A, value: T) => string;
notContains: <T, A extends WhereAttributeSymbol<T>>(attr: A, value: T) => string;
contains: <T, A extends WhereAttributeSymbol<T>>(attr: A, value: A extends WhereAttributeSymbol<infer V> ? V extends Array<infer I> ? I : V : never) => string;
notContains: <T, A extends WhereAttributeSymbol<T>>(attr: A, value: A extends WhereAttributeSymbol<infer V> ? V extends Array<infer I> ? I : V : never) => string;
value: <T, A extends WhereAttributeSymbol<T>>(attr: A, value: T) => string;
name: <T, A extends WhereAttributeSymbol<T>>(attr: A) => string;
size: <T, A extends WhereAttributeSymbol<T>>(attr: A) => string;
Expand Down Expand Up @@ -601,6 +601,11 @@ export type ElectroEventListener = (event: ElectroEvent) => void;
// details: any;
// };

export type EntityIdentifiers<E extends Entity<any, any, any, any>> =
E extends Entity<infer A, infer F, infer C, infer S>
? AllTableIndexCompositeAttributes<A, F, C, S>
: never;

export type EntityItem<E extends Entity<any, any, any, any>> =
E extends Entity<infer A, infer F, infer C, infer S>
? ResponseItem<A, F, C, S>
Expand All @@ -621,9 +626,14 @@ export type BatchGetEntityItem<E extends Entity<any, any, any, any>> =
? ResponseItem<A, F, C, S>[]
: never;

export type UpdateEntityResponseItem<E extends Entity<any, any, any, any>> =
E extends Entity<infer A, infer F, infer C, infer S>
? AllTableIndexCompositeAttributes<A,F,C,S>
: never;

export type UpdateEntityItem<E extends Entity<any, any, any, any>> =
E extends Entity<infer A, infer F, infer C, infer S>
? Partial<ResponseItem<A,F,C,S>>
? SetItem<A,F,C,S>
: never;

export type UpdateAddEntityItem<E extends Entity<any, any, any, any>> =
Expand Down Expand Up @@ -692,7 +702,7 @@ export type BatchGetResponse<E extends Entity<any, any, any, any>> =
} : never;

export type UpdateEntityResponse<E extends Entity<any, any, any, any>> = {
data: UpdateEntityItem<E>;
data: UpdateEntityResponseItem<E>;
}
export type UpdateAddEntityResponse<E extends Entity<any, any, any, any>> = {
data: UpdateAddEntityItem<E>;
Expand Down Expand Up @@ -793,7 +803,7 @@ export type UpdateRecordGoTransaction<ResponseType> = <T = ResponseType, Options
export interface SetRecordActionOptionsTransaction<A extends string, F extends string, C extends string, S extends Schema<A,F,C>, SetAttr,IndexCompositeAttributes,TableItem> {
commit: UpdateRecordGoTransaction<TableItem>;
params: ParamRecord<UpdateQueryParams>;
set: SetRecordTransaction<A,F,C,S, SetItem<A,F,C,S>,IndexCompositeAttributes,TableItem>;
set: SetRecordTransaction<A,F,C,S, SetItem<A,F,C,S>, IndexCompositeAttributes, TableItem>;
remove: RemoveRecordTransaction<A,F,C,S, Array<keyof SetItem<A,F,C,S>>,IndexCompositeAttributes,TableItem>;
add: SetRecordTransaction<A,F,C,S, AddItem<A,F,C,S>,IndexCompositeAttributes,TableItem>;
subtract: SetRecordTransaction<A,F,C,S, SubtractItem<A,F,C,S>,IndexCompositeAttributes,TableItem>;
Expand All @@ -818,19 +828,19 @@ export interface BatchGetRecordOperationOptions<A extends string, F extends stri
}

export interface PutRecordOperationOptions<A extends string, F extends string, C extends string, S extends Schema<A,F,C>, ResponseType> {
go: PutRecordGo<ResponseType, PutQueryOptions>;
go: PutRecordGo<ResponseType>;
params: ParamRecord<PutQueryOptions>;
where: WhereClause<A, F, C, S, Item<A, F, C, S, S["attributes"]>, PutRecordOperationOptions<A, F, C, S, ResponseType>>;
}

export interface UpsertRecordOperationOptions<A extends string, F extends string, C extends string, S extends Schema<A,F,C>, ResponseType> {
go: PutRecordGo<ResponseType, UpdateQueryParams>;
go: UpsertRecordGo<ResponseType, AllTableIndexCompositeAttributes<A,F,C,S>>;
params: ParamRecord<UpdateQueryParams>;
where: WhereClause<A, F, C, S, Item<A, F, C, S, S["attributes"]>, UpsertRecordOperationOptions<A, F, C, S, ResponseType>>;
}

export interface DeleteRecordOperationOptions<A extends string, F extends string, C extends string, S extends Schema<A,F,C>, ResponseType> {
go: DeleteRecordOperationGo<ResponseType, DeleteQueryOptions>;
go: DeleteRecordOperationGo<ResponseType, AllTableIndexCompositeAttributes<A,F,C,S>>;
params: ParamRecord<DeleteQueryOptions>;
where: WhereClause<A,F,C,S,Item<A,F,C,S,S["attributes"]>,DeleteRecordOperationOptions<A,F,C,S,ResponseType>>;
}
Expand All @@ -841,9 +851,10 @@ export interface BatchWriteOperationOptions<A extends string, F extends string,
}

export interface SetRecordActionOptions<A extends string, F extends string, C extends string, S extends Schema<A,F,C>, SetAttr,IndexCompositeAttributes,TableItem> {
go: UpdateRecordGo<TableItem>;
go: UpdateRecordGo<TableItem, AllTableIndexCompositeAttributes<A,F,C,S>>;
params: ParamRecord<UpdateQueryParams>;
set: SetRecord<A,F,C,S, SetItem<A,F,C,S>,IndexCompositeAttributes,TableItem>;
set: SetRecord<A,F,C,S, SetItem<A,F,C,S>,IndexCompositeAttributes, TableItem>;
// ifNotExists: SetRecord<A,F,C,S, SetItem<A,F,C,S>,IndexCompositeAttributes,TableItem>;
remove: SetRecord<A,F,C,S, Array<keyof SetItem<A,F,C,S>>,IndexCompositeAttributes,TableItem>;
add: SetRecord<A,F,C,S, AddItem<A,F,C,S>,IndexCompositeAttributes,TableItem>;
subtract: SetRecord<A,F,C,S, SubtractItem<A,F,C,S>,IndexCompositeAttributes,TableItem>;
Expand Down Expand Up @@ -1005,7 +1016,7 @@ export interface DeleteQueryOptions extends QueryOptions {
}

export interface PutQueryOptions extends QueryOptions {
response?: "default" | "none" | 'all_old';
response?: "default" | "none" | 'all_old' | 'all_new';
}

export interface ParamOptions {
Expand Down Expand Up @@ -1206,20 +1217,52 @@ export type ServiceQueryRecordsGo<ResponseType, Options = ServiceQueryGoTerminal

export type QueryRecordsGo<ResponseType, Options = QueryOptions> = <T = ResponseType>(options?: Options) => Promise<{ data: T, cursor: string | null }>;

export type UpdateRecordGo<ResponseType> = <T = ResponseType, Options extends UpdateQueryOptions = UpdateQueryOptions>(options?: Options) =>
export type UpdateRecordGo<ResponseType, Keys> = <T = ResponseType, Options extends UpdateQueryOptions = UpdateQueryOptions>(options?: Options) =>
Options extends infer O
? 'response' extends keyof O
? O['response'] extends 'all_new'
? Promise<{data: T}>
: O['response'] extends 'all_old'
? Promise<{data: T}>
: Promise<{data: Partial<T>}>
: Promise<{data: Partial<T>}>
? O['response'] extends 'all_new'
? Promise<{ data: T }>
: O['response'] extends 'all_old'
? Promise<{ data: T }>
: O['response'] extends 'default'
? Promise<{ data: Keys }>
: O['response'] extends 'none'
? Promise<{ data: null }>
: Promise<{ data: Partial<T> }>
: Promise<{ data: Keys }>
: never;

export type PutRecordGo<ResponseType, Options = QueryOptions> = <T = ResponseType>(options?: Options) => Promise<{ data: T }>;
export type UpsertRecordGo<ResponseType, Keys> = <T = ResponseType, Options extends UpdateQueryOptions = UpdateQueryOptions>(options?: Options) =>
Options extends infer O
? 'response' extends keyof O
? O['response'] extends 'all_new'
? Promise<{ data: T }>
: O['response'] extends 'all_old'
? Promise<{ data: T }>
: O['response'] extends 'default'
? Promise<{ data: Keys }>
: O['response'] extends 'none'
? Promise<{ data: null }>
: Promise<{ data: Partial<T> }>
: Promise<{ data: Keys }>
: never;

export type DeleteRecordOperationGo<ResponseType, Options = QueryOptions> = <T = ResponseType>(options?: Options) => Promise<{ data: T }>;
export type PutRecordGo<ResponseType> = <T = ResponseType, Options extends PutQueryOptions = PutQueryOptions>(options?: Options) => Promise<{ data: T }>

export type DeleteRecordOperationGo<ResponseType, Keys> = <T = ResponseType, Options extends DeleteQueryOptions = DeleteQueryOptions>(options?: Options) =>
Options extends infer O
? 'response' extends keyof O
? O['response'] extends 'all_new'
? Promise<{ data: T }>
: O['response'] extends 'all_old'
? Promise<{ data: T }>
: O['response'] extends 'default'
? Promise<{ data: Keys }>
: O['response'] extends 'none'
? Promise<{ data: null }>
: Promise<{ data: Partial<T> }>
: Promise<{ data: Keys }>
: never;

export type BatchWriteGo<ResponseType> = <O extends BulkOptions>(options?: O) =>
Promise<{ unprocessed: ResponseType }>
Expand Down Expand Up @@ -2419,8 +2462,8 @@ export interface WhereOperations<A extends string, F extends string, C extends s
begins: <T, A extends WhereAttributeSymbol<T>>(attr: A, value: T) => string;
exists: <A extends WhereAttributeSymbol<any>>(attr: A) => string;
notExists: <A extends WhereAttributeSymbol<any>>(attr: A) => string;
contains: <T, A extends WhereAttributeSymbol<T>>(attr: A, value: T) => string;
notContains: <T, A extends WhereAttributeSymbol<T>>(attr: A, value: T) => string;
contains: <T, A extends WhereAttributeSymbol<T>>(attr: A, value: A extends WhereAttributeSymbol<infer V> ? V extends Array<infer I> ? I : V : never) => string;
notContains: <T, A extends WhereAttributeSymbol<T>>(attr: A, value: A extends WhereAttributeSymbol<infer V> ? V extends Array<infer I> ? I : V : never) => string;
value: <T, A extends WhereAttributeSymbol<T>>(attr: A, value: A extends WhereAttributeSymbol<infer V> ? V : never) => A extends WhereAttributeSymbol<infer V> ? V : never;
name: <A extends WhereAttributeSymbol<any>>(attr: A) => string;
size: <T, A extends WhereAttributeSymbol<T>>(attr: A) => number;
Expand Down
34 changes: 28 additions & 6 deletions index.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import {WhereAttributeSymbol, UpdateEntityItem, Schema, EntityItem, Entity, Service, ResponseItem} from './';
import {
WhereAttributeSymbol,
UpdateEntityItem,
Schema,
EntityItem,
Entity,
Service,
UpdateEntityResponse,
} from './';
import {expectType, expectError, expectAssignable, expectNotAssignable, expectNotType} from 'tsd';
import * as tests from './test/tests.test-d';

type Resolve<T> = T extends Function | string | number | boolean
? T : {[Key in keyof T]: Resolve<T[Key]>}

const magnify = <T>(value: T): Resolve<T> => { return {} as Resolve<T> };
const magnify = <T>(value: T): Resolve<T> => {
return value as Resolve<T>
};

let entityWithSK = new Entity({
model: {
Expand Down Expand Up @@ -3187,7 +3197,7 @@ expectError(() => {
.go()
});

type ComplexShapesUpdateItem = UpdateEntityItem<typeof entityWithComplexShapes>;
type ComplexShapesUpdate = UpdateEntityResponse<typeof entityWithComplexShapes>;

const updateWithWhere = entityWithComplexShapes.update({
prop1: 'abc', prop2: 'def',
Expand Down Expand Up @@ -3221,7 +3231,19 @@ entityWithComplexShapes.update({
.go({

}).then(res => {
expectType<ComplexShapesUpdateItem>(res.data);
expectType<ComplexShapesUpdate>(res);
});

entityWithComplexShapes.patch({
prop1: 'abc', prop2: 'def',
})
.data((attr, op) => {
})
.where((attr, op) => op.eq(attr.prop3.val1, 'def'))
.go({
response: 'all_new'
}).then(res => {
expectType<EntityItem<typeof entityWithComplexShapes>>(res.data);
});

entityWithComplexShapes.update({
Expand All @@ -3232,7 +3254,7 @@ entityWithComplexShapes.update({
.go({
originalErr: true,
}).then(res => {
expectType<ComplexShapesUpdateItem>(res.data);
expectType<{ prop1: string, prop2: string }>(magnify(res.data));
});

entityWithComplexShapes.update({
Expand All @@ -3247,7 +3269,7 @@ entityWithComplexShapes.update({

})
.then(res => {
expectType<ComplexShapesUpdateItem>(res.data);
expectType<ComplexShapesUpdate>(res);
});

entityWithComplexShapes.remove({
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "electrodb",
"version": "2.6.1",
"version": "2.7.0",
"description": "A library to more easily create and interact with multiple entities and heretical relationships in dynamodb",
"main": "index.js",
"scripts": {
Expand Down
51 changes: 38 additions & 13 deletions src/clauses.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,13 +185,17 @@ let clauses = {
}
try {
const {pk, sk} = state.getCompositeAttributes();
const pkComposite = entity._expectFacets(facets, pk);
state.addOption('_includeOnResponseItem', pkComposite);
return state
.setMethod(MethodTypes.delete)
.setType(QueryTypes.eq)
.setPK(entity._expectFacets(facets, pk))
.setPK(pkComposite)
.ifSK(() => {
entity._expectFacets(facets, sk);
state.setSK(state.buildQueryComposites(facets, sk));
const skComposite = state.buildQueryComposites(facets, sk);
state.setSK(skComposite);
state.addOption('_includeOnResponseItem', {...skComposite, ...pkComposite});
});
} catch(err) {
state.setError(err);
Expand All @@ -215,13 +219,17 @@ let clauses = {
if (sk) {
filter.unsafeSet(FilterOperationNames.exists, sk);
}
const pkComposite = entity._expectFacets(facets, attributes.pk);
state.addOption('_includeOnResponseItem', pkComposite);
return state
.setMethod(MethodTypes.delete)
.setType(QueryTypes.eq)
.setPK(entity._expectFacets(facets, attributes.pk))
.setPK(pkComposite)
.ifSK(() => {
entity._expectFacets(facets, attributes.sk);
state.setSK(state.buildQueryComposites(facets, attributes.sk));
const skComposite = state.buildQueryComposites(facets, attributes.sk);
state.setSK(skComposite);
state.addOption('_includeOnResponseItem', {...skComposite, ...pkComposite});
});
} catch(err) {
state.setError(err);
Expand All @@ -239,14 +247,18 @@ let clauses = {
try {
let record = entity.model.schema.checkCreate({...payload});
const attributes = state.getCompositeAttributes();
const pkComposite = entity._expectFacets(record, attributes.pk);
state.addOption('_includeOnResponseItem', pkComposite);
return state
.setMethod(MethodTypes.upsert)
.setType(QueryTypes.eq)
.applyUpsert(record)
.setPK(entity._expectFacets(record, attributes.pk))
.setPK(pkComposite)
.ifSK(() => {
entity._expectFacets(record, attributes.sk);
state.setSK(entity._buildQueryFacets(record, attributes.sk));
const skComposite = entity._buildQueryFacets(record, attributes.sk);
state.setSK(skComposite);
state.addOption('_includeOnResponseItem', {...skComposite, ...pkComposite});
})
.whenOptions(({ state, options }) => {
if (!state.getParams()) {
Expand Down Expand Up @@ -337,13 +349,17 @@ let clauses = {
if (sk) {
filter.unsafeSet(FilterOperationNames.exists, sk);
}
const pkComposite = entity._expectFacets(facets, attributes.pk);
state.addOption('_includeOnResponseItem', pkComposite);
return state
.setMethod(MethodTypes.update)
.setType(QueryTypes.eq)
.setPK(entity._expectFacets(facets, attributes.pk))
.setPK(pkComposite)
.ifSK(() => {
entity._expectFacets(facets, attributes.sk);
state.setSK(state.buildQueryComposites(facets, attributes.sk));
const skComposite = state.buildQueryComposites(facets, attributes.sk);
state.setSK(skComposite);
state.addOption('_includeOnResponseItem', {...skComposite, ...pkComposite});
});
} catch(err) {
state.setError(err);
Expand All @@ -360,13 +376,17 @@ let clauses = {
}
try {
const attributes = state.getCompositeAttributes();
const pkComposite = entity._expectFacets(facets, attributes.pk);
state.addOption('_includeOnResponseItem', pkComposite);
return state
.setMethod(MethodTypes.update)
.setType(QueryTypes.eq)
.setPK(entity._expectFacets(facets, attributes.pk))
.setPK(pkComposite)
.ifSK(() => {
entity._expectFacets(facets, attributes.sk);
state.setSK(state.buildQueryComposites(facets, attributes.sk));
const skComposite = state.buildQueryComposites(facets, attributes.sk);
state.setSK(skComposite);
state.addOption('_includeOnResponseItem', {...pkComposite, ...skComposite});
});
} catch(err) {
state.setError(err);
Expand Down Expand Up @@ -808,7 +828,8 @@ class ChainState {
data: {},
},
upsert: {
data: {}
data: {},
ifNotExists: {},
},
keys: {
provided: [],
Expand Down Expand Up @@ -1018,8 +1039,12 @@ class ChainState {
}
}

applyUpsert(data = {}) {
this.query.upsert.data = {...this.query.upsert.data, ...data};
applyUpsert(data = {}, { ifNotExists } = {}) {
if (ifNotExists) {
this.query.upsert.ifNotExists = {...this.query.upsert.ifNotExists, ...data};
} else {
this.query.upsert.data = {...this.query.upsert.data, ...data};
}
return this;
}

Expand Down
Loading

0 comments on commit 3ce5403

Please sign in to comment.