electrodb 2.4.2 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts CHANGED
@@ -1,6 +1,9 @@
1
-
1
+ import {ScanCommandInput, GetCommandInput, QueryCommandInput, DeleteCommandInput, BatchWriteCommandInput, UpdateCommandInput, UpdateCommand, PutCommandInput, BatchGetCommandInput, TransactWriteCommandInput, TransactGetCommandInput} from '@aws-sdk/lib-dynamodb';
2
2
  export type DocumentClientMethod = (parameters: any) => {promise: () => Promise<any>};
3
3
 
4
+ type TransactGetItem = Extract<Required<TransactGetCommandInput['TransactItems']>, Array<any>>[number];
5
+ type TransactWriteItem = Extract<Required<TransactWriteCommandInput['TransactItems']>, Array<any>>[number];
6
+
4
7
  export type DocumentClient = {
5
8
  get: DocumentClientMethod;
6
9
  put: DocumentClientMethod;
@@ -712,12 +715,89 @@ export interface RecordsActionOptions<A extends string,
712
715
  where: WhereClause<A,F,C,S,Item<A,F,C,S,S["attributes"]>,RecordsActionOptions<A,F,C,S,Items,IndexCompositeAttributes>>;
713
716
  }
714
717
 
718
+ export type TransactionItemCode = 'None' | 'ConditionalCheckFailed' | 'ItemCollectionSizeLimitExceeded' | 'TransactionConflict' | 'ProvisionedThroughputExceeded' | 'ThrottlingError' | 'ValidationError';
719
+
720
+ export type TransactionItem<T> = {
721
+ item: null | T;
722
+ rejected: boolean;
723
+ code: TransactionItemCode;
724
+ message?: string | undefined;
725
+ };
726
+
727
+ type CommittedTransactionResult<T, Params> =
728
+ & Params
729
+ & { [TransactionSymbol]: T };
730
+
715
731
  export interface SingleRecordOperationOptions<A extends string, F extends string, C extends string, S extends Schema<A,F,C>, ResponseType> {
716
732
  go: GoGetTerminal<A,F,C,S, ResponseType>;
717
733
  params: ParamTerminal<A,F,C,S,ResponseType>;
718
734
  where: WhereClause<A,F,C,S,Item<A,F,C,S,S["attributes"]>,SingleRecordOperationOptions<A,F,C,S,ResponseType>>;
719
735
  }
720
736
 
