@aws-amplify/datastore 4.0.12 → 4.0.13-push-notification-dryrun.43

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 (111) hide show
  1. package/lib/datastore/datastore.d.ts +29 -3
  2. package/lib/datastore/datastore.js +308 -147
  3. package/lib/datastore/datastore.js.map +1 -1
  4. package/lib/predicates/index.d.ts +77 -7
  5. package/lib/predicates/index.js +142 -122
  6. package/lib/predicates/index.js.map +1 -1
  7. package/lib/predicates/next.d.ts +51 -10
  8. package/lib/predicates/next.js +111 -91
  9. package/lib/predicates/next.js.map +1 -1
  10. package/lib/storage/adapter/AsyncStorageAdapter.d.ts +28 -30
  11. package/lib/storage/adapter/AsyncStorageAdapter.js +135 -532
  12. package/lib/storage/adapter/AsyncStorageAdapter.js.map +1 -1
  13. package/lib/storage/adapter/AsyncStorageDatabase.js.map +1 -1
  14. package/lib/storage/adapter/IndexedDBAdapter.d.ts +28 -29
  15. package/lib/storage/adapter/IndexedDBAdapter.js +490 -885
  16. package/lib/storage/adapter/IndexedDBAdapter.js.map +1 -1
  17. package/lib/storage/adapter/StorageAdapterBase.d.ts +134 -0
  18. package/lib/storage/adapter/StorageAdapterBase.js +439 -0
  19. package/lib/storage/adapter/StorageAdapterBase.js.map +1 -0
  20. package/lib/storage/relationship.d.ts +9 -0
  21. package/lib/storage/relationship.js +9 -0
  22. package/lib/storage/relationship.js.map +1 -1
  23. package/lib/storage/storage.d.ts +1 -1
  24. package/lib/storage/storage.js +4 -3
  25. package/lib/storage/storage.js.map +1 -1
  26. package/lib/sync/index.d.ts +15 -1
  27. package/lib/sync/index.js +80 -13
  28. package/lib/sync/index.js.map +1 -1
  29. package/lib/sync/outbox.js +14 -7
  30. package/lib/sync/outbox.js.map +1 -1
  31. package/lib/sync/processors/mutation.d.ts +10 -1
  32. package/lib/sync/processors/mutation.js +33 -12
  33. package/lib/sync/processors/mutation.js.map +1 -1
  34. package/lib/sync/processors/subscription.d.ts +7 -1
  35. package/lib/sync/processors/subscription.js +196 -135
  36. package/lib/sync/processors/subscription.js.map +1 -1
  37. package/lib/sync/processors/sync.d.ts +1 -1
  38. package/lib/sync/processors/sync.js.map +1 -1
  39. package/lib/sync/utils.d.ts +66 -2
  40. package/lib/sync/utils.js +264 -16
  41. package/lib/sync/utils.js.map +1 -1
  42. package/lib/types.d.ts +9 -1
  43. package/lib/types.js.map +1 -1
  44. package/lib/util.d.ts +16 -0
  45. package/lib/util.js +31 -2
  46. package/lib/util.js.map +1 -1
  47. package/lib-esm/datastore/datastore.d.ts +29 -3
  48. package/lib-esm/datastore/datastore.js +310 -149
  49. package/lib-esm/datastore/datastore.js.map +1 -1
  50. package/lib-esm/predicates/index.d.ts +77 -7
  51. package/lib-esm/predicates/index.js +143 -123
  52. package/lib-esm/predicates/index.js.map +1 -1
  53. package/lib-esm/predicates/next.d.ts +51 -10
  54. package/lib-esm/predicates/next.js +111 -91
  55. package/lib-esm/predicates/next.js.map +1 -1
  56. package/lib-esm/storage/adapter/AsyncStorageAdapter.d.ts +28 -30
  57. package/lib-esm/storage/adapter/AsyncStorageAdapter.js +138 -535
  58. package/lib-esm/storage/adapter/AsyncStorageAdapter.js.map +1 -1
  59. package/lib-esm/storage/adapter/AsyncStorageDatabase.js.map +1 -1
  60. package/lib-esm/storage/adapter/IndexedDBAdapter.d.ts +28 -29
  61. package/lib-esm/storage/adapter/IndexedDBAdapter.js +489 -884
  62. package/lib-esm/storage/adapter/IndexedDBAdapter.js.map +1 -1
  63. package/lib-esm/storage/adapter/StorageAdapterBase.d.ts +134 -0
  64. package/lib-esm/storage/adapter/StorageAdapterBase.js +437 -0
  65. package/lib-esm/storage/adapter/StorageAdapterBase.js.map +1 -0
  66. package/lib-esm/storage/relationship.d.ts +9 -0
  67. package/lib-esm/storage/relationship.js +9 -0
  68. package/lib-esm/storage/relationship.js.map +1 -1
  69. package/lib-esm/storage/storage.d.ts +1 -1
  70. package/lib-esm/storage/storage.js +4 -3
  71. package/lib-esm/storage/storage.js.map +1 -1
  72. package/lib-esm/sync/index.d.ts +15 -1
  73. package/lib-esm/sync/index.js +82 -15
  74. package/lib-esm/sync/index.js.map +1 -1
  75. package/lib-esm/sync/outbox.js +14 -7
  76. package/lib-esm/sync/outbox.js.map +1 -1
  77. package/lib-esm/sync/processors/mutation.d.ts +10 -1
  78. package/lib-esm/sync/processors/mutation.js +33 -12
  79. package/lib-esm/sync/processors/mutation.js.map +1 -1
  80. package/lib-esm/sync/processors/subscription.d.ts +7 -1
  81. package/lib-esm/sync/processors/subscription.js +197 -136
  82. package/lib-esm/sync/processors/subscription.js.map +1 -1
  83. package/lib-esm/sync/processors/sync.d.ts +1 -1
  84. package/lib-esm/sync/processors/sync.js.map +1 -1
  85. package/lib-esm/sync/utils.d.ts +66 -2
  86. package/lib-esm/sync/utils.js +261 -18
  87. package/lib-esm/sync/utils.js.map +1 -1
  88. package/lib-esm/types.d.ts +9 -1
  89. package/lib-esm/types.js.map +1 -1
  90. package/lib-esm/util.d.ts +16 -0
  91. package/lib-esm/util.js +32 -3
  92. package/lib-esm/util.js.map +1 -1
  93. package/package.json +12 -11
  94. package/src/datastore/datastore.ts +288 -159
  95. package/src/predicates/index.ts +145 -175
  96. package/src/predicates/next.ts +114 -81
  97. package/src/storage/adapter/AsyncStorageAdapter.ts +97 -563
  98. package/src/storage/adapter/AsyncStorageDatabase.ts +2 -2
  99. package/src/storage/adapter/IndexedDBAdapter.ts +318 -770
  100. package/src/storage/adapter/StorageAdapterBase.ts +545 -0
  101. package/src/storage/relationship.ts +9 -0
  102. package/src/storage/storage.ts +12 -9
  103. package/src/sync/index.ts +108 -20
  104. package/src/sync/outbox.ts +17 -11
  105. package/src/sync/processors/mutation.ts +35 -4
  106. package/src/sync/processors/subscription.ts +124 -10
  107. package/src/sync/processors/sync.ts +4 -1
  108. package/src/sync/utils.ts +285 -15
  109. package/src/types.ts +15 -2
  110. package/src/util.ts +40 -1
  111. package/CHANGELOG.md +0 -904
