Skip to content

Commit

Permalink
Merge ae846f8 into 442a988
Browse files Browse the repository at this point in the history
  • Loading branch information
tywalch committed Nov 22, 2022
2 parents 442a988 + ae846f8 commit 7e9b9f4
Show file tree
Hide file tree
Showing 7 changed files with 632 additions and 240 deletions.
486 changes: 255 additions & 231 deletions README.md

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,12 @@ export interface PutRecordOperationOptions<A extends string, F extends string, C
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>;
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>;
params: ParamRecord<DeleteQueryOptions>;
Expand Down Expand Up @@ -2240,6 +2246,7 @@ export class Entity<A extends string, F extends string, C extends string, S exte
delete(key: AllTableIndexCompositeAttributes<A,F,C,S>[]): BatchWriteOperationOptions<A,F,C,S, AllTableIndexCompositeAttributes<A,F,C,S>[]>;
remove(key: AllTableIndexCompositeAttributes<A,F,C,S>): DeleteRecordOperationOptions<A,F,C,S, ResponseItem<A,F,C,S>>

upsert(record: PutItem<A,F,C,S>): UpsertRecordOperationOptions<A,F,C,S, ResponseItem<A,F,C,S>>;
put(record: PutItem<A,F,C,S>): PutRecordOperationOptions<A,F,C,S, ResponseItem<A,F,C,S>>;
put(record: PutItem<A,F,C,S>[]): BatchWriteOperationOptions<A,F,C,S, AllTableIndexCompositeAttributes<A,F,C,S>[]>;
create(record: PutItem<A,F,C,S>): PutRecordOperationOptions<A,F,C,S, ResponseItem<A,F,C,S>>
Expand Down
35 changes: 34 additions & 1 deletion src/clauses.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function batchAction(action, type, entity, state, payload) {
let clauses = {
index: {
name: "index",
children: ["get", "delete", "update", "query", "put", "scan", "collection", "clusteredCollection", "create", "remove", "patch", "batchPut", "batchDelete", "batchGet"],
children: ["get", "delete", "update", "query", "upsert", "put", "scan", "collection", "clusteredCollection", "create", "remove", "patch", "batchPut", "batchDelete", "batchGet"],
},
clusteredCollection: {
name: "clusteredCollection",
Expand Down Expand Up @@ -184,6 +184,31 @@ let clauses = {
},
children: ["where", "params", "go"],
},
upsert: {
name: 'upsert',
action(entity, state, payload = {}) {
if (state.getError() !== null) {
return state;
}
try {
let record = entity.model.schema.checkCreate({...payload});
const attributes = state.getCompositeAttributes();
return state
.setMethod(MethodTypes.upsert)
.setType(QueryTypes.eq)
.applyUpsert(record)
.setPK(entity._expectFacets(record, attributes.pk))
.ifSK(() => {
entity._expectFacets(record, attributes.sk);
state.setSK(entity._buildQueryFacets(record, attributes.sk));
});
} catch(err) {
state.setError(err);
return state;
}
},
children: ["params", "go", "where"],
},
put: {
name: "put",
/* istanbul ignore next */
Expand Down Expand Up @@ -678,6 +703,9 @@ class ChainState {
put: {
data: {},
},
upsert: {
data: {}
},
keys: {
provided: [],
pk: {},
Expand Down Expand Up @@ -874,6 +902,11 @@ class ChainState {
}
}

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

applyPut(data = {}) {
this.query.put.data = {...this.query.put.data, ...data};
return this;
Expand Down
39 changes: 35 additions & 4 deletions src/entity.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const { AllPages,
ResultOrderParam,
IndexTypes,
PartialComparisons,
MethodTypeTranslation,
} = require("./types");
const { FilterFactory } = require("./filters");
const { FilterOperations } = require("./operations");
Expand Down Expand Up @@ -256,6 +257,11 @@ class Entity {
}
}

upsert(attributes = {}) {
let index = TableIndex;
return this._makeChain(index, this._clausesWithFilters, clauses.index).upsert(attributes);
}

create(attributes = {}) {
let index = TableIndex;
let options = {};
Expand Down Expand Up @@ -314,7 +320,6 @@ class Entity {
}

async _exec(method, params, config = {}) {
const entity = this;
const notifyQuery = () => {
this.eventManager.trigger({
type: "query",
Expand All @@ -332,8 +337,8 @@ class Entity {
results,
}, config.listeners);
}

return this.client[method](params).promise()
const dynamoDBMethod = MethodTypeTranslation[method];
return this.client[dynamoDBMethod](params).promise()
.then((results) => {
notifyQuery();
notifyResults(results, true);
Expand Down Expand Up @@ -497,6 +502,7 @@ class Entity {
case MethodTypes.patch:
case MethodTypes.delete:
case MethodTypes.remove:
case MethodTypes.upsert:
return this.formatResponse(response, index, {...config, _objectOnEmpty: true});
default:
return this.formatResponse(response, index, config);
Expand Down Expand Up @@ -1110,7 +1116,7 @@ class Entity {
}
/* istanbul ignore next */
_params(state, config = {}) {
let { keys = {}, method = "", put = {}, update = {}, filter = {}, options = {} } = state.query;
let { keys = {}, method = "", put = {}, update = {}, filter = {}, options = {}, updateProxy, upsert } = state.query;
let consolidatedQueryFacets = this._consolidateQueryFacets(keys.sk);
let params = {};
switch (method) {
Expand All @@ -1119,6 +1125,9 @@ class Entity {
case MethodTypes.remove:
params = this._makeSimpleIndexParams(keys.pk, ...consolidatedQueryFacets);
break;
case MethodTypes.upsert:
params = this._makeUpsertParams({update, upsert}, keys.pk, ...keys.sk)
break;
case MethodTypes.put:
case MethodTypes.create:
params = this._makePutParams(put, keys.pk, ...keys.sk);
Expand Down Expand Up @@ -1477,6 +1486,28 @@ class Entity {
};
}

_makeUpsertParams({update, upsert} = {}, pk, sk) {
const { updatedKeys, setAttributes, indexKey } = this._getPutKeys(pk, sk && sk.facets, upsert.data);
const upsertAttributes = this.model.schema.translateToFields(setAttributes);
const keyNames = Object.keys(indexKey);
update.set(this.identifiers.entity, this.getName());
update.set(this.identifiers.version, this.getVersion());
for (const field of [...Object.keys(upsertAttributes), ...Object.keys(updatedKeys)]) {
const value = upsertAttributes[field] || updatedKeys[field];
if (!keyNames.includes(field)) {
update.set(field, value);
}
}

return {
TableName: this.getTableName(),
UpdateExpression: update.build(),
ExpressionAttributeNames: update.getNames(),
ExpressionAttributeValues: update.getValues(),
Key: indexKey,
};
}

_updateExpressionBuilder(data) {
let accessPattern = this.model.translations.indexes.fromIndexToAccessPattern[TableIndex]
let skip = [
Expand Down
20 changes: 18 additions & 2 deletions src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,28 @@ const MethodTypes = {
update: "update",
delete: "delete",
remove: "remove",
scan: "scan",
patch: "patch",
create: "create",
batchGet: "batchGet",
batchWrite: "batchWrite"
batchWrite: "batchWrite",
upsert: "upsert",
};

const MethodTypeTranslation = {
put: "put",
get: "get",
query: "query",
scan: "scan",
update: "update",
delete: "delete",
remove: "delete",
patch: "update",
create: "put",
batchGet: "batchGet",
batchWrite: "batchWrite",
upsert: "update",
}

const IndexTypes = {
isolated: 'isolated',
clustered: 'clustered',
Expand Down Expand Up @@ -286,6 +301,7 @@ module.exports = {
FormatToReturnValues,
AttributeProxySymbol,
ElectroInstanceTypes,
MethodTypeTranslation,
EventSubscriptionTypes,
AttributeMutationMethods,
AllPages,
Expand Down
29 changes: 27 additions & 2 deletions test/entity.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { Entity } from '../';
import {Entity, Resolve} from '../';
import { expectType } from 'tsd';

const troubleshoot = <Params extends any[], Response>(fn: (...params: Params) => Response, response: Response) => {};
const magnify = <T>(value: T): Resolve<T> => { return {} as Resolve<T> };

const entityWithSK = new Entity({
model: {
Expand Down Expand Up @@ -107,4 +111,25 @@ const entityWithSK = new Entity({
entityWithSK.update({
attr1: 'abc',
attr2: 'def'
}).append({})
}).append({});

type CreateOptions = Parameters<typeof entityWithSK.create>[0];
type UpsertOptions = Parameters<typeof entityWithSK.upsert>[0];

const createOptions = {} as CreateOptions;
const upsertOptions = {} as UpsertOptions;

expectType<UpsertOptions>(createOptions);
expectType<{
attr1?: string | undefined;
attr2: string;
attr3?: "123" | "def" | "ghi" | undefined;
attr4: 'abc' | 'ghi';
attr5?: string | undefined;
attr6?: number | undefined;
attr7?: any;
attr8: boolean;
attr9?: number | undefined;
attr10?: boolean | undefined;
attr11?: string[] | undefined;
}>(magnify(upsertOptions));

0 comments on commit 7e9b9f4

Please sign in to comment.