737
+ type GoGetTerminalTransaction<A extends string, F extends string, C extends string, S extends Schema<A,F,C>, ResponseItem, Params> = <Options extends TransactGetQueryOptions<keyof ResponseItem>>(options?: Options) =>
738
+ Options extends GoQueryTerminalOptions<infer Attr>
739
+ ? CommittedTransactionResult<{
740
+ [Name in keyof ResponseItem as Name extends Attr
741
+ ? Name
742
+ : never]: ResponseItem[Name]
743
+ }, Params>
744
+ : CommittedTransactionResult<ResponseItem, Params>
745
+
746
+ type GoSingleTerminalTransaction<A extends string, F extends string, C extends string, S extends Schema<A,F,C>, ResponseItem, Params> = <Options extends TransactWriteQueryOptions>(options?: Options) =>
747
+ CommittedTransactionResult<ResponseItem, Params>
748
+
749
+ export interface SingleRecordOperationOptionsTransaction<A extends string, F extends string, C extends string, S extends Schema<A,F,C>, ResponseType, Params> {
750
+ commit: GoSingleTerminalTransaction<A,F,C,S, ResponseType, Params>;
751
+ where: WhereClause<A,F,C,S,Item<A,F,C,S,S["attributes"]>,SingleRecordOperationOptionsTransaction<A,F,C,S,ResponseType, Params>>;
752
+ }
753
+
754
+ export interface GetOperationOptionsTransaction<A extends string, F extends string, C extends string, S extends Schema<A,F,C>, ResponseType, Params> {
755
+ commit: GoGetTerminalTransaction<A,F,C,S, ResponseType, Params>;
756
+ }
757
+
758
+ export type DeleteRecordOperationGoTransaction<ResponseType, Options = TransactWriteQueryOptions> = <T = ResponseType>(options?: Options) => CommittedTransactionResult<T, TransactWriteItem>;
759
+
760
+ export interface DeleteRecordOperationOptionsTransaction<A extends string, F extends string, C extends string, S extends Schema<A,F,C>, ResponseType> {
761
+ commit: DeleteRecordOperationGoTransaction<ResponseType, TransactWriteQueryOptions>;
762
+ where: WhereClause<A,F,C,S,Item<A,F,C,S,S["attributes"]>,DeleteRecordOperationOptionsTransaction<A,F,C,S,ResponseType>>;
763
+ }
764
+
765
+ export type PutRecordGoTransaction<ResponseType, Options = TransactWriteQueryOptions> = <T = ResponseType>(options?: Options) => CommittedTransactionResult<T, TransactWriteItem>;
766
+
767
+ export interface PutRecordOperationOptionsTransaction<A extends string, F extends string, C extends string, S extends Schema<A,F,C>, ResponseType> {
768
+ commit: PutRecordGoTransaction<ResponseType, TransactWriteQueryOptions>;
769
+ where: WhereClause<A, F, C, S, Item<A, F, C, S, S["attributes"]>, PutRecordOperationOptionsTransaction<A, F, C, S, ResponseType>>;
770
+ }
771
+
772
+ export interface UpsertRecordOperationOptionsTransaction<A extends string, F extends string, C extends string, S extends Schema<A,F,C>, ResponseType> {
773
+ commit: PutRecordGoTransaction<ResponseType, TransactWriteQueryOptions>;
774
+ where: WhereClause<A, F, C, S, Item<A, F, C, S, S["attributes"]>, UpsertRecordOperationOptionsTransaction<A, F, C, S, ResponseType>>;
775
+ }
776
+
777
+ export type UpdateRecordGoTransaction<ResponseType> = <T = ResponseType, Options extends TransactWriteQueryOptions = TransactWriteQueryOptions>(options?: Options) => CommittedTransactionResult<Partial<T>, TransactWriteItem>
778
+
779
+ export interface SetRecordActionOptionsTransaction<A extends string, F extends string, C extends string, S extends Schema<A,F,C>, SetAttr,IndexCompositeAttributes,TableItem> {
780
+ commit: UpdateRecordGoTransaction<TableItem>;
781
+ params: ParamRecord<UpdateQueryParams>;
782
+ set: SetRecordTransaction<A,F,C,S, SetItem<A,F,C,S>,IndexCompositeAttributes,TableItem>;
783
+ remove: RemoveRecordTransaction<A,F,C,S, Array<keyof SetItem<A,F,C,S>>,IndexCompositeAttributes,TableItem>;
784
+ add: SetRecordTransaction<A,F,C,S, AddItem<A,F,C,S>,IndexCompositeAttributes,TableItem>;
785
+ subtract: SetRecordTransaction<A,F,C,S, SubtractItem<A,F,C,S>,IndexCompositeAttributes,TableItem>;
786
+ append: SetRecordTransaction<A,F,C,S, AppendItem<A,F,C,S>,IndexCompositeAttributes,TableItem>;
787
+ delete: SetRecordTransaction<A,F,C,S, DeleteItem<A,F,C,S>,IndexCompositeAttributes,TableItem>;
788
+ data: DataUpdateMethodRecordTransaction<A,F,C,S, Item<A,F,C,S,S["attributes"]>,IndexCompositeAttributes,TableItem>;
789
+ where: WhereClause<A,F,C,S, Item<A,F,C,S,S["attributes"]>,SetRecordActionOptionsTransaction<A,F,C,S,SetAttr,IndexCompositeAttributes,TableItem>>;
790
+ }
791
+
792
+ export type RemoveRecordTransaction<A extends string, F extends string, C extends string, S extends Schema<A,F,C>, RemoveAttr, IndexCompositeAttributes, TableItem> = (properties: RemoveAttr) =>
793
+ SetRecordActionOptionsTransaction<A,F,C,S, RemoveAttr, IndexCompositeAttributes, TableItem>;
794
+
795
+ export type SetRecordTransaction<A extends string, F extends string, C extends string, S extends Schema<A,F,C>, SetAttr, IndexCompositeAttributes, TableItem> = (properties: SetAttr) =>
796
+ SetRecordActionOptionsTransaction<A,F,C,S, SetAttr, IndexCompositeAttributes, TableItem>;
797
+
798
+ export type DataUpdateMethodRecordTransaction<A extends string, F extends string, C extends string, S extends Schema<A,F,C>, SetAttr, IndexCompositeAttributes, TableItem> =
799
+ DataUpdateMethod<A,F,C,S, UpdateData<A,F,C,S>, SetRecordActionOptionsTransaction<A,F,C,S, SetAttr, IndexCompositeAttributes, TableItem>>
800
+
721
801
  export interface BatchGetRecordOperationOptions<A extends string, F extends string, C extends string, S extends Schema<A,F,C>, ResponseType> {
722
802
  go: GoBatchGetTerminal<A,F,C,S,ResponseType>
723
803
  params: ParamTerminal<A,F,C,S,ResponseType>;
@@ -915,6 +995,28 @@ interface GoQueryTerminalOptions<Attributes> {
915
995
  order?: 'asc' | 'desc';
916
996
  }
917
997
 
998
+ interface TransactWriteQueryOptions {
999
+ data?: 'raw' | 'includeKeys' | 'attributes';
1000
+ table?: string;
1001
+ params?: object;
1002
+ originalErr?: boolean;
1003
+ ignoreOwnership?: boolean;
1004
+ listeners?: Array<ElectroEventListener>;
1005
+ logger?: ElectroEventListener;
1006
+ response?: 'all_old';
1007
+ }
1008
+
1009
+ interface TransactGetQueryOptions<Attributes> {
1010
+ data?: 'raw' | 'includeKeys' | 'attributes';
1011
+ table?: string;
1012
+ params?: object;
1013
+ originalErr?: boolean;
1014
+ ignoreOwnership?: boolean;
1015
+ attributes?: ReadonlyArray<Attributes>;
1016
+ listeners?: Array<ElectroEventListener>;
1017
+ logger?: ElectroEventListener;
1018
+ }
1019
+
918
1020
  export interface ParamTerminalOptions<Attributes> {
919
1021
  table?: string;
920
1022
  limit?: number;
@@ -2151,6 +2253,7 @@ export declare const WhereSymbol: unique symbol;
2151
2253
  export declare const UpdateDataSymbol: unique symbol;
2152
2254
  export declare const CustomAttributeSymbol: unique symbol;
2153
2255
  export declare const OpaquePrimitiveSymbol: unique symbol;
2256
+ export declare const TransactionSymbol: unique symbol;
2154
2257
 
2155
2258
  export type WhereAttributeSymbol<T extends any> =
2156
2259
  { [WhereSymbol]: void }
@@ -2335,6 +2438,80 @@ export class Entity<A extends string, F extends string, C extends string, S exte
2335
2438
  client: any;
2336
2439
  }
2337
2440
 
2441
+
2442
+ export class TransactWriteEntity<A extends string, F extends string, C extends string, S extends Schema<A,F,C>> {
2443
+ readonly schema: S;
2444
+ constructor(schema: S);
2445
+
2446
+ check(key: AllTableIndexCompositeAttributes<A,F,C,S>): SingleRecordOperationOptionsTransaction<A,F,C,S, ResponseItem<A,F,C,S>, TransactWriteItem>;
2447
+ delete(key: AllTableIndexCompositeAttributes<A,F,C,S>): DeleteRecordOperationOptionsTransaction<A,F,C,S, ResponseItem<A,F,C,S>>;
2448
+ remove(key: AllTableIndexCompositeAttributes<A,F,C,S>): DeleteRecordOperationOptionsTransaction<A,F,C,S, ResponseItem<A,F,C,S>>
2449
+ put(record: PutItem<A,F,C,S>): PutRecordOperationOptionsTransaction<A,F,C,S, ResponseItem<A,F,C,S>>;
2450
+ create(record: PutItem<A,F,C,S>): PutRecordOperationOptionsTransaction<A,F,C,S, ResponseItem<A,F,C,S>>
2451
+ upsert(record: PutItem<A,F,C,S>): UpsertRecordOperationOptionsTransaction<A,F,C,S, ResponseItem<A,F,C,S>>;
2452
+ update(key: AllTableIndexCompositeAttributes<A,F,C,S>): {
2453
+ set: SetRecordTransaction<A,F,C,S, SetItem<A,F,C,S>, TableIndexCompositeAttributes<A,F,C,S>, ResponseItem<A,F,C,S>>;
2454
+ remove: RemoveRecordTransaction<A,F,C,S, RemoveItem<A,F,C,S>, TableIndexCompositeAttributes<A,F,C,S>, ResponseItem<A,F,C,S>>;
2455
+ add: SetRecordTransaction<A,F,C,S, AddItem<A,F,C,S>, TableIndexCompositeAttributes<A,F,C,S>, ResponseItem<A,F,C,S>>;
2456
+ subtract: SetRecordTransaction<A,F,C,S, SubtractItem<A,F,C,S>, TableIndexCompositeAttributes<A,F,C,S>, ResponseItem<A,F,C,S>>;
2457
+ append: SetRecordTransaction<A,F,C,S, AppendItem<A,F,C,S>, TableIndexCompositeAttributes<A,F,C,S>, ResponseItem<A,F,C,S>>;
2458
+ delete: SetRecordTransaction<A,F,C,S, DeleteItem<A,F,C,S>, TableIndexCompositeAttributes<A,F,C,S>, ResponseItem<A,F,C,S>>;
2459
+ data: DataUpdateMethodRecordTransaction<A,F,C,S, Item<A,F,C,S,S["attributes"]>, TableIndexCompositeAttributes<A,F,C,S>, ResponseItem<A,F,C,S>>;
2460
+ };
2461
+ patch(key: AllTableIndexCompositeAttributes<A,F,C,S>): {
2462
+ set: SetRecordTransaction<A,F,C,S, SetItem<A,F,C,S>, TableIndexCompositeAttributes<A,F,C,S>, ResponseItem<A,F,C,S>>;
2463
+ remove: RemoveRecordTransaction<A,F,C,S, RemoveItem<A,F,C,S>, TableIndexCompositeAttributes<A,F,C,S>, ResponseItem<A,F,C,S>>;
2464
+ add: SetRecordTransaction<A,F,C,S, AddItem<A,F,C,S>, TableIndexCompositeAttributes<A,F,C,S>, ResponseItem<A,F,C,S>>;
2465
+ subtract: SetRecordTransaction<A,F,C,S, SubtractItem<A,F,C,S>, TableIndexCompositeAttributes<A,F,C,S>, ResponseItem<A,F,C,S>>;
2466
+ append: SetRecordTransaction<A,F,C,S, AppendItem<A,F,C,S>, TableIndexCompositeAttributes<A,F,C,S>, ResponseItem<A,F,C,S>>;
2467
+ delete: SetRecordTransaction<A,F,C,S, DeleteItem<A,F,C,S>, TableIndexCompositeAttributes<A,F,C,S>, ResponseItem<A,F,C,S>>;
2468
+ data: DataUpdateMethodRecordTransaction<A,F,C,S, Item<A,F,C,S,S["attributes"]>, TableIndexCompositeAttributes<A,F,C,S>, ResponseItem<A,F,C,S>>;
2469
+ };
2470
+ }
2471
+
2472
+ export class TransactGetEntity<A extends string, F extends string, C extends string, S extends Schema<A,F,C>> {
2473
+ readonly schema: S;
2474
+ constructor(schema: S);
2475
+
2476
+ get(key: AllTableIndexCompositeAttributes<A,F,C,S>): GetOperationOptionsTransaction<A,F,C,S, ResponseItem<A,F,C,S>, TransactGetItem>;
2477
+ }
2478
+
2479
+ type TransactWriteFunctionOptions = {
2480
+ token?: string;
2481
+ };
2482
+
2483
+ type TransactGetFunctionOptions = {};
2484
+
2485
+ type TransactWriteExtractedType<T extends readonly any[], A extends readonly any[] = []> =
2486
+ T extends [infer F, ...infer R] ?
2487
+ F extends CommittedTransactionResult<infer V, TransactWriteItem>
2488
+ ? TransactWriteExtractedType<R, [...A, TransactionItem<V>]>
2489
+ : never
2490
+ : A;
2491
+
2492
+ type TransactGetExtractedType<T extends readonly any[], A extends readonly any[] = []> =
2493
+ T extends [infer F, ...infer R] ?
2494
+ F extends CommittedTransactionResult<infer V, TransactGetItem>
2495
+ ? TransactWriteExtractedType<R, [...A, TransactionItem<V>]>
2496
+ : never
2497
+ : A
2498
+
2499
+ type TransactWriteEntities<E extends {[name: string]: Entity<any, any, any, any>}> = {
2500
+ [EntityName in keyof E]: E[EntityName] extends Entity<infer A, infer F, infer C, infer S>
2501
+ ? TransactWriteEntity<A,F,C,S>
2502
+ : never;
2503
+ }
2504
+
2505
+ type TransactGetEntities<E extends {[name: string]: Entity<any, any, any, any>}> = {
2506
+ [EntityName in keyof E]: E[EntityName] extends Entity<infer A, infer F, infer C, infer S>
2507
+ ? TransactGetEntity<A,F,C,S>
2508
+ : never;
2509
+ }
2510
+
2511
+ type TransactWriteFunction<E extends {[name: string]: Entity<any, any, any, any>}, T, R extends ReadonlyArray<CommittedTransactionResult<T, TransactWriteItem>> > = (entities: TransactWriteEntities<E>) => [...R];
2512
+
2513
+ type TransactGetFunction<E extends {[name: string]: Entity<any, any, any, any>}, T, R extends ReadonlyArray<CommittedTransactionResult<T, TransactGetItem>> > = (entities: TransactGetEntities<E>) => [...R];
2514
+
2338
2515
  export type ServiceConfiguration = {
2339
2516
  table?: string;
2340
2517
  client?: DocumentClient;
@@ -2342,11 +2519,46 @@ export type ServiceConfiguration = {
2342
2519
  logger?: ElectroEventListener;
2343
2520
  };
2344
2521
 
2522
+ declare function createWriteTransaction<E extends {[name: string]: Entity<any, any, any, any>}, T, R extends ReadonlyArray<CommittedTransactionResult<T, TransactWriteItem>>>(entities: E, fn: TransactWriteFunction<E,T,R>): {
2523
+ go: (options?: TransactWriteFunctionOptions) => Promise<{
2524
+ canceled: boolean;
2525
+ data: TransactWriteExtractedType<R>
2526
+ }>;
2527
+ params: <O extends TransactWriteFunctionOptions = TransactWriteFunctionOptions>(options?: O) => TransactWriteCommandInput
2528
+ }
2529
+
2530
+ declare function createGetTransaction<E extends {[name: string]: Entity<any, any, any, any>},T, R extends ReadonlyArray<CommittedTransactionResult<T, TransactGetItem>>>(entities: E, fn: TransactGetFunction<E,T,R>): {
2531
+ go: (options?: TransactGetFunctionOptions) => Promise<{
2532
+ canceled: boolean;
2533
+ data: TransactGetExtractedType<R>
2534
+ }>;
2535
+ params: <O extends TransactGetFunctionOptions = TransactGetFunctionOptions>(options?: O) => TransactGetCommandInput
2536
+ };
2537
+
2345
2538
  export class Service<E extends {[name: string]: Entity<any, any, any, any>}> {
2346
2539
  entities: E;
2347
2540
  collections:
2348
2541
  ClusteredCollectionQueries<E, ClusteredCollectionAssociations<E>>
2349
2542
  & IsolatedCollectionQueries<E, IsolatedCollectionAssociations<E>>
2543
+
2544
+ transaction: {
2545
+ write: <T, R extends ReadonlyArray<CommittedTransactionResult<T, TransactWriteItem>>>(fn: TransactWriteFunction<E,T,R>) => {
2546
+ go: (options?: TransactWriteFunctionOptions) => Promise<{
2547
+ canceled: boolean;
2548
+ data: TransactWriteExtractedType<R>
2549
+ }>;
2550
+ params: <O extends TransactWriteFunctionOptions = TransactWriteFunctionOptions>(options?: O) => TransactWriteCommandInput
2551
+ };
2552
+
2553
+ get: <T, R extends ReadonlyArray<CommittedTransactionResult<T, TransactGetItem>>>(fn: TransactGetFunction<E,T,R>) => {
2554
+ go: (options?: TransactGetFunctionOptions) => Promise<{
2555
+ canceled: boolean;
2556
+ data: TransactGetExtractedType<R>
2557
+ }>;
2558
+ params: <O extends TransactGetFunctionOptions = TransactGetFunctionOptions>(options?: O) => TransactGetCommandInput
2559
+ };
2560
+ }
2561
+
2350
2562
  constructor(entities: E, config?: ServiceConfiguration);
2351
2563
 
2352
2564
  setTableName(tableName: string): void;
package/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  const { Entity } = require("./src/entity");
2
2
  const { Service } = require("./src/service");
3
+ const { createGetTransaction, createWriteTransaction } = require('./src/transaction');
3
4
  const { createCustomAttribute, CustomAttributeType, createSchema } = require('./src/schema');
4
5
  const { ElectroError, ElectroValidationError, ElectroUserValidationError, ElectroAttributeValidationError } = require('./src/errors');
5
6
 
@@ -11,4 +12,6 @@ module.exports = {
11
12
  CustomAttributeType,
12
13
  createCustomAttribute,
13
14
  ElectroValidationError,
15
+ createGetTransaction,
16
+ createWriteTransaction,
14
17
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "electrodb",
3
- "version": "2.4.2",
3
+ "version": "2.5.0",
4
4
  "description": "A library to more easily create and interact with multiple entities and heretical relationships in dynamodb",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/src/clauses.js CHANGED
@@ -1,4 +1,4 @@
1
- const { QueryTypes, MethodTypes, ItemOperations, ExpressionTypes, TableIndex, TerminalOperation, KeyTypes, IndexTypes } = require("./types");
1
+ const { QueryTypes, MethodTypes, ItemOperations, ExpressionTypes, TransactionCommitSymbol, TransactionOperations, TerminalOperation, KeyTypes, IndexTypes } = require("./types");
2
2
  const {AttributeOperationProxy, UpdateOperations, FilterOperationNames} = require("./operations");
3
3
  const {UpdateExpression} = require("./update");
4
4
  const {FilterExpression} = require("./where");
@@ -28,7 +28,7 @@ function batchAction(action, type, entity, state, payload) {
28
28
  let clauses = {
29
29
  index: {
30
30
  name: "index",
31
- children: ["get", "delete", "update", "query", "upsert", "put", "scan", "collection", "clusteredCollection", "create", "remove", "patch", "batchPut", "batchDelete", "batchGet"],
31
+ children: ["check", "get", "delete", "update", "query", "upsert", "put", "scan", "collection", "clusteredCollection", "create", "remove", "patch", "batchPut", "batchDelete", "batchGet"],
32
32
  },
33
33
  clusteredCollection: {
34
34
  name: "clusteredCollection",
@@ -156,7 +156,15 @@ let clauses = {
156
156
  return state;
157
157
  }
158
158
  },
159
- children: ["params", "go"],
159
+ children: ["params", "go", "commit"],
160
+ },
161
+ check: {
162
+ name: 'check',
163
+ action(...params) {
164
+ return clauses.get.action(...params)
165
+ .setMethod(MethodTypes.check);
166
+ },
167
+ children: ["commit"],
160
168
  },
161
169
  batchGet: {
162
170
  name: "batchGet",
@@ -190,7 +198,7 @@ let clauses = {
190
198
  return state;
191
199
  }
192
200
  },
193
- children: ["where", "params", "go"],
201
+ children: ["where", "params", "go", "commit"],
194
202
  },
195
203
  remove: {
196
204
  name: "remove",
@@ -220,7 +228,7 @@ let clauses = {
220
228
  return state;
221
229
  }
222
230
  },
223
- children: ["where", "params", "go"],
231
+ children: ["where", "params", "go", "commit"],
224
232
  },
225
233
  upsert: {
226
234
  name: 'upsert',
@@ -245,7 +253,7 @@ let clauses = {
245
253
  return state;
246
254
  }
247
255
  },
248
- children: ["params", "go", "where"],
256
+ children: ["params", "go", "where", "commit"],
249
257
  },
250
258
  put: {
251
259
  name: "put",
@@ -271,7 +279,7 @@ let clauses = {
271
279
  return state;
272
280
  }
273
281
  },
274
- children: ["params", "go"],
282
+ children: ["params", "go", "commit"],
275
283
  },
276
284
  batchPut: {
277
285
  name: "batchPut",
@@ -307,7 +315,7 @@ let clauses = {
307
315
  return state;
308
316
  }
309
317
  },
310
- children: ["params", "go"],
318
+ children: ["params", "go", "commit"],
311
319
  },
