@aws-amplify/data-schema 1.2.0 → 1.2.2

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.
Files changed (30) hide show
  1. package/dist/cjs/runtime/internals/APIClient.js +132 -22
  2. package/dist/cjs/runtime/internals/APIClient.js.map +1 -1
  3. package/dist/cjs/runtime/internals/operations/list.js +4 -1
  4. package/dist/cjs/runtime/internals/operations/list.js.map +1 -1
  5. package/dist/cjs/runtime/utils/index.js +5 -1
  6. package/dist/cjs/runtime/utils/index.js.map +1 -1
  7. package/dist/cjs/runtime/utils/stringTransformation.js +13 -0
  8. package/dist/cjs/runtime/utils/stringTransformation.js.map +1 -0
  9. package/dist/esm/MappedTypes/MapIndexes.d.ts +13 -3
  10. package/dist/esm/ModelType.d.ts +1 -0
  11. package/dist/esm/runtime/client/index.d.ts +57 -8
  12. package/dist/esm/runtime/internals/APIClient.mjs +129 -18
  13. package/dist/esm/runtime/internals/APIClient.mjs.map +1 -1
  14. package/dist/esm/runtime/internals/operations/list.mjs +4 -1
  15. package/dist/esm/runtime/internals/operations/list.mjs.map +1 -1
  16. package/dist/esm/runtime/utils/index.d.ts +2 -0
  17. package/dist/esm/runtime/utils/index.mjs +2 -0
  18. package/dist/esm/runtime/utils/index.mjs.map +1 -1
  19. package/dist/esm/runtime/utils/stringTransformation.d.ts +5 -0
  20. package/dist/esm/runtime/utils/stringTransformation.mjs +10 -0
  21. package/dist/esm/runtime/utils/stringTransformation.mjs.map +1 -0
  22. package/dist/meta/cjs.tsbuildinfo +1 -1
  23. package/package.json +2 -1
  24. package/src/MappedTypes/MapIndexes.ts +26 -3
  25. package/src/ModelType.ts +1 -1
  26. package/src/runtime/client/index.ts +70 -15
  27. package/src/runtime/internals/APIClient.ts +147 -27
  28. package/src/runtime/internals/operations/list.ts +6 -1
  29. package/src/runtime/utils/index.ts +2 -0
  30. package/src/runtime/utils/stringTransformation.ts +7 -0