package/src/sync/utils.ts CHANGED
@@ -20,6 +20,7 @@ import {
20
20
  PersistentModel,
21
21
  PersistentModelConstructor,
22
22
  PredicatesGroup,
23
+ PredicateObject,
23
24
  RelationshipType,
24
25
  SchemaModel,
25
26
  SchemaNamespace,
@@ -28,6 +29,7 @@ import {
28
29
  InternalSchema,
29
30
  AuthModeStrategy,
30
31
  ModelAttributes,
32
+ isPredicateGroup,
31
33
  } from '../types';
32
34
  import {
33
35
  extractPrimaryKeyFieldNames,
@@ -247,6 +249,7 @@ export function getAuthorizationRules(
247
249
  groupClaim = 'cognito:groups',
248
250
  allow: authStrategy = 'iam',
249
251
  groups = [],
252
+ groupsField = '',
250
253
  } = rule;
251
254
 
252
255
  const isReadAuthorized = operations.includes('read');
@@ -263,6 +266,7 @@ export function getAuthorizationRules(
263
266
  groupClaim,
264
267
  authStrategy,
265
268
  groups,
269
+ groupsField,
266
270
  areSubscriptionsPublic: false,
267
271
  };
268
272
 
@@ -300,26 +304,36 @@ export function buildSubscriptionGraphQLOperation(
300
304
  modelDefinition: SchemaModel,
301
305
  transformerMutationType: TransformerMutationType,
302
306
  isOwnerAuthorization: boolean,
303
- ownerField: string
307
+ ownerField: string,
308
+ filterArg: boolean = false
304
309
  ): [TransformerMutationType, string, string] {
305
310
  const selectionSet = generateSelectionSet(namespace, modelDefinition);
306
311
 
307
- const { name: typeName, pluralName: pluralTypeName } = modelDefinition;
312
+ const { name: typeName } = modelDefinition;
308
313
 
309
314
  const opName = `on${transformerMutationType}${typeName}`;
310
- let docArgs = '';
311
- let opArgs = '';
315
+
316
+ const docArgs: string[] = [];
317
+ const opArgs: string[] = [];
318
+
319
+ if (filterArg) {
320
+ docArgs.push(`$filter: ModelSubscription${typeName}FilterInput`);
321
+ opArgs.push('filter: $filter');
322
+ }
312
323
 
313
324
  if (isOwnerAuthorization) {
314
- docArgs = `($${ownerField}: String!)`;
315
- opArgs = `(${ownerField}: $${ownerField})`;
325
+ docArgs.push(`$${ownerField}: String!`);
326
+ opArgs.push(`${ownerField}: $${ownerField}`);
316
327
  }
317
328
 
329
+ const docStr = docArgs.length ? `(${docArgs.join(',')})` : '';
330
+ const opStr = opArgs.length ? `(${opArgs.join(',')})` : '';
331
+
318
332
  return [
319
333
  transformerMutationType,
320
334
  opName,
321
- `subscription operation${docArgs}{
322
- ${opName}${opArgs}{
335
+ `subscription operation${docStr}{
336
+ ${opName}${opStr}{
323
337
  ${selectionSet}
324
338
  }
325
339
  }`,
@@ -476,10 +490,25 @@ export function predicateToGraphQLCondition(
476
490
  const keyFields = extractPrimaryKeyFieldNames(modelDefinition);
477
491
  return predicateToGraphQLFilter(predicate, keyFields) as GraphQLCondition;
478
492
  }
479
-
493
+ /**
494
+ * @param predicatesGroup - Predicate Group
495
+ @returns GQL Filter Expression from Predicate Group
496
+
497
+ @remarks Flattens redundant list predicates
498
+ @example
499
+
500
+ ```js
501
+ { and:[{ and:[{ username: { eq: 'bob' }}] }] }
502
+ ```
503
+ Becomes
504
+ ```js
505
+ { and:[{ username: { eq: 'bob' }}] }
506
+ ```
507
+ */
480
508
  export function predicateToGraphQLFilter(
481
509
  predicatesGroup: PredicatesGroup<any>,
482
- fieldsToOmit: string[] = []
510
+ fieldsToOmit: string[] = [],
511
+ root = true
483
512
  ): GraphQLFilter {
484
513
  const result: GraphQLFilter = {};
485
514
 
@@ -492,8 +521,7 @@ export function predicateToGraphQLFilter(
492
521
 
493
522
  result[type] = isList ? [] : {};
494
523
 
495
- const appendToFilter = value =>
496
- isList ? result[type].push(value) : (result[type] = value);
524
+ const children: GraphQLFilter[] = [];
497
525
 
498
526
  predicates.forEach(predicate => {
499
527
  if (isPredicateObj(predicate)) {
@@ -505,12 +533,37 @@ export function predicateToGraphQLFilter(
505
533
  [field]: { [operator]: operand },
506
534
  };
507
535
 
508
- appendToFilter(gqlField);
536
+ children.push(gqlField);
509
537
  return;
510
538
  }
511
539
 
512
- const child = predicateToGraphQLFilter(predicate, fieldsToOmit);
513
- Object.keys(child).length > 0 && appendToFilter(child);
540
+ const child = predicateToGraphQLFilter(predicate, fieldsToOmit, false);
541
+ if (Object.keys(child).length > 0) {
542
+ children.push(child);
543
+ }
544
+ });
545
+
546
+ // flatten redundant list predicates
547
+ if (children.length === 1) {
548
+ const [child] = children;
549
+ if (
550
+ // any nested list node
551
+ (isList && !root) ||
552
+ // root list node where the only child is also a list node
553
+ (isList && root && ('and' in child || 'or' in child))
554
+ ) {
555
+ delete result[type];
556
+ Object.assign(result, child);
557
+ return result;
558
+ }
559
+ }
560
+
561
+ children.forEach(child => {
562
+ if (isList) {
563
+ result[type].push(child);
564
+ } else {
565
+ result[type] = child;
566
+ }
514
567
  });
515
568
 
516
569
  if (isList) {
@@ -522,6 +575,223 @@ export function predicateToGraphQLFilter(
522
575
  return result;
523
576
  }
524
577
 
578
+ /**
579
+ *
580
+ * @param group - selective sync predicate group
581
+ * @returns set of distinct field names in the filter group
582
+ */
583
+ export function filterFields(group?: PredicatesGroup<any>): Set<string> {
584
+ const fields = new Set<string>();
585
+
586
+ if (!group || !Array.isArray(group.predicates)) return fields;
587
+
588
+ const { predicates } = group;
589
+ const stack = [...predicates];
590
+
591
+ while (stack.length > 0) {
592
+ const current = stack.pop();
593
+ if (isPredicateObj(current)) {
594
+ fields.add(current.field as string);
595
+ } else if (isPredicateGroup(current)) {
596
+ stack.push(...current.predicates);
597
+ }
598
+ }
599
+
600
+ return fields;
601
+ }
602
+
603
+ /**
604
+ *
605
+ * @param modelDefinition
606
+ * @returns set of field names used with dynamic auth modes configured for the provided model definition
607
+ */
608
+ export function dynamicAuthFields(modelDefinition: SchemaModel): Set<string> {
609
+ const rules = getAuthorizationRules(modelDefinition);
610
+ const fields = new Set<string>();
611
+
612
+ for (const rule of rules) {
613
+ if (rule.groupsField && !rule.groups.length) {
614
+ // dynamic group rule will have no values in `rule.groups`
615
+ fields.add((rule as AuthorizationRule).groupsField);
616
+ } else if (rule.ownerField) {
617
+ fields.add(rule.ownerField);
618
+ }
619
+ }
620
+
621
+ return fields;
622
+ }
623
+
624
+ /**
625
+ *
626
+ * @param group - selective sync predicate group
627
+ * @returns the total number of OR'd predicates in the filter group
628
+ *
629
+ * @example returns 2
630
+ * ```js
631
+ * { type: "or", predicates: [
632
+ * { field: "username", operator: "beginsWith", operand: "a" },
633
+ * { field: "title", operator: "contains", operand: "abc" },
634
+ * ]}
635
+ * ```
636
+ */
637
+ export function countFilterCombinations(group?: PredicatesGroup<any>): number {
638
+ if (!group || !Array.isArray(group.predicates)) return 0;
639
+
640
+ let count = 0;
641
+ const stack: (PredicatesGroup<any> | PredicateObject<any>)[] = [group];
642
+
643
+ while (stack.length > 0) {
644
+ const current = stack.pop();
645
+
646
+ if (isPredicateGroup(current)) {
647
+ const { predicates, type } = current;
648
+ // ignore length = 1; groups with 1 predicate will get flattened when converted to gqlFilter
649
+ if (type === 'or' && predicates.length > 1) {
650
+ count += predicates.length;
651
+ }
652
+ stack.push(...predicates);
653
+ }
654
+ }
655
+
656
+ // if we didn't encounter any OR groups, default to 1
657
+ return count || 1;
658
+ }
659
+
660
+ /**
661
+ *
662
+ * @param group - selective sync predicate group
663
+ * @returns name of repeated field | null
664
+ *
665
+ * @example returns "username"
666
+ * ```js
667
+ * { type: "and", predicates: [
668
+ * { field: "username", operator: "beginsWith", operand: "a" },
669
+ * { field: "username", operator: "contains", operand: "abc" },
670
+ * ] }
671
+ * ```
672
+ */
673
+ export function repeatedFieldInGroup(
674
+ group?: PredicatesGroup<any>
675
+ ): string | null {
676
+ if (!group || !Array.isArray(group.predicates)) return null;
677
+
678
+ // convert to filter in order to flatten redundant groups
679
+ const gqlFilter = predicateToGraphQLFilter(group);
680
+
681
+ const stack: GraphQLFilter[] = [gqlFilter];
682
+
683
+ const hasGroupRepeatedFields = (fields: GraphQLFilter[]): string | null => {
684
+ const seen = {};
685
+
686
+ for (const f of fields) {
687
+ const [fieldName] = Object.keys(f);
688
+ if (seen[fieldName]) {
689
+ return fieldName;
690
+ }
691
+ seen[fieldName] = true;
692
+ }
693
+ return null;
694
+ };
695
+
696
+ while (stack.length > 0) {
697
+ const current = stack.pop();
698
+
699
+ const [key] = Object.keys(current!);
700
+ const values = current![key];
701
+
702
+ if (!Array.isArray(values)) {
703
+ return null;
704
+ }
705
+
706
+ // field value will be single object
707
+ const predicateObjects = values.filter(
708
+ v => !Array.isArray(Object.values(v)[0])
709
+ );
710
+
711
+ // group value will be an array
712
+ const predicateGroups = values.filter(v =>
713
+ Array.isArray(Object.values(v)[0])
714
+ );
715
+
716
+ if (key === 'and') {
717
+ const repeatedField = hasGroupRepeatedFields(predicateObjects);
718
+ if (repeatedField) {
719
+ return repeatedField;
720
+ }
721
+ }
722
+
723
+ stack.push(...predicateGroups);
724
+ }
725
+
726
+ return null;
727
+ }
728
+
729
+ export enum RTFError {
730
+ UnknownField,
731
+ MaxAttributes,
732
+ MaxCombinations,
733
+ RepeatedFieldname,
734
+ NotGroup,
735
+ FieldNotInType,
736
+ }
737
+
738
+ export function generateRTFRemediation(
739
+ errorType: RTFError,
740
+ modelDefinition: SchemaModel,
741
+ predicatesGroup: PredicatesGroup<any> | undefined
742
+ ): string {
743
+ const selSyncFields = filterFields(predicatesGroup);
744
+ const selSyncFieldStr = [...selSyncFields].join(', ');
745
+ const dynamicAuthModeFields = dynamicAuthFields(modelDefinition);
746
+ const dynamicAuthFieldsStr = [...dynamicAuthModeFields].join(', ');
747
+ const filterCombinations = countFilterCombinations(predicatesGroup);
748
+ const repeatedField = repeatedFieldInGroup(predicatesGroup);
749
+
750
+ switch (errorType) {
751
+ case RTFError.UnknownField:
752
+ return (
753
+ `Your API was generated with an older version of the CLI that doesn't support backend subscription filtering.` +
754
+ 'To enable backend subscription filtering, upgrade your Amplify CLI to the latest version and push your app by running `amplify upgrade` followed by `amplify push`'
755
+ );
756
+
757
+ case RTFError.MaxAttributes: {
758
+ let message = `Your selective sync expression for ${modelDefinition.name} contains ${selSyncFields.size} different model fields: ${selSyncFieldStr}.\n\n`;
759
+
760
+ if (dynamicAuthModeFields.size > 0) {
761
+ message +=
762
+ `Note: the number of fields you can use with selective sync is affected by @auth rules configured on the model.\n\n` +
763
+ `Dynamic auth modes, such as owner auth and dynamic group auth each utilize 1 field.\n` +
764
+ `You currently have ${dynamicAuthModeFields.size} dynamic auth mode(s) configured on this model: ${dynamicAuthFieldsStr}.`;
765
+ }
766
+
767
+ return message;
768
+ }
769
+
770
+ case RTFError.MaxCombinations: {
771
+ let message = `Your selective sync expression for ${modelDefinition.name} contains ${filterCombinations} field combinations (total number of predicates in an OR expression).\n\n`;
772
+
773
+ if (dynamicAuthModeFields.size > 0) {
774
+ message +=
775
+ `Note: the number of fields you can use with selective sync is affected by @auth rules configured on the model.\n\n` +
776
+ `Dynamic auth modes, such as owner auth and dynamic group auth factor in to the number of combinations you're using.\n` +
777
+ `You currently have ${dynamicAuthModeFields.size} dynamic auth mode(s) configured on this model: ${dynamicAuthFieldsStr}.`;
778
+ }
779
+ return message;
780
+ }
781
+
782
+ case RTFError.RepeatedFieldname:
783
+ return `Your selective sync expression for ${modelDefinition.name} contains multiple entries for ${repeatedField} in the same AND group.`;
784
+ case RTFError.NotGroup:
785
+ return (
786
+ `Your selective sync expression for ${modelDefinition.name} uses a \`not\` group. If you'd like to filter subscriptions in the backend, ` +
787
+ `rewrite your expression using \`ne\` or \`notContains\` operators.`
788
+ );
789
+ case RTFError.FieldNotInType:
790
+ // no remediation instructions. We'll surface the message directly
791
+ return '';
792
+ }
793
+ }
794
+
525
795
  export function getUserGroupsFromToken(
526
796
  token: { [field: string]: any },
527
797
  rule: AuthorizationRule
package/src/types.ts CHANGED
@@ -48,7 +48,17 @@ export type SchemaModel = {
48
48
  name: string;
49
49
  pluralName: string;
50
50
  attributes?: ModelAttributes;
51
+
52
+ /**
53
+ * Explicitly defined fields.
54
+ */
51
55
  fields: ModelFields;
56
+
57
+ /**
58
+ * Explicitly defined fields plus implied fields. (E.g., foreign keys.)
59
+ */
60
+ allFields?: ModelFields;
61
+
52
62
  syncable?: boolean;
53
63
  };
54
64
 
@@ -306,6 +316,7 @@ export type AuthorizationRule = {
306
316
  provider: 'userPools' | 'oidc' | 'iam' | 'apiKey';
307
317
  groupClaim: string;
308
318
  groups: [string];
319
+ groupsField: string;
309
320
  authStrategy: 'owner' | 'groups' | 'private' | 'public';
310
321
  areSubscriptionsPublic: boolean;
311
322
  };
@@ -587,9 +598,11 @@ type DeepWritable<T> = {
587
598
  -readonly [P in keyof T]: T[P] extends TypeName<T[P]>
588
599
  ? T[P]
589
600
  : T[P] extends Promise<infer InnerPromiseType>
590
- ? InnerPromiseType
601
+ ? undefined extends InnerPromiseType
602
+ ? InnerPromiseType | null
603
+ : InnerPromiseType
591
604
  : T[P] extends AsyncCollection<infer InnerCollectionType>
592
- ? InnerCollectionType[] | undefined
605
+ ? InnerCollectionType[] | undefined | null
593
606
  : DeepWritable<T[P]>;
594
607
  };
595
608
 
package/src/util.ts CHANGED
@@ -962,7 +962,13 @@ export const establishRelationAndKeys = (
962
962
 
963
963
  if (targetNames) {
964
964
  const idxName = indexNameFromKeys(targetNames);
965
- relationship[mKey].indexes.push([idxName, targetNames]);
965
+ const idxExists = relationship[mKey].indexes.find(
966
+ ([index]) => index === idxName
967
+ );
968
+
969
+ if (!idxExists) {
970
+ relationship[mKey].indexes.push([idxName, targetNames]);
971
+ }
966
972
  }
967
973
  }
968
974
  }
@@ -1098,3 +1104,36 @@ export const getIndexKeys = (
1098
1104
  };
1099
1105
 
1100
1106
  //#endregion
1107
+
1108
+ /**
1109
+ * Determine what the managed timestamp field names are for the given model definition
1110
+ * and return the mapping.
1111
+ *
1112
+ * All timestamp fields are included in the mapping, regardless of whether the final field
1113
+ * names are the defaults or customized in the `@model` directive.
1114
+ *
1115
+ * @see https://docs.amplify.aws/cli/graphql/data-modeling/#customize-creation-and-update-timestamps
1116
+ *
1117
+ * @param definition modelDefinition to inspect.
1118
+ * @returns An object mapping `createdAt` and `updatedAt` to their field names.
1119
+ */
1120
+ export const getTimestampFields = (
1121
+ definition: SchemaModel
1122
+ ): { createdAt: string; updatedAt: string } => {
1123
+ const modelAttributes = definition.attributes?.find(
1124
+ attr => attr.type === 'model'
1125
+ );
1126
+ const timestampFieldsMap = modelAttributes?.properties?.timestamps;
1127
+
1128
+ const defaultFields = {
1129
+ createdAt: 'createdAt',
1130
+ updatedAt: 'updatedAt',
1131
+ };
1132
+
1133
+ const customFields = timestampFieldsMap || {};
1134
+
1135
+ return {
1136
+ ...defaultFields,
1137
+ ...customFields,
1138
+ };
1139
+ };