312
320
  patch: {
313
321
  name: "patch",
@@ -336,7 +344,7 @@ let clauses = {
336
344
  return state;
337
345
  }
338
346
  },
339
- children: ["set", "append","updateRemove", "updateDelete", "add", "subtract", "data"],
347
+ children: ["set", "append","updateRemove", "updateDelete", "add", "subtract", "data", "commit"],
340
348
  },
341
349
  update: {
342
350
  name: "update",
@@ -359,7 +367,7 @@ let clauses = {
359
367
  return state;
360
368
  }
361
369
  },
362
- children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract"],
370
+ children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract", "commit"],
363
371
  },
364
372
  data: {
365
373
  name: "data",
@@ -389,7 +397,7 @@ let clauses = {
389
397
  return state;
390
398
  }
391
399
  },
392
- children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract"],
400
+ children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract", "commit"],
393
401
  },
394
402
  set: {
395
403
  name: "set",
@@ -406,7 +414,7 @@ let clauses = {
406
414
  return state;
407
415
  }
408
416
  },
409
- children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract"],
417
+ children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract", "commit"],
410
418
  },
411
419
  append: {
412
420
  name: "append",
@@ -423,7 +431,7 @@ let clauses = {
423
431
  return state;
424
432
  }
425
433
  },
