electrodb 1.10.2 → 1.11.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/README.md CHANGED
@@ -2607,9 +2607,10 @@ const formattedQueryResults = myEntity.parse(formattedQueryResults);
2607
2607
 
2608
2608
  Parse also accepts an optional `options` object as a second argument (see the section [Query Options](#query-options) to learn more). Currently, the following query options are relevant to the `parse()` method:
2609
2609
 
2610
- Option | Default | Notes
2611
- ----------------- : ------- | -----
2612
- `ignoreOwnership` | `true` | This property defaults to `true` here, unlike elsewhere in the application when it defaults to `false`. You can overwrite the default here with your own preference.
2610
+ Option | Type | Default | Notes
2611
+ ----------------- : -------- : ------------------ | -----
2612
+ ignoreOwnership | boolean | `true` | This property defaults to `true` here, unlike elsewhere in the application when it defaults to `false`. You can overwrite the default here with your own preference.
2613
+ attributes | string[] | _(all attributes)_ | The `attributes` option allows you to specify a subset of attributes to return
2613
2614
 
2614
2615
  # Building Queries
2615
2616
  > For hands-on learners: the following example can be followed along with **and** executed on runkit: https://runkit.com/tywalch/electrodb-building-queries
@@ -4300,6 +4301,7 @@ By default, **ElectroDB** enables you to work with records as the names and prop
4300
4301
  logger?: (event) => void;
4301
4302
  listeners Array<(event) => void>;
4302
4303
  preserveBatchOrder?: boolean;
4304
+ attributes?: string[];
4303
4305
  };
4304
4306
  ```
4305
4307
 
@@ -4307,6 +4309,7 @@ Option | Default | Description
4307
4309
  ------------------ | :------------------: | -----------
4308
4310
  params | `{}` | Properties added to this object will be merged onto the params sent to the document client. Any conflicts with **ElectroDB** will favor the params specified here.
4309
4311
  table | _(from constructor)_ | Use a different table than the one defined in the [Service Options](#service-options)
4312
+ attributes | _(all attributes)_ | The `attributes` query option allows you to specify ProjectionExpression Attributes for your `get` or `query` operation. As of `1.11.0` only root attributes are allowed to be specified.
4310
4313
  raw | `false` | Returns query results as they were returned by the docClient.
4311
4314
  includeKeys | `false` | By default, **ElectroDB** does not return partition, sort, or global keys in its response.
4312
4315
  pager | `"named"` | Used in with pagination (`.pages()`) calls to override ElectroDBs default behaviour to break apart `LastEvaluatedKeys` records into composite attributes. See more detail about this in the sections for [Pager Query Options](#pager-query-options).
package/index.d.ts CHANGED
@@ -79,6 +79,7 @@ export type CollectionWhereCallback<E extends {[name: string]: Entity<any, any,
79
79
 
80
80
  export type CollectionWhereClause<E extends {[name: string]: Entity<any, any, any, any>}, A extends string, F extends string, C extends string, S extends Schema<A,F,C>, I extends Partial<AllEntityAttributes<E>>, T> = (where: CollectionWhereCallback<E, I>) => T;
81
81
 
82
+
82
83
  export interface WhereRecordsActionOptions<E extends {[name: string]: Entity<any, any, any, any>}, A extends string, F extends string, C extends string, S extends Schema<A,F,C>, I extends Partial<AllEntityAttributes<E>>, Items, IndexCompositeAttributes> {
83
84
  go: GoRecord<Items>;
84
85
  params: ParamRecord;
@@ -349,6 +350,13 @@ export type CollectionItem<SERVICE extends Service<any>, COLLECTION extends keyo
349
350
  : never>
350
351
  : never
351
352
 
353
+ export interface QueryBranches<A extends string,
354
+ F extends string, C extends string, S extends Schema<A,F,C>, ResponseItem, IndexCompositeAttributes> {
355
+ go: GoQueryTerminal<A,F,C,S,ResponseItem>;
356
+ params: ParamTerminal<A,F,C,S,ResponseItem>;
357
+ page: PageQueryTerminal<A,F,C,S,ResponseItem,IndexCompositeAttributes>;
358
+ where: WhereClause<A,F,C,S,Item<A,F,C,S,S["attributes"]>,QueryBranches<A,F,C,S,ResponseItem,IndexCompositeAttributes>>
359
+ }
352
360
 
353
361
  export interface RecordsActionOptions<A extends string,
354
362
  F extends string, C extends string, S extends Schema<A,F,C>, Items, IndexCompositeAttributes> {
@@ -359,11 +367,16 @@ export interface RecordsActionOptions<A extends string,
359
367
  }
360
368
 
361
369
  export interface SingleRecordOperationOptions<A extends string, F extends string, C extends string, S extends Schema<A,F,C>, ResponseType> {
362
- go: GoRecord<ResponseType, QueryOptions>;
363
- params: ParamRecord<QueryOptions>;
370
+ go: GoGetTerminal<A,F,C,S, ResponseType>;
371
+ params: ParamTerminal<A,F,C,S,ResponseType>;
364
372
  where: WhereClause<A,F,C,S,Item<A,F,C,S,S["attributes"]>,SingleRecordOperationOptions<A,F,C,S,ResponseType>>;
365
373
  }
366
374
 
375
+ export interface BatchGetRecordOperationOptions<A extends string, F extends string, C extends string, S extends Schema<A,F,C>, ResponseType> {
376
+ go: GoBatchGetTerminal<A,F,C,S,ResponseType>
377
+ params: ParamTerminal<A,F,C,S,ResponseType>;
378
+ }
379
+
367
380
  export interface PutRecordOperationOptions<A extends string, F extends string, C extends string, S extends Schema<A,F,C>, ResponseType> {
368
381
  go: GoRecord<ResponseType, PutQueryOptions>;
369
382
  params: ParamRecord<PutQueryOptions>;
@@ -406,17 +419,17 @@ export type RemoveRecord<A extends string, F extends string, C extends string, S
406
419
  export type DataUpdateMethodRecord<A extends string, F extends string, C extends string, S extends Schema<A,F,C>, SetAttr, IndexCompositeAttributes, TableItem> =
407
420
  DataUpdateMethod<A,F,C,S, UpdateData<A,F,C,S>, SetRecordActionOptions<A,F,C,S, SetAttr, IndexCompositeAttributes, TableItem>>
408
421
 
409
- interface QueryOperations<A extends string, F extends string, C extends string, S extends Schema<A,F,C>, CompositeAttributes, TableItem, IndexCompositeAttributes> {
410
- between: (skCompositeAttributesStart: CompositeAttributes, skCompositeAttributesEnd: CompositeAttributes) => RecordsActionOptions<A,F,C,S, Array<TableItem>,IndexCompositeAttributes>;
411
- gt: (skCompositeAttributes: CompositeAttributes) => RecordsActionOptions<A,F,C,S, Array<TableItem>,IndexCompositeAttributes>;
412
- gte: (skCompositeAttributes: CompositeAttributes) => RecordsActionOptions<A,F,C,S, Array<TableItem>,IndexCompositeAttributes>;
413
- lt: (skCompositeAttributes: CompositeAttributes) => RecordsActionOptions<A,F,C,S, Array<TableItem>,IndexCompositeAttributes>;
414
- lte: (skCompositeAttributes: CompositeAttributes) => RecordsActionOptions<A,F,C,S, Array<TableItem>,IndexCompositeAttributes>;
415
- begins: (skCompositeAttributes: CompositeAttributes) => RecordsActionOptions<A,F,C,S, Array<TableItem>,IndexCompositeAttributes>;
416
- go: GoRecord<Array<TableItem>>;
417
- params: ParamRecord;
418
- page: PageRecord<Array<TableItem>,IndexCompositeAttributes>;
419
- where: WhereClause<A,F,C,S,Item<A,F,C,S,S["attributes"]>,RecordsActionOptions<A,F,C,S,Array<TableItem>,IndexCompositeAttributes>>
422
+ interface QueryOperations<A extends string, F extends string, C extends string, S extends Schema<A,F,C>, CompositeAttributes, ResponseItem, IndexCompositeAttributes> {
423
+ between: (skCompositeAttributesStart: CompositeAttributes, skCompositeAttributesEnd: CompositeAttributes) => QueryBranches<A,F,C,S, ResponseItem, IndexCompositeAttributes>;
424
+ gt: (skCompositeAttributes: CompositeAttributes) => QueryBranches<A,F,C,S, ResponseItem,IndexCompositeAttributes>;
425
+ gte: (skCompositeAttributes: CompositeAttributes) => QueryBranches<A,F,C,S, ResponseItem,IndexCompositeAttributes>;
426
+ lt: (skCompositeAttributes: CompositeAttributes) => QueryBranches<A,F,C,S, ResponseItem,IndexCompositeAttributes>;
427
+ lte: (skCompositeAttributes: CompositeAttributes) => QueryBranches<A,F,C,S, ResponseItem,IndexCompositeAttributes>;
428
+ begins: (skCompositeAttributes: CompositeAttributes) => QueryBranches<A,F,C,S, ResponseItem,IndexCompositeAttributes>;
429
+ go: GoQueryTerminal<A,F,C,S,ResponseItem>;
430
+ params: ParamTerminal<A,F,C,S,ResponseItem>;
431
+ page: PageQueryTerminal<A,F,C,S,ResponseItem,IndexCompositeAttributes>;
432
+ where: WhereClause<A,F,C,S,Item<A,F,C,S,S["attributes"]>,QueryBranches<A,F,C,S,ResponseItem,IndexCompositeAttributes>>
420
433
  }
421
434
 
422
435
  export type Queries<A extends string, F extends string, C extends string, S extends Schema<A,F,C>> = {
@@ -424,7 +437,7 @@ export type Queries<A extends string, F extends string, C extends string, S exte
424
437
  IndexSKAttributes<A,F,C,S,I> extends infer SK
425
438
  // If there is no SK, dont show query operations (when an empty array is provided)
426
439
  ? [keyof SK] extends [never]
427
- ? RecordsActionOptions<A,F,C,S, ResponseItem<A,F,C,S>[], AllTableIndexCompositeAttributes<A,F,C,S> & Required<CompositeAttributes>>
440
+ ? QueryBranches<A,F,C,S, ResponseItem<A,F,C,S>, AllTableIndexCompositeAttributes<A,F,C,S> & Required<CompositeAttributes>>
428
441
  // If there is no SK, dont show query operations (When no PK is specified)
429
442
  : S["indexes"][I] extends IndexWithSortKey
430
443
  ? QueryOperations<
@@ -434,7 +447,7 @@ export type Queries<A extends string, F extends string, C extends string, S exte
434
447
  ResponseItem<A,F,C,S>,
435
448
  AllTableIndexCompositeAttributes<A,F,C,S> & Required<CompositeAttributes> & SK
436
449
  >
437
- : RecordsActionOptions<A,F,C,S, ResponseItem<A,F,C,S>[], AllTableIndexCompositeAttributes<A,F,C,S> & Required<CompositeAttributes> & SK>
450
+ : QueryBranches<A,F,C,S, ResponseItem<A,F,C,S>, AllTableIndexCompositeAttributes<A,F,C,S> & Required<CompositeAttributes> & SK>
438
451
  : never
439
452
  }
440
453
 
@@ -464,7 +477,8 @@ export interface QueryOptions {
464
477
  }
465
478
 
466
479
  // subset of QueryOptions
467
- export interface ParseOptions {
480
+ export interface ParseOptions<Attributes> {
481
+ attributes?: ReadonlyArray<Attributes>;
468
482
  ignoreOwnership?: boolean;
469
483
  }
470
484
 
@@ -510,6 +524,162 @@ export type OptionalDefaultEntityIdentifiers = {
510
524
  __edb_v__?: string;
511
525
  }
512
526
 
527
+ interface GoBatchGetTerminalOptions<Attributes> {
528
+ raw?: boolean;
529
+ table?: string;
530
+ limit?: number;
531
+ params?: object;
532
+ includeKeys?: boolean;
533
+ originalErr?: boolean;
534
+ ignoreOwnership?: boolean;
535
+ pages?: number;
536
+ attributes?: ReadonlyArray<Attributes>;
537
+ unprocessed?: "raw" | "item";
538
+ concurrency?: number;
539
+ preserveBatchOrder?: boolean;
540
+ listeners?: Array<ElectroEventListener>;
541
+ logger?: ElectroEventListener;
542
+ }
543
+
544
+ interface GoQueryTerminalOptions<Attributes> {
545
+ raw?: boolean;
546
+ table?: string;
547
+ limit?: number;
548
+ params?: object;
549
+ includeKeys?: boolean;
550
+ originalErr?: boolean;
551
+ ignoreOwnership?: boolean;
552
+ pages?: number;
553
+ attributes?: ReadonlyArray<Attributes>;
554
+ listeners?: Array<ElectroEventListener>;
555
+ logger?: ElectroEventListener;
556
+ }
557
+
558
+ interface PageQueryTerminalOptions<Attributes> extends GoQueryTerminalOptions<Attributes> {
559
+ pager?: "raw" | "item" | "named";
560
+ raw?: boolean;
561
+ table?: string;
562
+ limit?: number;
563
+ includeKeys?: boolean;
564
+ originalErr?: boolean;
565
+ ignoreOwnership?: boolean;
566
+ attributes?: ReadonlyArray<Attributes>;
567
+ listeners?: Array<ElectroEventListener>;
568
+ logger?: ElectroEventListener;
569
+ }
570
+
571
+ export interface ParamTerminalOptions<Attributes> {
572
+ table?: string;
573
+ limit?: number;
574
+ params?: object;
575
+ originalErr?: boolean;
576
+ attributes?: ReadonlyArray<Attributes>;
577
+ response?: "default" | "none" | 'all_old' | 'updated_old' | 'all_new' | 'updated_new';
578
+ }
579
+
580
+ type GoBatchGetTerminal<A extends string, F extends string, C extends string, S extends Schema<A,F,C>, ResponseItem> = <Options extends GoBatchGetTerminalOptions<keyof ResponseItem>>(options?: Options) =>
581
+ Options extends GoBatchGetTerminalOptions<infer Attr>
582
+ ? 'preserveBatchOrder' extends keyof Options
583
+ ? Options['preserveBatchOrder'] extends true
584
+ ? Promise<[
585
+ Array<Resolve<{
586
+ [
587
+ Name in keyof ResponseItem as Name extends Attr
588
+ ? Name
589
+ : never
590
+ ]: ResponseItem[Name]
591
+ } | null>>, Array<Resolve<AllTableIndexCompositeAttributes<A,F,C,S>>>
592
+ ]>
593
+ : Promise<[
594
+ Array<Resolve<{
595
+ [
596
+ Name in keyof ResponseItem as Name extends Attr
597
+ ? Name
598
+ : never
599
+ ]: ResponseItem[Name]
600
+ }>>, Array<Resolve<AllTableIndexCompositeAttributes<A,F,C,S>>>
601
+ ]>
602
+ : Promise<[
603
+ Array<Resolve<{
604
+ [
605
+ Name in keyof ResponseItem as Name extends Attr
606
+ ? Name
607
+ : never
608
+ ]: ResponseItem[Name]
609
+ }>>, Array<Resolve<AllTableIndexCompositeAttributes<A,F,C,S>>>
610
+ ]>
611
+ : 'preserveBatchOrder' extends keyof Options
612
+ ? Options['preserveBatchOrder'] extends true
613
+ ? [Array<Resolve<ResponseItem | null>>, Array<Resolve<AllTableIndexCompositeAttributes<A,F,C,S>>>]
614
+ : [Array<Resolve<ResponseItem>>, Array<Resolve<AllTableIndexCompositeAttributes<A,F,C,S>>>]
615
+ : [Array<Resolve<ResponseItem>>, Array<Resolve<AllTableIndexCompositeAttributes<A,F,C,S>>>]
616
+
617
+ type GoGetTerminal<A extends string, F extends string, C extends string, S extends Schema<A,F,C>, ResponseItem> = <Options extends GoQueryTerminalOptions<keyof ResponseItem>>(options?: Options) =>
618
+ Options extends GoQueryTerminalOptions<infer Attr>
619
+ ? Promise<{
620
+ [
621
+ Name in keyof ResponseItem as Name extends Attr
622
+ ? Name
623
+ : never
624
+ ]: ResponseItem[Name]
625
+ } | null>
626
+ : Promise<ResponseItem | null>
627
+
628
+ export type GoQueryTerminal<A extends string, F extends string, C extends string, S extends Schema<A,F,C>, Item> = <Options extends GoQueryTerminalOptions<keyof Item>>(options?: Options) =>
629
+ Options extends GoQueryTerminalOptions<infer Attr>
630
+ ? Promise<Array<{
631
+ [
632
+ Name in keyof Item as Name extends Attr
633
+ ? Name
634
+ : never
635
+ ]: Item[Name]
636
+ }>>
637
+ : Promise<Array<Item>>
638
+
639
+ export type EntityParseSingleItem<A extends string, F extends string, C extends string, S extends Schema<A,F,C>, ResponseItem> =
640
+ <Options extends ParseOptions<keyof ResponseItem>>(item: ParseSingleInput, options?: Options) =>
641
+ Options extends ParseOptions<infer Attr>
642
+ ? {
643
+ [
644
+ Name in keyof ResponseItem as Name extends Attr
645
+ ? Name
646
+ : never
647
+ ]: ResponseItem[Name]
648
+ } | null
649
+ : ResponseItem | null
650
+
651
+ export type EntityParseMultipleItems<A extends string, F extends string, C extends string, S extends Schema<A,F,C>, ResponseItem> =
652
+ <Options extends ParseOptions<keyof ResponseItem>>(item: ParseMultiInput, options?: Options) =>
653
+ Options extends ParseOptions<infer Attr>
654
+ ? Array<{
655
+ [
656
+ Name in keyof ResponseItem as Name extends Attr
657
+ ? Name
658
+ : never
659
+ ]: ResponseItem[Name]
660
+ }>
661
+ : Array<ResponseItem>
662
+
663
+
664
+ export type PageQueryTerminal<A extends string, F extends string, C extends string, S extends Schema<A,F,C>, Item, CompositeAttributes> = <Options extends PageQueryTerminalOptions<keyof Item>>(page?: (CompositeAttributes & OptionalDefaultEntityIdentifiers) | null, options?: Options) =>
665
+ Options extends GoQueryTerminalOptions<infer Attr>
666
+ ? Promise<[
667
+ (CompositeAttributes & OptionalDefaultEntityIdentifiers) | null,
668
+ Array<{
669
+ [
670
+ Name in keyof Item as Name extends Attr
671
+ ? Name
672
+ : never
673
+ ]: Item[Name]
674
+ }>
675
+ ]>
676
+ : Promise<[
677
+ (CompositeAttributes & OptionalDefaultEntityIdentifiers) | null,
678
+ Array<ResponseType>
679
+ ]>;
680
+
681
+ export type ParamTerminal<A extends string, F extends string, C extends string, S extends Schema<A,F,C>, ResponseItem> = <P extends any = any, Options extends ParamTerminalOptions<keyof ResponseItem> = ParamTerminalOptions<keyof ResponseItem>>(options?: Options) => P;
682
+
513
683
  export type GoRecord<ResponseType, Options = QueryOptions> = <T = ResponseType>(options?: Options) => Promise<T>;
514
684
 
515
685
  export type BatchGoRecord<ResponseType, AlternateResponseType> = <O extends BulkOptions>(options?: O) =>
@@ -1037,18 +1207,6 @@ export type ItemAttribute<A extends Attribute> =
1037
1207
  : never
1038
1208
  : never
1039
1209
 
1040
- type FormattedPutMapAttributes<A extends MapAttribute> = {
1041
- [P in keyof A["properties"]]: A["properties"][P] extends infer M
1042
- ? M extends HiddenAttribute
1043
- ? false
1044
- : M extends DefaultedAttribute
1045
- ? false
1046
- : M extends RequiredAttribute
1047
- ? true
1048
- : false
1049
- : false
1050
- }
1051
-
1052
1210
  export type ReturnedAttribute<A extends Attribute> =
1053
1211
  A["type"] extends infer R
1054
1212
  ? R extends "string" ? string
@@ -1079,27 +1237,6 @@ export type ReturnedAttribute<A extends Attribute> =
1079
1237
  : never
1080
1238
  : never
1081
1239
  }
1082
- // SkipKeys<{
1083
- // [P in keyof A["properties"]]: A["properties"][P] extends infer M
1084
- // ? M extends Attribute
1085
- // ? M extends HiddenAttribute
1086
- // ? SkipValue
1087
- // : M extends RequiredAttribute
1088
- // ? ReturnedAttribute<M>
1089
- // : SkipValue
1090
- // : never
1091
- // : never
1092
- // }> & SkipKeys<{
1093
- // [P in keyof A["properties"]]?: A["properties"][P] extends infer M
1094
- // ? M extends Attribute
1095
- // ? M extends HiddenAttribute
1096
- // ? SkipValue
1097
- // : M extends RequiredAttribute
1098
- // ? SkipValue
1099
- // : ReturnedAttribute<M> | undefined
1100
- // : never
1101
- // : never
1102
- // }>
1103
1240
  : never
1104
1241
  : R extends "list"
1105
1242
  ? "items" extends keyof A
@@ -1151,27 +1288,6 @@ export type CreatedAttribute<A extends Attribute> =
1151
1288
  : never
1152
1289
  : never
1153
1290
  }
1154
- // ? SkipKeys<{
1155
- // [P in keyof A["properties"]]: A["properties"][P] extends infer M
1156
- // ? M extends Attribute
1157
- // ? M extends HiddenAttribute
1158
- // ? SkipValue
1159
- // : M extends DefaultedAttribute
1160
- // ? SkipValue
1161
- // : M extends RequiredAttribute
1162
- // ? CreatedAttribute<M>
1163
- // : SkipValue
1164
- // : never
1165
- // : never
1166
- // }> & SkipKeys<{
1167
- // [P in keyof A["properties"]]?: A["properties"][P] extends infer M
1168
- // ? M extends Attribute
1169
- // ? M extends HiddenAttribute
1170
- // ? SkipValue
1171
- // : CreatedAttribute<M> | undefined
1172
- // : never
1173
- // : never
1174
- // }>
1175
1291
  : never
1176
1292
  : R extends "list"
1177
1293
  ? "items" extends keyof A
@@ -1632,9 +1748,8 @@ export class Entity<A extends string, F extends string, C extends string, S exte
1632
1748
  private config?: EntityConfiguration;
1633
1749
  constructor(schema: S, config?: EntityConfiguration);
1634
1750
 
1635
- get(key: AllTableIndexCompositeAttributes<A,F,C,S>): SingleRecordOperationOptions<A,F,C,S, ResponseItem<A,F,C,S> | null>;
1636
- get(key: AllTableIndexCompositeAttributes<A,F,C,S>[]): BulkRecordOperationOptions<A,F,C,S, [Array<Resolve<ResponseItem<A,F,C,S>>>, Array<Resolve<AllTableIndexCompositeAttributes<A,F,C,S>>>], [Array<Resolve<ResponseItem<A,F,C,S>> | null>, Array<Resolve<AllTableIndexCompositeAttributes<A,F,C,S>>>]>;
1637
-
1751
+ get(key: AllTableIndexCompositeAttributes<A,F,C,S>): SingleRecordOperationOptions<A,F,C,S, ResponseItem<A,F,C,S>>;
1752
+ get(key: AllTableIndexCompositeAttributes<A,F,C,S>[]): BatchGetRecordOperationOptions<A,F,C,S, ResponseItem<A,F,C,S>>;
1638
1753
  delete(key: AllTableIndexCompositeAttributes<A,F,C,S>): DeleteRecordOperationOptions<A,F,C,S, ResponseItem<A,F,C,S>>;
1639
1754
  delete(key: AllTableIndexCompositeAttributes<A,F,C,S>[]): BulkRecordOperationOptions<A,F,C,S, AllTableIndexCompositeAttributes<A,F,C,S>[], AllTableIndexCompositeAttributes<A,F,C,S>[]>;
1640
1755
 
@@ -1671,8 +1786,26 @@ export class Entity<A extends string, F extends string, C extends string, S exte
1671
1786
  scan: RecordsActionOptions<A,F,C,S, ResponseItem<A,F,C,S>[], TableIndexCompositeAttributes<A,F,C,S>>;
1672
1787
  query: Queries<A,F,C,S>;
1673
1788
 
1674
- parse(item: ParseSingleInput, options?: ParseOptions): ResponseItem<A,F,C,S> | null;
1675
- parse(item: ParseMultiInput, options?: ParseOptions): ResponseItem<A,F,C,S>[];
1789
+ parse<Options extends ParseOptions<keyof ResponseItem<A,F,C,S>>>(item: ParseSingleInput, options?: Options):
1790
+ Options extends ParseOptions<infer Attr>
1791
+ ? {
1792
+ [
1793
+ Name in keyof ResponseItem<A,F,C,S> as Name extends Attr
1794
+ ? Name
1795
+ : never
1796
+ ]: ResponseItem<A,F,C,S>[Name]
1797
+ } | null
1798
+ : ResponseItem<A,F,C,S> | null
1799
+ parse<Options extends ParseOptions<keyof ResponseItem<A,F,C,S>>>(item: ParseMultiInput, options?: Options):
1800
+ Options extends ParseOptions<infer Attr>
1801
+ ? Array<{
1802
+ [
1803
+ Name in keyof ResponseItem<A,F,C,S> as Name extends Attr
1804
+ ? Name
1805
+ : never
1806
+ ]: ResponseItem<A,F,C,S>[Name]
1807
+ }>
1808
+ : Array<ResponseItem<A,F,C,S>>
1676
1809
  setIdentifier(type: "entity" | "version", value: string): void;
1677
1810
  client: any;
1678
1811
  }
package/package.json CHANGED
@@ -1,17 +1,14 @@
1
1
  {
2
2
  "name": "electrodb",
3
- "version": "1.10.2",
3
+ "version": "1.11.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": {
7
- "test": "mocha ./test/offline**.spec.js",
8
- "test-ts": "mocha -r ts-node/register ./test/**.spec.ts",
9
- "test-all": "mocha ./test/**.spec.js",
10
- "test-all-local": "LOCAL_DYNAMO_ENDPOINT=http://localhost:8000 node ./test/init.js && LOCAL_DYNAMO_ENDPOINT=http://localhost:8000 mocha ./test/**.spec.js && LOCAL_DYNAMO_ENDPOINT=http://localhost:8000 npm run test-ts && npm run test-types",
7
+ "test": "npm run test-types && LOCAL_DYNAMO_ENDPOINT=http://localhost:8000 node ./test/init.js && LOCAL_DYNAMO_ENDPOINT=http://localhost:8000 mocha -r ts-node/register ./test/**.spec.*",
11
8
  "test-types": "tsd",
12
- "coverage": "nyc npm run test-all && nyc report --reporter=text-lcov | coveralls",
13
- "coverage-coveralls-local": "nyc npm run test-all-local && nyc report --reporter=text-lcov | coveralls",
14
- "coverage-html-local": "nyc npm run test-all-local && nyc report --reporter=html",
9
+ "coverage": "nyc npm test && nyc report --reporter=text-lcov | coveralls",
10
+ "coverage-coveralls-local": "nyc npm test && nyc report --reporter=text-lcov | coveralls",
11
+ "coverage-html-local": "nyc npm test && nyc report --reporter=html",
15
12
  "build:browser": "browserify playground/browser.js -o playground/bundle.js"
16
13
  },
17
14
  "repository": {
@@ -43,7 +40,7 @@
43
40
  "moment": "2.24.0",
44
41
  "nyc": "^15.1.0",
45
42
  "source-map-support": "^0.5.19",
46
- "ts-node": "^10.8.1",
43
+ "ts-node": "^10.8.2",
47
44
  "tsd": "^0.21.0",
48
45
  "typescript": "^4.7.4",
49
46
  "uuid": "7.0.1"
package/src/clauses.js CHANGED
@@ -1,4 +1,4 @@
1
- const { QueryTypes, MethodTypes, ItemOperations, ExpressionTypes, TableIndex } = require("./types");
1
+ const { QueryTypes, MethodTypes, ItemOperations, ExpressionTypes, TableIndex, TerminalOperation } = require("./types");
2
2
  const {AttributeOperationProxy, UpdateOperations, FilterOperationNames} = require("./operations");
3
3
  const {UpdateExpression} = require("./update");
4
4
  const {FilterExpression} = require("./where");
@@ -579,6 +579,7 @@ let clauses = {
579
579
  if (entity.client === undefined) {
580
580
  throw new e.ElectroError(e.ErrorCodes.NoClientDefined, "No client defined on model");
581
581
  }
582
+ options.terminalOperation = TerminalOperation.go;
582
583
  let params = clauses.params.action(entity, state, options);
583
584
  let {config} = entity._applyParameterOptions({}, state.getOptions(), options);
584
585
  return entity.go(state.getMethod(), params, config);
@@ -595,11 +596,12 @@ let clauses = {
595
596
  return Promise.reject(state.error);
596
597
  }
597
598
  try {
598
- options.page = page;
599
- options._isPagination = true;
600
599
  if (entity.client === undefined) {
601
600
  throw new e.ElectroError(e.ErrorCodes.NoClientDefined, "No client defined on model");
602
601
  }
602
+ options.page = page;
603
+ options._isPagination = true;
604
+ options.terminalOperation = TerminalOperation.page;
603
605
  let params = clauses.params.action(entity, state, options);
604
606
  let {config} = entity._applyParameterOptions({}, state.getOptions(), options);
605
607
  return entity.go(state.getMethod(), params, config);
package/src/entity.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  const { Schema } = require("./schema");
3
- const { KeyCasing, TableIndex, FormatToReturnValues, ReturnValues, EntityVersions, ItemOperations, UnprocessedTypes, Pager, ElectroInstance, KeyTypes, QueryTypes, MethodTypes, Comparisons, ExpressionTypes, ModelVersions, ElectroInstanceTypes, MaxBatchItems } = require("./types");
3
+ const { KeyCasing, TableIndex, FormatToReturnValues, ReturnValues, EntityVersions, ItemOperations, UnprocessedTypes, Pager, ElectroInstance, KeyTypes, QueryTypes, MethodTypes, Comparisons, ExpressionTypes, ModelVersions, ElectroInstanceTypes, MaxBatchItems, TerminalOperation } = require("./types");
4
4
  const { FilterFactory } = require("./filters");
5
5
  const { FilterOperations } = require("./operations");
6
6
  const { WhereFactory } = require("./where");
@@ -84,13 +84,14 @@ class Entity {
84
84
  return pkMatch;
85
85
  }
86
86
 
87
- ownsPager(pager, index) {
87
+ ownsPager(pager, index = TableIndex) {
88
88
  if (pager === null) {
89
89
  return false;
90
90
  }
91
- let tableIndex = TableIndex;
92
- let tableIndexFacets = this.model.facets.byIndex[tableIndex];
93
- let indexFacets = this.model.facets.byIndex[tableIndex];
91
+ let tableIndexFacets = this.model.facets.byIndex[TableIndex];
92
+ // todo: is the fact it doesn't use the provided index a bug?
93
+ // feels like collections may have played a roll into why this is this way
94
+ let indexFacets = this.model.facets.byIndex[TableIndex];
94
95
 
95
96
  // Unknown index
96
97
  if (tableIndexFacets === undefined || indexFacets === undefined) {
@@ -260,6 +261,7 @@ class Entity {
260
261
  }
261
262
 
262
263
  async _exec(method, params, config = {}) {
264
+ const entity = this;
263
265
  const notifyQuery = () => {
264
266
  this.eventManager.trigger({
265
267
  type: "query",
@@ -285,6 +287,7 @@ class Entity {
285
287
  return results;
286
288
  })
287
289
  .catch(err => {
290
+ notifyQuery();
288
291
  notifyResults(err, false);
289
292
  err.__isAWSError = true;
290
293
  throw err;
@@ -822,6 +825,8 @@ class Entity {
822
825
  pages: undefined,
823
826
  listeners: [],
824
827
  preserveBatchOrder: false,
828
+ attributes: [],
829
+ terminalOperation: undefined,
825
830
  };
826
831
 
827
832
  config = options.reduce((config, option) => {
@@ -834,6 +839,14 @@ class Entity {
834
839
  config.params.ReturnValues = FormatToReturnValues[format];
835
840
  }
836
841
 
842
+ if (option.terminalOperation in TerminalOperation) {
843
+ config.terminalOperation = TerminalOperation[option.terminalOperation];
844
+ }
845
+
846
+ if (Array.isArray(option.attributes)) {
847
+ config.attributes = config.attributes.concat(option.attributes);
848
+ }
849
+
837
850
  if (option.preserveBatchOrder === true) {
838
851
  config.preserveBatchOrder = true;
839
852
  }
@@ -1019,7 +1032,103 @@ class Entity {
1019
1032
  throw new Error(`Invalid method: ${method}`);
1020
1033
  }
1021
1034
  let applied = this._applyParameterOptions(params, options, config);
1022
- return this._applyParameterExpressionTypes(applied.parameters, filter);
1035
+ return this._applyParameterExpressions(method, applied.parameters, applied.config, filter);
1036
+ }
1037
+
1038
+ _applyParameterExpressions(method, parameters, config, filter) {
1039
+ if (method !== MethodTypes.get) {
1040
+ return this._applyParameterExpressionTypes(parameters, filter);
1041
+ } else {
1042
+ parameters = this._applyProjectionExpressions({parameters, config});
1043
+ return this._applyParameterExpressionTypes(parameters, filter);
1044
+ }
1045
+
1046
+ }
1047
+
1048
+ _applyProjectionExpressions({parameters = {}, config = {}} = {}) {
1049
+ const attributes = config.attributes || [];
1050
+ if (attributes.length === 0) {
1051
+ return parameters;
1052
+ }
1053
+
1054
+ const requiresRawResponse = !!config.raw;
1055
+ const enforcesOwnership = !config.ignoreOwnership;
1056
+ const requiresUserInvolvedPagination = TerminalOperation[config.terminalOperation] === TerminalOperation.page;
1057
+ const isServerBound = TerminalOperation[config.terminalOperation] === TerminalOperation.go ||
1058
+ TerminalOperation[config.terminalOperation] === TerminalOperation.page;
1059
+
1060
+ // 1. Take stock of invalid attributes, if there are any this should be considered
1061
+ // unintentional and should throw to prevent unintended results
1062
+ // 2. Convert all attribute names to their respective "field" names
1063
+ const unknownAttributes = [];
1064
+ let attributeFields = new Set();
1065
+ for (const attributeName of attributes) {
1066
+ const fieldName = this.model.schema.getFieldName(attributeName);
1067
+ if (typeof fieldName !== "string") {
1068
+ unknownAttributes.push(attributeName);
1069
+ } else {
1070
+ attributeFields.add(fieldName);
1071
+ }
1072
+ }
1073
+
1074
+ // Stop doing work, prepare error message and throw
1075
+ if (attributeFields.size === 0 || unknownAttributes.length > 0) {
1076
+ let message = 'Unknown attributes provided in query options';
1077
+ if (unknownAttributes.length) {
1078
+ message += `: ${u.commaSeparatedString(unknownAttributes)}`;
1079
+ }
1080
+ throw new e.ElectroError(e.ErrorCodes.InvalidOptions, message);
1081
+ }
1082
+
1083
+ // add ExpressionAttributeNames if it doesn't exist already
1084
+ parameters.ExpressionAttributeNames = parameters.ExpressionAttributeNames || {};
1085
+
1086
+ if (
1087
+ // The response you're returning:
1088
+ // 1. is not expected to be raw
1089
+ !requiresRawResponse
1090
+ // 2. is making a request to the server
1091
+ && isServerBound
1092
+ // 3. will expect entity identifiers down stream
1093
+ && enforcesOwnership
1094
+
1095
+ ) {
1096
+ // add entity identifiers to so items can be identified
1097
+ attributeFields.add(this.identifiers.entity);
1098
+ attributeFields.add(this.identifiers.version);
1099
+
1100
+ // if pagination is required you may enter into a scenario where
1101
+ // the LastEvaluatedKey doesn't belong to entity and one must be formed.
1102
+ // We must add the attributes necessary to make that key to not break
1103
+ // pagination. This stinks.
1104
+ if (
1105
+ requiresUserInvolvedPagination
1106
+ && config.pager !== Pager.raw
1107
+ ) {
1108
+ // LastEvaluatedKeys return the TableIndex keys and the keys for the SecondaryIndex
1109
+ let tableIndexFacets = this.model.facets.byIndex[TableIndex];
1110
+ let indexFacets = this.model.facets.byIndex[parameters.IndexName] || { all: [] };
1111
+
1112
+ for (const attribute of [...tableIndexFacets.all, ...indexFacets.all]) {
1113
+ const fieldName = this.model.schema.getFieldName(attribute.name);
1114
+ attributeFields.add(fieldName);
1115
+ }
1116
+ }
1117
+ }
1118
+
1119
+ for (const attributeField of attributeFields) {
1120
+ // prefix the ExpressionAttributeNames because some prefixes are not allowed
1121
+ parameters.ExpressionAttributeNames['#' + attributeField] = attributeField;
1122
+ }
1123
+
1124
+ // if there is already a ProjectionExpression (e.g. config "params"), merge it
1125
+ if (typeof parameters.ProjectionExpression === 'string') {
1126
+ parameters.ProjectionExpression = [parameters.ProjectionExpression, ...Object.keys([parameters.ExpressionAttributeNames])].join(', ');
1127
+ } else {
1128
+ parameters.ProjectionExpression = Object.keys(parameters.ExpressionAttributeNames).join(', ');
1129
+ }
1130
+
1131
+ return parameters;
1023
1132
  }
1024
1133
 
1025
1134
  _batchGetParams(state, config = {}) {
@@ -1411,7 +1520,7 @@ class Entity {
1411
1520
  throw new Error(`Invalid query type: ${state.query.type}`);
1412
1521
  }
1413
1522
  let applied = this._applyParameterOptions(parameters, state.query.options, options);
1414
- return applied.parameters;
1523
+ return this._applyProjectionExpressions(applied);
1415
1524
  }
1416
1525
 
1417
1526
  _makeBetweenQueryParams(index, filter, pk, ...sk) {
package/src/schema.js CHANGED
@@ -1,4 +1,4 @@
1
- const { CastTypes, ValueTypes, KeyCasing, AttributeTypes, AttributeMutationMethods, AttributeWildCard, PathTypes, TraverserIndexes } = require("./types");
1
+ const { CastTypes, ValueTypes, KeyCasing, AttributeTypes, AttributeMutationMethods, AttributeWildCard, PathTypes } = require("./types");
2
2
  const AttributeTypeNames = Object.keys(AttributeTypes);
3
3
  const ValidFacetTypes = [AttributeTypes.string, AttributeTypes.number, AttributeTypes.boolean, AttributeTypes.enum];
4
4
  const e = require("./errors");
@@ -1263,7 +1263,7 @@ class Schema {
1263
1263
  translateToFields(payload = {}) {
1264
1264
  let record = {};
1265
1265
  for (let [name, value] of Object.entries(payload)) {
1266
- let field = this.translationForTable[name];
1266
+ let field = this.getFieldName(name);
1267
1267
  if (value !== undefined) {
1268
1268
  record[field] = value;
1269
1269
  }
@@ -1271,6 +1271,12 @@ class Schema {
1271
1271
  return record;
1272
1272
  }
1273
1273
 
1274
+ getFieldName(name) {
1275
+ if (typeof name === 'string') {
1276
+ return this.translationForTable[name];
1277
+ }
1278
+ }
1279
+
1274
1280
  checkCreate(payload = {}) {
1275
1281
  let record = {};
1276
1282
  for (let attribute of Object.values(this.attributes)) {
@@ -1320,13 +1326,18 @@ class Schema {
1320
1326
  }
1321
1327
 
1322
1328
  formatItemForRetrieval(item, config) {
1329
+ let returnAttributes = new Set(config.attributes || []);
1330
+ let hasUserSpecifiedReturnAttributes = returnAttributes.size > 0;
1323
1331
  let remapped = this.translateFromFields(item, config);
1324
1332
  let data = this._fulfillAttributeMutationMethod("get", remapped);
1325
- if (this.hiddenAttributes.size > 0) {
1333
+ if (this.hiddenAttributes.size > 0 || hasUserSpecifiedReturnAttributes) {
1326
1334
  for (let attribute of Object.keys(data)) {
1327
1335
  if (this.hiddenAttributes.has(attribute)) {
1328
1336
  delete data[attribute];
1329
1337
  }
1338
+ if (hasUserSpecifiedReturnAttributes && !returnAttributes.has(attribute)) {
1339
+ delete data[attribute];
1340
+ }
1330
1341
  }
1331
1342
  }
1332
1343
  return data;
package/src/types.js CHANGED
@@ -186,6 +186,11 @@ const EventSubscriptionTypes = [
186
186
  "results"
187
187
  ];
188
188
 
189
+ const TerminalOperation = {
190
+ go: 'go',
191
+ page: 'page',
192
+ }
193
+
189
194
  module.exports = {
190
195
  Pager,
191
196
  KeyTypes,
@@ -210,6 +215,7 @@ module.exports = {
210
215
  TraverserIndexes,
211
216
  UnprocessedTypes,
212
217
  AttributeWildCard,
218
+ TerminalOperation,
213
219
  FormatToReturnValues,
214
220
  AttributeProxySymbol,
215
221
  ElectroInstanceTypes,
package/src/parse.js DELETED
@@ -1,45 +0,0 @@
1
- function getPartDetail(part = "") {
2
- let detail = {
3
- expression: "",
4
- name: "",
5
- value: "",
6
- };
7
- if (part.includes("[")) {
8
- if (!part.match(/\[\d\]/gi)) {
9
- throw new Error(`Invalid path part "${part}" has bracket containing non-numeric characters.`);
10
- }
11
- let [name] = part.match(/.*(?=\[)/gi);
12
- detail.name = `#${name}`;
13
- detail.value = name;
14
- } else {
15
- detail.name = `#${part}`;
16
- detail.value = part;
17
- }
18
- detail.expression = `#${part}`;
19
- return detail;
20
- }
21
-
22
- function parse(path = "") {
23
- if (typeof path !== "string" || !path.length) {
24
- throw new Error("Path must be a string with a non-zero length");
25
- }
26
- let parts = path.split(/\./gi);
27
- let attr = getPartDetail(parts[0]).value;
28
- let target = getPartDetail(parts[parts.length-1]);
29
- if (target.expression.includes("[")) {
30
-
31
- }
32
- let names = {};
33
- let expressions = [];
34
- for (let part of parts) {
35
- let detail = getPartDetail(part);
36
- names[detail.name] = detail.value;
37
- expressions.push(detail.expression);
38
- }
39
- return {attr, path, names, target: target.value, expression: expressions.join(".")};
40
- }
41
-
42
- module.exports = {
43
- parse,
44
- getPartDetail
45
- };