package/src/ModelType.ts CHANGED
@@ -363,7 +363,7 @@ export function model<T extends ModelFields>(
363
363
  fields: T,
364
364
  ): ModelType<{
365
365
  fields: T;
366
- identifier: { pk: { id: string }; sk: never };
366
+ identifier: { pk: { id: string }; sk: never; compositeSk: never };
367
367
  secondaryIndexes: [];
368
368
  authorization: [];
369
369
  }> {
@@ -449,7 +449,7 @@ type SizeFilter = {
449
449
  /**
450
450
  * Filters options that can be used on string-like fields.
451
451
  */
452
- type StringFilter<T extends string = string> = {
452
+ export type StringFilter<T extends string = string> = {
453
453
  attributeExists?: boolean;
454
454
  beginsWith?: string;
455
455
  between?: [string, string];
@@ -464,7 +464,7 @@ type StringFilter<T extends string = string> = {
464
464
  size?: SizeFilter;
465
465
  };
466
466
 
467
- type NumericFilter = {
467
+ export type NumericFilter = {
468
468
  attributeExists?: boolean;
469
469
  between?: [number, number];
470
470
  eq?: number;
@@ -481,12 +481,59 @@ type BooleanFilters = {
481
481
  ne?: boolean;
482
482
  };
483
483
 
484
+ /**
485
+ * A composite SK (in an identifier or secondaryIndex) resolves to this type for
486
+ * list queries and index queries
487
+ *
488
+ * @example
489
+ * Given
490
+ * ```ts
491
+ * MyModel: a
492
+ .model({
493
+ pk: a.string().required(),
494
+ sk1: a.string().required(),
495
+ sk2: a.integer().required(),
496
+ })
497
+ .identifier(['pk', 'sk1', 'sk2']),
498
+ * ```
499
+ * Expected list options:
500
+ * ```ts
501
+ * {
502
+ * pk?: string
503
+ * sk1Sk2?: ModelPrimaryCompositeKeyConditionInput
504
+ * }
505
+ * ```
506
+ * Where ModelPrimaryCompositeKeyConditionInput resolves to:
507
+ * ```ts
508
+ * {
509
+ * eq: {sk1: string; sk2: number};
510
+ * le: {sk1: string; sk2: number};
511
+ * lt: {sk1: string; sk2: number};
512
+ * ge: {sk1: string; sk2: number};
513
+ * gt: {sk1: string; sk2: number};
514
+ * between: [ {sk1: string; sk2: number} ];
515
+ * beginsWith: {sk1: string; sk2: number};
516
+ * }
517
+ * ```
518
+ * */
519
+ export type ModelPrimaryCompositeKeyInput<
520
+ SkIr extends Record<string, string | number>,
521
+ > = {
522
+ eq?: SkIr;
523
+ le?: SkIr;
524
+ lt?: SkIr;
525
+ ge?: SkIr;
526
+ gt?: SkIr;
527
+ between?: [SkIr];
528
+ beginsWith?: SkIr;
529
+ };
530
+
484
531
  type ModelFilter<Model extends Record<any, any>> = LogicalFilters<Model> & {
485
532
  [K in keyof Model as Model[K] extends LazyLoader<any, any>
486
533
  ? never
487
- : K]?: Model[K] extends boolean
534
+ : K]?: boolean extends Model[K]
488
535
  ? BooleanFilters
489
- : Model[K] extends number
536
+ : number extends Model[K]
490
537
  ? NumericFilter
491
538
  : StringFilter;
492
539
  };
@@ -498,7 +545,6 @@ type ModelMetaShape = {
498
545
  identifier: PrimaryIndexIrShape;
499
546
  };
500
547
 
501
- // TODO: remove export. added for debugging.
502
548
  export type ModelTypesClient<
503
549
  ModelName extends string,
504
550
  Model extends Record<string, unknown>,
@@ -890,6 +936,7 @@ export type CustomHeaders =
890
936
  export interface PrimaryIndexIrShape {
891
937
  pk: { [key: string]: string | number };
892
938
  sk: { [key: string]: string | number } | never;
939
+ compositeSk: never | string;
893
940
  }
894
941
 
895
942
  /**
@@ -900,7 +947,7 @@ export interface SecondaryIndexIrShape extends PrimaryIndexIrShape {
900
947
  queryField: string;
901
948
  }
902
949
 
903
- type IndexQueryMethodsFromIR<
950
+ export type IndexQueryMethodsFromIR<
904
951
  SecondaryIdxTuple extends SecondaryIndexIrShape[],
905
952
  ModelName extends string,
906
953
  Model extends Record<string, unknown>,
@@ -919,7 +966,7 @@ type IndexQueryMethodsFromIR<
919
966
  >
920
967
  : Res;
921
968
 
922
- type ListPkOptions<
969
+ export type ListPkOptions<
923
970
  ModelMeta extends ModelMetaShape,
924
971
  Enums extends Record<string, string>,
925
972
  > = ModelMeta['identifier']['sk'] extends never
@@ -933,20 +980,28 @@ type ListPkOptions<
933
980
  /**
934
981
  * Accepts a PrimaryIndexIr or SecondaryIndexIr and returns resolved parameters
935
982
  */
936
- type IndexQueryInput<
983
+ export type IndexQueryInput<
937
984
  Idx extends PrimaryIndexIrShape,
938
985
  Enums extends Record<string, string>,
939
986
  > = {
940
987
  [PKField in keyof Idx['pk']]: Idx['pk'][PKField] extends `${deferredRefResolvingPrefix}${infer R}`
941
988
  ? Enums[R]
942
989
  : Idx['pk'][PKField];
943
- } & {
944
- [SKField in keyof Idx['sk']]+?: number extends Idx['sk'][SKField]
945
- ? NumericFilter
946
- : Idx['sk'][SKField] extends `${deferredRefResolvingPrefix}${infer R}`
947
- ? StringFilter<Enums[R]>
948
- : StringFilter<Idx['sk'][SKField] & string>;
949
- };
990
+ } & (Idx['compositeSk'] extends never
991
+ ? {
992
+ [SKField in keyof Idx['sk']]+?: number extends Idx['sk'][SKField]
993
+ ? NumericFilter
994
+ : Idx['sk'][SKField] extends `${deferredRefResolvingPrefix}${infer R}`
995
+ ? StringFilter<Enums[R]>
996
+ : StringFilter<Idx['sk'][SKField] & string>;
997
+ }
998
+ : {
999
+ [CompositeSk in Idx['compositeSk']]+?: ModelPrimaryCompositeKeyInput<{
1000
+ [SKField in keyof Idx['sk']]: Idx['sk'][SKField] extends `${deferredRefResolvingPrefix}${infer _R}`
1001
+ ? string
1002
+ : Idx['sk'][SKField];
1003
+ }>;
1004
+ });
950
1005
 
951
1006
  type IndexQueryMethodSignature<
952
1007
  Idx extends SecondaryIndexIrShape,
@@ -20,7 +20,7 @@ import {
20
20
  } from '../bridge-types';
21
21
 
22
22
  import { CustomHeaders } from '../client';
23
- import { resolveOwnerFields } from '../utils/resolveOwnerFields';
23
+ import { resolveOwnerFields, capitalize } from '../utils';
24
24
 
25
25
  import type { IndexMeta } from './operations/indexQuery';
26
26
 
@@ -38,6 +38,39 @@ const connectionType = {
38
38
  BELONGS_TO: 'BELONGS_TO',
39
39
  };
40
40
 
41
+ // When generating an SK's KeyConditionInput name, string-like types map to String
42
+ const skGraphQlFieldTypeMap = {
43
+ ID: 'ID',
44
+ String: 'String',
45
+ AWSDate: 'String',
46
+ AWSTime: 'String',
47
+ AWSDateTime: 'String',
48
+ AWSTimestamp: 'String',
49
+ AWSEmail: 'String',
50
+ AWSPhone: 'String',
51
+ AWSURL: 'String',
52
+ AWSIPAddress: 'String',
53
+ AWSJSON: 'String',
54
+ Boolean: 'Boolean',
55
+ Int: 'Int',
56
+ Float: 'Float',
57
+ };
58
+
59
+ // move to util
60
+ const resolvedSkName = (sk: string[]): string => {
61
+ if (sk.length === 1) {
62
+ return sk[0];
63
+ } else {
64
+ return sk.reduce((acc, curr, idx) => {
65
+ if (idx === 0) {
66
+ return curr;
67
+ } else {
68
+ return acc + capitalize(curr);
69
+ }
70
+ }, '');
71
+ }
72
+ };
73
+
41
74
  /**
42
75
  *
43
76
  * @param GraphQL response object
@@ -676,12 +709,14 @@ export function generateGraphQLDocument(
676
709
  primaryKeyFieldName,
677
710
  sortKeyFieldNames,
678
711
  },
712
+ attributes,
679
713
  } = modelDefinition;
680
714
 
681
715
  // Use pascal case of the model name to generate the operations and the arguments.
682
716
  // This is required to be in sync with the resources generated by the GraphQL transformers.
683
717
  const namePascalCase = name.charAt(0).toUpperCase() + name.slice(1);
684
- const pluralNamePascalCase = pluralName.charAt(0).toUpperCase() + pluralName.slice(1);
718
+ const pluralNamePascalCase =
719
+ pluralName.charAt(0).toUpperCase() + pluralName.slice(1);
685
720
 
686
721
  const { operationPrefix, usePlural } = graphQLOperationsInfo[modelOperation];
687
722
 
@@ -696,17 +731,45 @@ export function generateGraphQLDocument(
696
731
  const { queryField, pk, sk = [] } = indexMeta;
697
732
  graphQLFieldName = queryField;
698
733
 
699
- const skQueryArgs = sk.reduce((acc: Record<string, any>, fieldName) => {
700
- const fieldType = Object.prototype.hasOwnProperty.call(
701
- fields[fieldName].type,
702
- 'enum',
703
- )
704
- ? 'String' // AppSync schema sets `ModelStringKeyConditionInput` as the type of the enum field that's used as SK
705
- : fields[fieldName].type;
706
- acc[fieldName] = `Model${fieldType}KeyConditionInput`;
734
+ /**
735
+ * **a. Single field SK** -> single arg where name is the field name and the type is `Model${gqlFieldType}KeyConditionInput` (nullable)
736
+ * Note: string-like data types e.g., AWSDateTime, AWSEmail, AWSPhone, etc. should map to String. See `skGraphQlFieldTypeMap` above
737
+ * @example
738
+ * ```
739
+ * sk1: ModelStringKeyConditionInput
740
+ * ```
741
+ *
742
+ * **b. Composite SK** -> single arg where the name is camelCase concatenation of all the field names that comprise the SK
743
+ * and the type is `Model${modelName}${keyAttributeName}CompositeKeyConditionInput` (nullable)
744
+ * @example
745
+ * ```
746
+ * sk1Sk2: ModelMyModelMyModelByPkAndSk1AndSk2CompositeKeyConditionInput
747
+ */
748
+ let skQueryArgs = {};
749
+
750
+ if (sk.length === 1) {
751
+ const [skField] = sk;
752
+ const type = (
753
+ typeof fields[skField].type === 'string'
754
+ ? fields[skField].type
755
+ : 'String'
756
+ ) as keyof typeof skGraphQlFieldTypeMap;
757
+ const normalizedType = skGraphQlFieldTypeMap[type];
758
+
759
+ skQueryArgs = {
760
+ [skField]: `Model${normalizedType}KeyConditionInput`,
761
+ };
762
+ } else if (sk.length > 1) {
763
+ const compositeSkArgName = resolvedSkName(sk);
764
+
765
+ const keyName = attributes?.find(
766
+ (attr) => attr?.properties?.queryField === queryField,
767
+ )?.properties?.name;
707
768
 
708
- return acc;
709
- }, {});
769
+ skQueryArgs = {
770
+ [compositeSkArgName]: `Model${capitalize(modelName)}${capitalize(keyName)}CompositeKeyConditionInput`,
771
+ };
772
+ }
710
773
 
711
774
  indexQueryArgs = {
712
775
  [pk]: `${
@@ -739,21 +802,69 @@ export function generateGraphQLDocument(
739
802
  };
740
803
  const listPkArgs = {};
741
804
 
805
+ /**
806
+ * Generate query field args for the SK if it's defined
807
+ *
808
+ * **1. Get queries** require each SK field to be present as a separate arg where the type is the field's GraphQL scalar type (non-nullable)
809
+ * @example
810
+ * ```
811
+ * sk1: String!, sk2: Int!
812
+ * ```
813
+ *
814
+ * **2. List queries**
815
+ *
816
+ * **a. Single field SK** -> single arg where name is the field name and the type is `Model${gqlFieldType}KeyConditionInput` (nullable)
817
+ * Note: string-like data types e.g., AWSDateTime, AWSEmail, AWSPhone, etc. should map to String. See `skGraphQlFieldTypeMap` above
818
+ * @example
819
+ * ```
820
+ * sk1: ModelStringKeyConditionInput
821
+ * ```
822
+ *
823
+ * **b. Composite SK** -> single arg where the name is camelCase concatenation of all the field names that comprise the SK
824
+ * and the type is `Model${modelName}PrimaryCompositeKeyConditionInput` (nullable)
825
+ * @example
826
+ * ```
827
+ * sk1Sk2: ModelMyModelPrimaryCompositeKeyConditionInput
828
+ * ```
829
+ */
742
830
  const generateSkArgs = (op: 'get' | 'list') => {
743
- return sortKeyFieldNames.reduce(
744
- (acc: Record<string, any>, fieldName: string) => {
745
- const fieldType = fields[fieldName].type;
746
-
747
- if (op === 'get') {
748
- acc[fieldName] = `${fieldType}!`;
749
- } else if (op === 'list') {
750
- acc[fieldName] = `Model${fieldType}KeyConditionInput`;
751
- }
831
+ if (sortKeyFieldNames.length === 0) return {};
752
832
 
753
- return acc;
754
- },
755
- {},
756
- );
833
+ if (op === 'get') {
834
+ return sortKeyFieldNames.reduce(
835
+ (acc: Record<string, any>, fieldName: string) => {
836
+ const fieldType = fields[fieldName].type;
837
+
838
+ if (op === 'get') {
839
+ acc[fieldName] = `${fieldType}!`; // ! - SK args are non-nullable in Get queries
840
+ }
841
+
842
+ return acc;
843
+ },
844
+ {},
845
+ );
846
+ } else {
847
+ // list SK
848
+ if (sortKeyFieldNames.length === 1) {
849
+ // Single SK
850
+ const [sk] = sortKeyFieldNames;
851
+ const type = (
852
+ typeof fields[sk].type === 'string' ? fields[sk].type : 'String'
853
+ ) as keyof typeof skGraphQlFieldTypeMap;
854
+ const normalizedType = skGraphQlFieldTypeMap[type];
855
+
856
+ return {
857
+ [sk]: `Model${normalizedType}KeyConditionInput`,
858
+ };
859
+ } else {
860
+ // Composite SK
861
+ const compositeSkArgName = resolvedSkName(sortKeyFieldNames);
862
+
863
+ return {
864
+ [compositeSkArgName]: `Model${capitalize(modelName)}PrimaryCompositeKeyConditionInput`,
865
+ };
866
+ }
867
+ }
757
868
  };
758
869
 
759
870
  if (isCustomPrimaryKey) {
@@ -873,6 +984,8 @@ export function buildGraphQLVariables(
873
984
  },
874
985
  } = modelDefinition;
875
986
 
987
+ const skName = sortKeyFieldNames?.length && resolvedSkName(sortKeyFieldNames);
988
+
876
989
  let variables: Record<string, any> = {};
877
990
 
878
991
  // TODO: process input
@@ -930,8 +1043,13 @@ export function buildGraphQLVariables(
930
1043
  }
931
1044
  if (arg?.sortDirection) {
932
1045
  variables.sortDirection = arg.sortDirection;
1046
+ }
1047
+ if (arg && arg[primaryKeyFieldName]) {
933
1048
  variables[primaryKeyFieldName] = arg[primaryKeyFieldName];
934
1049
  }
1050
+ if (skName && arg && arg[skName]) {
1051
+ variables[skName] = arg[skName];
1052
+ }
935
1053
  if (arg?.nextToken) {
936
1054
  variables.nextToken = arg.nextToken;
937
1055
  }
@@ -942,10 +1060,12 @@ export function buildGraphQLVariables(
942
1060
  case 'INDEX_QUERY': {
943
1061
  const { pk, sk = [] } = indexMeta!;
944
1062
 
1063
+ const indexQuerySkName = sk?.length && resolvedSkName(sk);
1064
+
945
1065
  variables[pk] = arg![pk];
946
1066
 
947
- for (const skField of sk) {
948
- variables[skField] = arg![skField];
1067
+ if (indexQuerySkName && arg && arg[indexQuerySkName]) {
1068
+ variables[indexQuerySkName] = arg[indexQuerySkName];
949
1069
  }
950
1070
 
951
1071
  if (arg?.filter) {
@@ -144,7 +144,12 @@ async function _list(
144
144
  const { data, errors } = error;
145
145
 
146
146
  // `data` is not `null`, and is not an empty object:
147
- if (data !== undefined && Object.keys(data).length !== 0 && errors) {
147
+ if (
148
+ data !== undefined &&
149
+ data !== null &&
150
+ Object.keys(data).length !== 0 &&
151
+ errors
152
+ ) {
148
153
  const [key] = Object.keys(data);
149
154
 
150
155
  if (data[key]?.items) {
@@ -3,3 +3,5 @@
3
3
 
4
4
  export { resolvePKFields } from './resolvePKFields';
5
5
  export { findIndexByFields } from './findIndexByFields';
6
+ export { resolveOwnerFields } from './resolveOwnerFields';
7
+ export { capitalize } from './stringTransformation';
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @param s string to capitalize
3
+ * @returns capitalized string
4
+ */
5
+ export function capitalize<T extends string>(s: T): Capitalize<T> {
6
+ return `${s[0].toUpperCase()}${s.slice(1)}` as Capitalize<T>;
7
+ }