426
- children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract"],
434
+ children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract", "commit"],
427
435
  },
428
436
  updateRemove: {
429
437
  name: "remove",
@@ -443,7 +451,7 @@ let clauses = {
443
451
  return state;
444
452
  }
445
453
  },
446
- children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract"],
454
+ children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract", "commit"],
447
455
  },
448
456
  updateDelete: {
449
457
  name: "delete",
@@ -460,7 +468,7 @@ let clauses = {
460
468
  return state;
461
469
  }
462
470
  },
463
- children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract"],
471
+ children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract", "commit"],
464
472
  },
465
473
  add: {
466
474
  name: "add",
@@ -477,7 +485,7 @@ let clauses = {
477
485
  return state;
478
486
  }
479
487
  },
480
- children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract"],
488
+ children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract", "commit"],
481
489
  },
482
490
  subtract: {
483
491
  name: "subtract",
@@ -494,7 +502,7 @@ let clauses = {
494
502
  return state;
495
503
  }
496
504
  },
497
- children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract"],
505
+ children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract", "commit"],
498
506
  },
499
507
  query: {
500
508
  name: "query",
@@ -662,6 +670,35 @@ let clauses = {
662
670
  },
663
671
  children: ["go", "params"],
664
672
  },
673
+ commit: {
674
+ name: 'commit',
675
+ action(entity, state, options) {
676
+ if (state.getError() !== null) {
677
+ throw state.error;
678
+ }
679
+
680
+ const results = clauses.params.action(entity, state, {
681
+ ...options,
682
+ _returnOptions: true,
683
+ _isTransaction: true,
684
+ });
685
+
686
+ const method = TransactionOperations[state.query.method];
687
+ if (!method) {
688
+ throw new Error('Invalid commit method');
689
+ }
690
+
691
+ return {
692
+ [method]: results.params,
693
+ [TransactionCommitSymbol]: () => {
694
+ return {
695
+ entity,
696
+ }
697
+ },
698
+ }
699
+ },
700
+ children: [],
701
+ },
665
702
  params: {
666
703
  name: "params",
667
704
  action(entity, state, options = {}) {
@@ -673,7 +710,10 @@ let clauses = {
673
710
  throw new e.ElectroError(e.ErrorCodes.MissingTable, `Table name not defined. Table names must be either defined on the model, instance configuration, or as a query option.`);
674
711
  }
675
712
  const method = state.getMethod();
676
- const normalizedOptions = entity._normalizeExecutionOptions({ provided: [ state.getOptions(), state.query.options, options ] });
713
+ const normalizedOptions = entity._normalizeExecutionOptions({
714
+ provided: [ state.getOptions(), state.query.options, options ],
715
+ context: { operation: options._isTransaction ? MethodTypes.transactWrite : undefined }
716
+ });
677
717
  state.applyWithOptions(normalizedOptions);
678
718
  let results;
679
719
  switch (method) {
package/src/client.js CHANGED
@@ -1,12 +1,13 @@
1
1
  const lib = require('@aws-sdk/lib-dynamodb')
2
+ const util = require('@aws-sdk/lib-dynamodb/dist-cjs/commands/utils')
2
3
  const { isFunction } = require('./validations');
3
4
  const { ElectroError, ErrorCodes } = require('./errors');
4
-
5
5
  const DocumentClientVersions = {
6
6
  v2: 'v2',
7
7
  v3: 'v3',
8
8
  electro: 'electro',
9
9
  };
10
+ const unmarshallOutput = util.unmarshallOutput || ((val) => val);
10
11
 
11
12
  const v3Methods = ['send'];
12
13
  const v2Methods = ['get', 'put', 'update', 'delete', 'batchWrite', 'batchGet', 'scan', 'query', 'createSet', 'transactWrite', 'transactGet'];
@@ -15,6 +16,99 @@ const supportedClientVersions = {
15
16
  [DocumentClientVersions.v3]: v3Methods,
16
17
  }
17
18
 
19
+ class DocumentClientV2Wrapper {
20
+ static init(client) {
21
+ return new DocumentClientV2Wrapper(client, lib);
22
+ }
23
+
24
+ constructor(client, lib) {
25
+ this.client = client;
26
+ this.lib = lib;
27
+ this.__v = 'v2';
28
+ }
29
+
30
+ get(params) {
31
+ return this.client.get(params);
32
+ }
33
+
34
+ put(params) {
35
+ return this.client.put(params);
36
+ }
37
+
38
+ update(params) {
39
+ return this.client.update(params);
40
+ }
41
+
42
+ delete(params) {
43
+ return this.client.delete(params);
44
+ }
45
+
46
+ batchWrite(params) {
47
+ return this.client.batchWrite(params);
48
+ }
49
+
50
+ batchGet(params) {
51
+ return this.client.batchGet(params);
52
+ }
53
+
54
+ scan(params) {
55
+ return this.client.scan(params);
56
+ }
57
+
58
+ query(params) {
59
+ return this.client.query(params);
60
+ }
61
+
62
+ _transact(transactionRequest) {
63
+ let cancellationReasons;
64
+ transactionRequest.on('extractError', (response) => {
65
+ try {
66
+ cancellationReasons = JSON.parse(response.httpResponse.body.toString()).CancellationReasons;
67
+ } catch (err) {}
68
+ });
69
+
70
+ return {
71
+ async promise() {
72
+ return transactionRequest.promise()
73
+ .catch((err) => {
74
+ if (err) {
75
+ if (Array.isArray(cancellationReasons)) {
76
+ return {
77
+ canceled: cancellationReasons
78
+ .map(reason => {
79
+ if (reason.Item) {
80
+ return unmarshallOutput(reason, [{ key: "Item" }]);
81
+ }
82
+ return reason;
83
+ })
84
+ };
85
+ }
86
+ throw err;
87
+ }
88
+ });
89
+ }
90
+ }
91
+ }
92
+
93
+ transactWrite(params) {
94
+ const transactionRequest = this.client.transactWrite(params);
95
+ return this._transact(transactionRequest);
96
+ }
97
+
98
+ transactGet(params) {
99
+ const transactionRequest = this.client.transactGet(params);
100
+ return this._transact(transactionRequest);
101
+ }
102
+
103
+ createSet(value, ...rest) {
104
+ if (Array.isArray(value)) {
105
+ return this.client.createSet(value, ...rest);
106
+ } else {
107
+ return this.client.createSet([value], ...rest);
108
+ }
109
+ }
110
+ }
111
+
18
112
  class DocumentClientV3Wrapper {
19
113
  static init(client) {
20
114
  return new DocumentClientV3Wrapper(client, lib);
@@ -23,6 +117,7 @@ class DocumentClientV3Wrapper {
23
117
  constructor(client, lib) {
24
118
  this.client = client;
25
119
  this.lib = lib;
120
+ this.__v = 'v3';
26
121
  }
27
122
 
28
123
  promiseWrap(fn) {
@@ -81,16 +176,49 @@ class DocumentClientV3Wrapper {
81
176
  return this.client.send(command);
82
177
  });
83
178
  }
179
+
84
180
  transactWrite(params) {
85
181
  return this.promiseWrap(async () => {
86
182
  const command = new this.lib.TransactWriteCommand(params);
87
- return this.client.send(command);
183
+ return this.client.send(command)
184
+ .then((result) => {
185
+ return result;
186
+ })
187
+ .catch(err => {
188
+ if (err.CancellationReasons) {
189
+ return {
190
+ canceled: err.CancellationReasons.map(reason => {
191
+ if (reason.Item) {
192
+ return unmarshallOutput(reason, [{ key: "Item" }]);
193
+ }
194
+ return reason;
195
+ })
196
+ }
197
+ }
198
+ throw err;
199
+ });
88
200
  });
89
201
  }
90
202
  transactGet(params) {
91
203
  return this.promiseWrap(async () => {
92
204
  const command = new this.lib.TransactGetCommand(params);
93
- return this.client.send(command);
205
+ return this.client.send(command)
206
+ .then((result) => {
207
+ return result;
208
+ })
209
+ .catch(err => {
210
+ if (err.CancellationReasons) {
211
+ return {
212
+ canceled: err.CancellationReasons.map(reason => {
213
+ if (reason.Item) {
214
+ return unmarshallOutput(reason, [{ key: "Item" }]);
215
+ }
216
+ return reason;
217
+ })
218
+ }
219
+ }
220
+ throw err;
221
+ });
94
222
  });
95
223
  }
96
224
  createSet(value) {
@@ -103,7 +231,9 @@ class DocumentClientV3Wrapper {
103
231
  }
104
232
 
105
233
  function identifyClientVersion(client = {}) {
106
- if (client instanceof DocumentClientV3Wrapper) return DocumentClientVersions.electro;
234
+ if (client instanceof DocumentClientV3Wrapper || client instanceof DocumentClientV2Wrapper) {
235
+ return DocumentClientVersions.electro;
236
+ }
107
237
  for (const [version, methods] of Object.entries(supportedClientVersions)) {
108
238
  const hasMethods = methods.every(method => {
109
239
  return method in client && isFunction(client[method]);
@@ -121,6 +251,7 @@ function normalizeClient(client) {
121
251
  case DocumentClientVersions.v3:
122
252
  return DocumentClientV3Wrapper.init(client);
123
253
  case DocumentClientVersions.v2:
254
+ return DocumentClientV2Wrapper.init(client);
124
255
  case DocumentClientVersions.electro:
125
256
  return client;
126
257
  default:
@@ -136,6 +267,7 @@ function normalizeConfig(config = {}) {
136
267
  }
137
268
 
138
269
  module.exports = {
270
+ util,
139
271
  v2Methods,
140
272
  v3Methods,
141
273
  normalizeClient,
@@ -144,4 +276,5 @@ module.exports = {
144
276
  DocumentClientVersions,
145
277
  supportedClientVersions,
146
278
  DocumentClientV3Wrapper,
279
+ DocumentClientV2Wrapper,
147
280
  };
package/src/entity.js CHANGED
@@ -24,6 +24,7 @@ const { AllPages,
24
24
  IndexTypes,
25
25
  PartialComparisons,
26
26
  MethodTypeTranslation,
27
+ TransactionCommitSymbol,
27
28
  } = require("./types");
28
29
  const { FilterFactory } = require("./filters");
29
30
  const { FilterOperations } = require("./operations");
@@ -35,6 +36,7 @@ const c = require('./client');
35
36
  const u = require("./util");
36
37
  const e = require("./errors");
37
38
  const { validate } = require("jsonschema");
39
+ const v = require('./validations');
38
40
 
39
41
  class Entity {
40
42
  constructor(model, config = {}) {
@@ -229,6 +231,10 @@ class Entity {
229
231
  return validations.model(model);
230
232
  }
231
233
 
234
+ check(compositeAttributes = {}) {
235
+ return this._makeChain(TableIndex, this._clausesWithFilters, clauses.index).check(compositeAttributes);
236
+ }
237
+
232
238
  get(facets = {}) {
233
239
  let index = TableIndex;
234
240
  if (Array.isArray(facets)) {
@@ -285,6 +291,16 @@ class Entity {
285
291
  return this._makeChain(index, this._clausesWithFilters, clauses.index, options).remove(facets);
286
292
  }
287
293
 
294
+ async transactWrite(parameters, config) {
295
+ let response = await this._exec(MethodTypes.transactWrite, parameters, config);
296
+ return response;
297
+ }
298
+
299
+ async transactGet(parameters, config) {
300
+ let response = await this._exec(MethodTypes.transactGet, parameters, config);
301
+ return response;
302
+ }
303
+
288
304
  async go(method, parameters = {}, config = {}) {
289
305
  let stackTrace;
290
306
  if (!config.originalErr) {
@@ -486,7 +502,7 @@ class Entity {
486
502
  case FormatToReturnValues.all_old:
487
503
  case FormatToReturnValues.updated_new:
488
504
  case FormatToReturnValues.updated_old:
489
- return this.formatResponse(response, config);
505
+ return this.formatResponse(response, TableIndex, config);
490
506
  case FormatToReturnValues.default:
491
507
  default:
492
508
  return this._formatDefaultResponse(method, parameters.IndexName, parameters, config, response);
@@ -883,7 +899,7 @@ class Entity {
883
899
  return pager
884
900
  }
885
901
 
886
- _normalizeExecutionOptions({ provided = [] } = {}) {
902
+ _normalizeExecutionOptions({ provided = [], context = {} } = {}) {
887
903
  let config = {
888
904
  includeKeys: false,
889
905
  originalErr: false,
@@ -931,7 +947,11 @@ class Entity {
931
947
  throw new e.ElectroError(e.ErrorCodes.InvalidOptions, `Invalid value for query option "format" provided: "${option.format}". Allowed values include ${u.commaSeparatedString(Object.keys(ReturnValues))}.`);
932
948
  }
933
949
  config.response = format;
934
- config.params.ReturnValues = FormatToReturnValues[format];
950
+ if (context.operation === MethodTypes.transactWrite) {
951
+ config.params.ReturnValuesOnConditionCheckFailure = FormatToReturnValues[format];
952
+ } else {
953
+ config.params.ReturnValues = FormatToReturnValues[format];
954
+ }
935
955
  }
936
956
 
937
957
  if (option.formatCursor) {
@@ -1125,6 +1145,7 @@ class Entity {
1125
1145
  let consolidatedQueryFacets = this._consolidateQueryFacets(keys.sk);
1126
1146
  let params = {};
1127
1147
  switch (method) {
1148
+ case MethodTypes.check:
1128
1149
  case MethodTypes.get:
1129
1150
  case MethodTypes.delete:
1130
1151
  case MethodTypes.remove:
@@ -1392,7 +1413,7 @@ class Entity {
1392
1413
  });
1393
1414
  let Key = this._makeParameterKey(index, keys.pk, ...keys.sk);
1394
1415
  let TableName = this.getTableName();
1395
- return {Key, TableName};
1416
+ return { Key, TableName };
1396
1417
  }
1397
1418
 
1398
1419
  _removeAttributes(item, keys) {
@@ -3134,7 +3155,49 @@ class Entity {
3134
3155
  }
3135
3156
  }
3136
3157
 
3158
+ function getEntityIdentifiers(entities) {
3159
+ let identifiers = [];
3160
+ for (let alias of Object.keys(entities)) {
3161
+ let entity = entities[alias];
3162
+ let name = entity.model.entity;
3163
+ let version = entity.model.version;
3164
+ identifiers.push({
3165
+ name,
3166
+ alias,
3167
+ version,
3168
+ entity,
3169
+ nameField: entity.identifiers.entity,
3170
+ versionField: entity.identifiers.version
3171
+ });
3172
+ }
3173
+ return identifiers;
3174
+ }
3175
+
3176
+ function matchToEntityAlias({ paramItem, identifiers, record } = {}) {
3177
+ let entity;
3178
+ let entityAlias;
3179
+
3180
+ if (paramItem && v.isFunction(paramItem[TransactionCommitSymbol])) {
3181
+ const committed = paramItem[TransactionCommitSymbol]();
3182
+ entity = committed.entity;
3183
+ }
3184
+
3185
+ for (let {name, version, nameField, versionField, alias} of identifiers) {
3186
+ if (entity && entity.model.entity === name && entity.model.version === version) {
3187
+ entityAlias = alias;
3188
+ break;
3189
+ } else if (record[nameField] !== undefined && record[nameField] === name && record[versionField] !== undefined && record[versionField] === version) {
3190
+ entityAlias = alias;
3191
+ break;
3192
+ }
3193
+ }
3194
+
3195
+ return entityAlias;
3196
+ }
3197
+
3137
3198
  module.exports = {
3138
3199
  Entity,
3139
3200
  clauses,
3201
+ getEntityIdentifiers,
3202
+ matchToEntityAlias,
3140
3203
  };
package/src/filters.js CHANGED
@@ -76,7 +76,7 @@ class FilterFactory {
76
76
  let filterParents = Object.entries(injected)
77
77
  .filter(clause => {
78
78
  let [name, { children }] = clause;
79
- return children.includes("go");
79
+ return children.find(child => ['go', 'commit'].includes(child));
80
80
  })
81
81
  .map(([name]) => name);
82
82
  let modelFilters = Object.keys(filters);
@@ -86,7 +86,7 @@ class FilterFactory {
86
86
  injected[name] = {
87
87
  name: name,
88
88
  action: this.buildClause(filter),
89
- children: ["params", "go", "filter", ...modelFilters],
89
+ children: ["params", "go", "commit", "filter", ...modelFilters],
90
90
  };
91
91
  }
92
92
  filterChildren.push("filter");
@@ -95,7 +95,7 @@ class FilterFactory {
95
95
  action: (entity, state, fn) => {
96
96
  return this.buildClause(fn)(entity, state);
97
97
  },
98
- children: ["params", "go", "filter", ...modelFilters],
98
+ children: ["params", "go", "commit", "filter", ...modelFilters],
99
99
  };
100
100
  for (let parent of filterParents) {
101
101
  injected[parent] = { ...injected[parent] };
package/src/service.js CHANGED
@@ -1,14 +1,15 @@
1
- const { Entity } = require("./entity");
1
+ const { Entity, getEntityIdentifiers, matchToEntityAlias } = require("./entity");
2
2
  const { clauses } = require("./clauses");
3
- const { KeyCasing, ServiceVersions, Pager, ElectroInstance, ElectroInstanceTypes, ModelVersions, IndexTypes } = require("./types");
3
+ const { TableIndex, TransactionMethods, KeyCasing, ServiceVersions, Pager, ElectroInstance, ElectroInstanceTypes, ModelVersions, IndexTypes } = require("./types");
4
4
  const { FilterFactory } = require("./filters");
5
5
  const { FilterOperations } = require("./operations");
6
6
  const { WhereFactory } = require("./where");
7
- const { getInstanceType, getModelVersion, applyBetaModelOverrides } = require("./util");
8
7
  const v = require("./validations");
9
8
  const c = require('./client');
10
9
  const e = require("./errors");
11
10
  const u = require("./util");
11
+ const txn = require("./transaction");
12
+ const { getInstanceType, getModelVersion, applyBetaModelOverrides } = require("./util");
12
13
 
13
14
  const ConstructorTypes = {
14
15
  beta: "beta",
@@ -68,6 +69,22 @@ class Service {
68
69
  this.compositeAttributes = {};
69
70
  this.collections = {};
70
71
  this.identifiers = {};
72
+ this.transaction = {
73
+ get: (fn) => {
74
+ return txn.createTransaction({
75
+ fn,
76
+ getEntities: () => this.entities,
77
+ method: TransactionMethods.transactGet,
78
+ });
79
+ },
80
+ write: (fn) => {
81
+ return txn.createTransaction({
82
+ fn,
83
+ getEntities: () => this.entities,
84
+ method: TransactionMethods.transactWrite,
85
+ });
86
+ }
87
+ };
71
88
  this._instance = ElectroInstance.service;
72
89
  this._instanceType = ElectroInstanceTypes.service;
73
90
  }
@@ -94,6 +111,22 @@ class Service {
94
111
  this.compositeAttributes = {};
95
112
  this.collections = {};
96
113
  this.identifiers = {};
114
+ this.transaction = {
115
+ get: (fn) => {
116
+ return txn.createTransaction({
117
+ fn,
118
+ getEntities: () => this.entities,
119
+ method: TransactionMethods.transactGet,
120
+ });
121
+ },
122
+ write: (fn) => {
123
+ return txn.createTransaction({
124
+ fn,
125
+ getEntities: () => this.entities,
126
+ method: TransactionMethods.transactWrite,
127
+ });
128
+ }
129
+ };
97
130
  this._instance = ElectroInstance.service;
98
131
  this._instanceType = ElectroInstanceTypes.service;
99
132
  }
@@ -270,53 +303,39 @@ class Service {
270
303
  }
271
304
  }
272
305
 
273
- _getEntityIdentifiers(entities) {
274
- let identifiers = [];
275
- for (let alias of Object.keys(entities)) {
276
- let entity = entities[alias];
277
- let name = entity.model.entity;
278
- let version = entity.model.version;
279
- identifiers.push({
280
- name,
281
- alias,
282
- version,
283
- entity,
284
- nameField: entity.identifiers.entity,
285
- versionField: entity.identifiers.version
286
- });
287
- }
288
- return identifiers;
289
- }
290
-
291
- cleanseRetrievedData(collection = "", entities, data = {}, config = {}) {
306
+ cleanseRetrievedData(index = TableIndex, entities, data = {}, config = {}) {
292
307
  if (config.raw) {
293
308
  return data;
294
309
  }
310
+ const identifiers = getEntityIdentifiers(entities);
311
+
295
312
  data.Items = data.Items || [];
296
- let index = this.collectionSchema[collection].index;
297
- let results = {};
298
- let identifiers = this._getEntityIdentifiers(entities);
313
+
314
+ const results = {};
299
315
  for (let {alias} of identifiers) {
300
316
  results[alias] = [];
301
317
  }
302
- for (let record of data.Items) {
303
- let entityAlias;
304
- for (let {name, version, nameField, versionField, alias} of identifiers) {
305
- if (record[nameField] !== undefined && record[nameField] === name && record[versionField] !== undefined && record[versionField] === version) {
306
- entityAlias = alias;
307
- break;
308
- }
318
+
319
+ for (let i = 0; i < data.Items.length; i++) {
320
+ const record = data.Items[i];
321
+
322
+ if (!record) {
323
+ continue;
309
324
  }
325
+
326
+ const entityAlias = matchToEntityAlias({identifiers, record});
327
+
310
328
  if (!entityAlias) {
311
329
  continue;
312
330
  }
313
331
  // pager=false because we don't want the entity trying to parse the lastEvaluatedKey
314
- let items = this.collectionSchema[collection].entities[entityAlias].formatResponse({Item: record}, index, {
332
+ let formatted = entities[entityAlias].formatResponse({Item: record}, index, {
315
333
  ...config,
316
334
  pager: false,
317
335
  parse: undefined
318
336
  });
319
- results[entityAlias].push(items.data);
337
+
338
+ results[entityAlias].push(formatted.data);
320
339
  }
321
340
  return results;
322
341
  }
@@ -364,7 +383,7 @@ class Service {
364
383
  name = "",
365
384
  initialClauses = {},
366
385
  }, facets = {}) {
367
- const { entities, attributes, identifiers, indexType } = this.collectionSchema[name];
386
+ const { entities, attributes, identifiers, indexType, index } = this.collectionSchema[name];
368
387
  const compositeAttributes = this.compositeAttributes[name];
369
388
  const allEntities = Object.values(entities);
370
389
  const entity = allEntities[0];
@@ -381,7 +400,10 @@ class Service {
381
400
  let options = {
382
401
  // expressions, // DynamoDB doesnt return what I expect it would when provided with these entity filters
383
402
  parse: (options, data) => {
384
- return this.cleanseRetrievedData(name, entities, data, options);
403
+ if (options.raw) {
404
+ return data;
405
+ }
406
+ return this.cleanseRetrievedData(index, entities, data, options);
385
407
  },
386
408
  formatCursor: {
387
409
  serialize: (key) => {
@@ -735,4 +757,6 @@ class Service {
735
757
  }
736
758
  }
737
759
 
738
- module.exports = { Service };
760
+ module.exports = {
761
+ Service,
762
+ };
@@ -0,0 +1,179 @@
1
+ const { TableIndex, TransactionMethods } = require('./types');
2
+ const { getEntityIdentifiers, matchToEntityAlias } = require('./entity');
3
+
4
+ function cleanseCanceledData(index = TableIndex, entities, data = {}, config = {}) {
5
+ if (config.raw) {
6
+ return data;
7
+ }
8
+ const identifiers = getEntityIdentifiers(entities);
9
+ const canceled = data.canceled || [];
10
+ const paramItems = config._paramItems || [];
11
+ const results = [];
12
+ for (let i = 0; i < canceled.length; i++) {
13
+ const { Item, Code, Message } = canceled[i] || {};
14
+ const paramItem = paramItems[i];
15
+ const code = Code || 'None';
16
+ const rejected = code !== 'None';
17
+ const result = {
18
+ rejected,
19
+ code,
20
+ message: Message,
21
+ }
22
+
23
+ if (Item) {
24
+ const entityAlias = matchToEntityAlias({
25
+ record: Item,
26
+ paramItem,
27
+ identifiers
28
+ });
29
+ result.item = entities[entityAlias].formatResponse({Item}, index, {
30
+ ...config,
31
+ pager: false,
32
+ parse: undefined,
33
+ }).data;
34
+ } else {
35
+ result.item = null;
36
+ }
37
+
38
+ results.push(result);
39
+ }
40
+
41
+ return results;
42
+ }
43
+
44
+ function cleanseTransactionData(index = TableIndex, entities, data = {}, config = {}) {
45
+ if (config.raw) {
46
+ return data;
47
+ }
48
+ const identifiers = getEntityIdentifiers(entities);
49
+ data.Items = data.Items || [];
50
+ const paramItems = config._paramItems || [];
51
+ const results = [];
52
+ for (let i = 0; i < data.Items.length; i++) {
53
+ const record = data.Items[i];
54
+ if (!record) {
55
+ results.push(null);
56
+ continue;
57
+ }
58
+
59
+ const paramItem = paramItems[i];
60
+ const entityAlias = matchToEntityAlias({paramItem, identifiers, record});
61
+ if (!entityAlias) {
62
+ continue;
63
+ }
64
+
65
+ // pager=false because we don't want the entity trying to parse the lastEvaluatedKey
66
+ let formatted = entities[entityAlias].formatResponse({ Item: record }, index, {
67
+ ...config,
68
+ pager: false,
69
+ parse: undefined
70
+ });
71
+
72
+ results.push(formatted.data);
73
+ }
74
+
75
+ return results.map(item => ({
76
+ rejected: false,
77
+ item,
78
+ }));
79
+ }
80
+
81
+ function createTransaction(options) {
82
+ const { fn, method, getEntities } = options;
83
+ const operations = {
84
+ params: (options = {}) => {
85
+ const paramItems = fn(getEntities());
86
+ const params = {
87
+ TransactItems: paramItems,
88
+ };
89
+
90
+ if (typeof options.token === 'string' && options.token.length) {
91
+ params['ClientRequestToken'] = options.token;
92
+ }
93
+ if (options._returnParamItems) {
94
+ return { params, paramItems };
95
+ }
96
+ return params;
97
+ },
98
+ go: async (options) => {
99
+ const driver = Object.values(getEntities())[0];
100
+
101
+ if (!driver) {
102
+ throw new Error('At least one entity must exist to perform a transaction');
103
+ }
104
+
105
+ const { params, paramItems } = operations.params({
106
+ ...options,
107
+ _returnParamItems: true
108
+ });
109
+
110
+ let canceled = false;
111
+ if (paramItems.length === 0) {
112
+ return {
113
+ canceled,
114
+ data: [],
115
+ }
116
+ }
117
+
118
+ const response = await driver.go(method, params, {
119
+ ...options,
120
+ parse: (options, data) => {
121
+ if (options.raw) {
122
+ return data;
123
+ } else if (data.canceled) {
124
+ canceled = true;
125
+ return cleanseCanceledData(TableIndex, getEntities(), data, {
126
+ ...options,
127
+ _isTransaction: true,
128
+ _paramItems: paramItems,
129
+ });
130
+ } else if (data.Responses) {
131
+ return cleanseTransactionData(TableIndex, getEntities(), {
132
+ Items: data.Responses.map(response => response.Item)
133
+ }, {
134
+ ...options,
135
+ _isTransaction: true,
136
+ _paramItems: paramItems,
137
+ });
138
+ } else {
139
+ return new Array(paramItems ? paramItems.length : 0).fill({
140
+ item: null,
141
+ code: 'None',
142
+ rejected: false,
143
+ message: undefined,
144
+ });
145
+ }
146
+ }
147
+ });
148
+
149
+ return {
150
+ ...response,
151
+ canceled,
152
+ }
153
+ }
154
+ }
155
+
156
+ return operations;
157
+ }
158
+
159
+ function createWriteTransaction(entities, fn) {
160
+ return createTransaction({
161
+ fn,
162
+ method: TransactionMethods.transactWrite,
163
+ getEntities: () => entities,
164
+ });
165
+ }
166
+
167
+ function createGetTransaction(entities, fn) {
168
+ return createTransaction({
169
+ fn,
170
+ method: TransactionMethods.transactGet,
171
+ getEntities: () => entities,
172
+ });
173
+ }
174
+
175
+ module.exports = {
176
+ createTransaction,
177
+ createWriteTransaction,
178
+ createGetTransaction,
179
+ }
package/src/types.js CHANGED
@@ -23,6 +23,7 @@ const QueryTypes = {
23
23
  };
24
24
 
25
25
  const MethodTypes = {
26
+ check: "check",
26
27
  put: "put",
27
28
  get: "get",
28
29
  query: "query",
@@ -35,8 +36,27 @@ const MethodTypes = {
35
36
  batchGet: "batchGet",
36
37
  batchWrite: "batchWrite",
37
38
  upsert: "upsert",
39
+ transactWrite: "transactWrite",
40
+ transactGet: "transactGet",
38
41
  };
39
42
 
43
+ const TransactionMethods = {
44
+ transactWrite: MethodTypes.transactWrite,
45
+ transactGet: MethodTypes.transactGet,
46
+ }
47
+
48
+ const TransactionOperations = {
49
+ [MethodTypes.get]: "Get",
50
+ [MethodTypes.check]: "ConditionCheck",
51
+ [MethodTypes.put]: "Put",
52
+ [MethodTypes.create]: "Put",
53
+ [MethodTypes.upsert]: "Update",
54
+ [MethodTypes.update]: "Update",
55
+ [MethodTypes.patch]: "Update",
56
+ [MethodTypes.remove]: "Delete",
57
+ [MethodTypes.delete]: "Delete",
58
+ }
59
+
40
60
  const MethodTypeTranslation = {
41
61
  put: "put",
42
62
  get: "get",
@@ -50,6 +70,8 @@ const MethodTypeTranslation = {
50
70
  batchGet: "batchGet",
51
71
  batchWrite: "batchWrite",
52
72
  upsert: "update",
73
+ transactWrite: 'transactWrite',
74
+ transactGet: 'transactGet',
53
75
  }
54
76
 
55
77
  const IndexTypes = {
@@ -198,6 +220,7 @@ const ItemOperations = {
198
220
  };
199
221
 
200
222
  const AttributeProxySymbol = Symbol("attribute_proxy");
223
+ const TransactionCommitSymbol = Symbol('transaction_commit');
201
224
 
202
225
  const BuilderTypes = {
203
226
  update: "update",
@@ -325,4 +348,7 @@ module.exports = {
325
348
  AllPages,
326
349
  ResultOrderOption,
327
350
  ResultOrderParam,
351
+ TransactionCommitSymbol,
352
+ TransactionOperations,
353
+ TransactionMethods,
328
354
  };
package/src/where.js CHANGED
@@ -76,6 +76,7 @@ class WhereFactory {
76
76
  case MethodTypes.remove:
77
77
  case MethodTypes.upsert:
78
78
  case MethodTypes.get:
79
+ case MethodTypes.check:
79
80
  return ExpressionTypes.ConditionExpression
80
81
  default:
81
82
  return ExpressionTypes.FilterExpression
@@ -108,7 +109,7 @@ class WhereFactory {
108
109
  let filterParents = Object.entries(injected)
109
110
  .filter(clause => {
110
111
  let [name, { children }] = clause;
111
- return children.includes("go");
112
+ return children.find(child => ['go', 'commit'].includes(child));
112
113
  })
113
114
  .map(([name]) => name);
114
115
  let modelFilters = Object.keys(filters);
@@ -118,7 +119,7 @@ class WhereFactory {
118
119
  injected[name] = {
119
120
  name,
120
121
  action: this.buildClause(filter),
121
- children: ["params", "go", "where", ...modelFilters],
122
+ children: ["params", "go", "commit", "where", ...modelFilters],
122
123
  };
123
124
  }
124
125
  filterChildren.push("where");
@@ -127,7 +128,7 @@ class WhereFactory {
127
128
  action: (entity, state, fn) => {
128
129
  return this.buildClause(fn)(entity, state);
129
130
  },
130
- children: ["params", "go", "where", ...modelFilters],
131
+ children: ["params", "go", "commit", "where", ...modelFilters],
131
132
  };
132
133
  for (let parent of filterParents) {
133
134
  injected[parent] = { ...injected[parent] };
package/notes DELETED
@@ -1,8 +0,0 @@
1
- - add hydrate option to query
2
- - add hydrate option to collection
3
- - implement hydrate on entity query
4
- - implement hydrate on collection query
5
- - add index property to signify only keys are present
6
- - skip for match/find
7
- - do not apply automatic filters on queries
8
- - validate