@aws-amplify/data-schema 1.23.0 → 1.25.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.
@@ -1,7 +1,7 @@
1
1
  import type { deferredRefResolvingPrefix, ModelTypeParamShape, ModelDefaultIdentifier, DisableOperationsOptions } from '../../ModelType';
2
2
  import type { ClientSchemaProperty } from './ClientSchemaProperty';
3
3
  import type { Authorization, ImpliedAuthFields } from '../../Authorization';
4
- import type { SchemaMetadata, ResolveFields } from '../utilities';
4
+ import type { SchemaMetadata, ResolveFields, FlatResolveFields } from '../utilities';
5
5
  import type { IsEmptyStringOrNever, UnionToIntersection, Equal, Prettify } from '@aws-amplify/data-schema-types';
6
6
  import type { ModelField } from '../../ModelField';
7
7
  import type { ModelRelationshipField } from '../../ModelRelationshipField';
@@ -20,6 +20,8 @@ export interface ClientModel<Bag extends Record<string, unknown>, Metadata exten
20
20
  secondaryIndexes: IndexQueryMethodsFromIR<Bag, T['secondaryIndexes'], K>;
21
21
  __meta: {
22
22
  listOptionsPkParams: ListOptionsPkParams<Bag, T>;
23
+ rawType: T;
24
+ flatModel: FlatClientFields<Bag, Metadata, false, T, K>;
23
25
  disabledOperations: DisabledOpsToMap<T['disabledOperations']>;
24
26
  };
25
27
  }
@@ -42,6 +44,7 @@ type DisabledOpsToMap<Ops extends ReadonlyArray<DisableOperationsOptions>> = {
42
44
  [Op in Ops[number] as Op extends 'queries' ? 'list' | 'get' | 'observeQuery' : Op extends 'mutations' ? 'create' | 'update' | 'delete' : Op extends 'subscriptions' ? 'onCreate' | 'onUpdate' | 'onDelete' | 'observeQuery' : Op extends 'list' ? 'list' | 'observeQuery' : Op]: true;
43
45
  };
44
46
  type ClientFields<Bag extends Record<string, unknown>, Metadata extends SchemaMetadata<any>, IsRDS extends boolean, T extends ModelTypeParamShape> = ResolveFields<Bag, T['fields']> & If<Not<IsRDS>, ImplicitIdentifier<T>> & AuthFields<Metadata, T> & Omit<SystemFields<IsRDS>, keyof ResolveFields<Bag, T['fields']>>;
47
+ type FlatClientFields<Bag extends Record<string, unknown>, Metadata extends SchemaMetadata<any>, IsRDS extends boolean, T extends ModelTypeParamShape, ModelName extends keyof Bag & string> = FlatResolveFields<Bag, T['fields'], ModelName> & If<Not<IsRDS>, ImplicitIdentifier<T>> & AuthFields<Metadata, T> & Omit<SystemFields<IsRDS>, keyof ResolveFields<Bag, T['fields']>>;
45
48
  type SystemFields<IsRDS extends boolean> = IsRDS extends false ? {
46
49
  readonly createdAt: string;
47
50
  readonly updatedAt: string;
@@ -5,6 +5,8 @@ import { CustomType } from '../../CustomType';
5
5
  import { RefType, RefTypeParamShape } from '../../RefType';
6
6
  import { ResolveRef } from './ResolveRef';
7
7
  import { LazyLoader } from '../../runtime';
8
+ import type { ModelTypeParamShape } from '../../ModelType';
9
+ type ExtendsNever<T> = [T] extends [never] ? true : false;
8
10
  /**
9
11
  * Takes a `ReturnType<typeof a.model()>` and turns it into a client-consumable type. Fields
10
12
  * definitions (e.g., `a.string()`) are turned into the client facing types (e.g., `string`),
@@ -19,14 +21,42 @@ export type ResolveFields<Bag extends Record<string, any>, T> = ShallowPretty<{
19
21
  } & {
20
22
  [K in keyof T as IsRequired<T[K]> extends true ? never : K]+?: ResolveIndividualField<Bag, T[K]>;
21
23
  }>;
24
+ export type FlatResolveFields<Bag extends Record<string, any>, T, FlatModelName extends keyof Bag & string = never> = ShallowPretty<{
25
+ [K in keyof T]: ResolveIndividualField<Bag, T[K], FlatModelName>;
26
+ }>;
22
27
  type ShallowPretty<T> = {
23
28
  [K in keyof T]: T[K];
24
29
  };
25
- export type ResolveIndividualField<Bag extends Record<string, any>, T> = T extends BaseModelField<infer FieldShape> ? FieldShape : T extends RefType<infer RefShape, any, any> ? ResolveRef<RefShape, Bag> : T extends ModelRelationshipField<infer RelationshipShape, any, any, any> ? ResolveRelationship<Bag, RelationshipShape> : T extends CustomType<infer CT> ? ResolveFields<Bag, CT['fields']> | null : T extends EnumType<infer values> ? values[number] | null : never;
30
+ export type ResolveIndividualField<Bag extends Record<string, any>, T, FlatModelName extends keyof Bag & string = never> = T extends BaseModelField<infer FieldShape> ? FieldShape : T extends RefType<infer RefShape, any, any> ? ResolveRef<RefShape, Bag> : T extends ModelRelationshipField<infer RelationshipShape, any, any, any> ? ResolveRelationship<Bag, RelationshipShape, FlatModelName> : T extends CustomType<infer CT> ? ResolveFields<Bag, CT['fields']> | null : T extends EnumType<infer values> ? values[number] | null : never;
26
31
  /**
27
- * Resolves to never if the related model has disabled list or get ops for hasOne/hasMany or belongsTo respectively
32
+ * This mapped type eliminates redundant recursive types when
33
+ * generating the ['__meta']['flatModel'] type that serves as the
34
+ * basis for custom selection set path type generation
35
+ *
36
+ * It drops belongsTo relational fields that match the source model
37
+ *
38
+ * For example, assuming the typical Post->Comment bi-directional hasMany relationship,
39
+ * The generated structure will be
40
+ * {
41
+ * id: string;
42
+ * title: string;
43
+ * createdAt: string;
44
+ * updatedAt: string;
45
+ * comments: {
46
+ * id: string;
47
+ * createdAt: string;
48
+ * updatedAt: string;
49
+ * content: string;
50
+ * postId: string;
51
+ * ~~post~~ is dropped because data would be the same as top level object
52
+ * }[]
53
+ * }
54
+ *
28
55
  */
29
- type ResolveRelationship<Bag extends Record<string, any>, RelationshipShape extends ModelRelationshipFieldParamShape> = DependentLazyLoaderOpIsAvailable<Bag, RelationshipShape> extends true ? LazyLoader<RelationshipShape['valueRequired'] extends true ? Bag[RelationshipShape['relatedModel']]['type'] : Bag[RelationshipShape['relatedModel']]['type'] | null, RelationshipShape['array']> : never;
56
+ type ShortCircuitBiDirectionalRelationship<Model extends Record<string, any>, ParentModelName extends string, Raw extends ModelTypeParamShape['fields']> = {
57
+ [Field in keyof Model as Field extends keyof Raw ? Raw[Field] extends ModelRelationshipField<infer RelationshipShape, any, any, any> ? RelationshipShape['relationshipType'] extends 'belongsTo' ? RelationshipShape['relatedModel'] extends ParentModelName ? never : Field : Field : Field : Field]: Model[Field];
58
+ };
59
+ type ResolveRelationship<Bag extends Record<string, any>, RelationshipShape extends ModelRelationshipFieldParamShape, ParentModelName extends keyof Bag & string = never> = ExtendsNever<ParentModelName> extends true ? DependentLazyLoaderOpIsAvailable<Bag, RelationshipShape> extends true ? LazyLoader<RelationshipShape['valueRequired'] extends true ? Bag[RelationshipShape['relatedModel']]['type'] : Bag[RelationshipShape['relatedModel']]['type'] | null, RelationshipShape['array']> : never : RelationshipShape['array'] extends true ? Array<ShortCircuitBiDirectionalRelationship<Bag[RelationshipShape['relatedModel']]['__meta']['flatModel'], ParentModelName, Bag[RelationshipShape['relatedModel']]['__meta']['rawType']['fields']>> : ShortCircuitBiDirectionalRelationship<Bag[RelationshipShape['relatedModel']]['__meta']['flatModel'], ParentModelName, Bag[RelationshipShape['relatedModel']]['__meta']['rawType']['fields']>;
30
60
  type DependentLazyLoaderOpIsAvailable<Bag extends Record<string, any>, RelationshipShape extends ModelRelationshipFieldParamShape> = RelationshipShape['relationshipType'] extends 'hasOne' | 'hasMany' ? 'list' extends keyof Bag[RelationshipShape['relatedModel']]['__meta']['disabledOperations'] ? false : true : 'get' extends keyof Bag[RelationshipShape['relatedModel']]['__meta']['disabledOperations'] ? false : true;
31
61
  type IsRequired<T> = T extends BaseModelField<infer FieldShape> ? null extends FieldShape ? false : true : T extends RefType<infer RefShape, any, any> ? IsRefRequired<RefShape> : T extends ModelRelationshipField<any, any, any, any> ? true : T extends CustomType<any> | EnumType<any> ? false : never;
32
62
  type IsRefRequired<T extends RefTypeParamShape> = T['array'] extends true ? T['arrayRequired'] : T['valueRequired'];
@@ -1,10 +1,24 @@
1
1
  import { Brand } from './util';
2
2
  declare const brandName = "modelIndexType";
3
+ /**
4
+ * DynamoDB Global Secondary Index (GSI) projection types.
5
+ * - `KEYS_ONLY`: Only the index and primary keys are projected into the index.
6
+ * - `INCLUDE`: In addition to the attributes in KEYS_ONLY, includes specified non-key attributes.
7
+ * - `ALL`: All attributes from the base table are projected into the index.
8
+ */
9
+ export type GSIProjectionType = 'KEYS_ONLY' | 'INCLUDE' | 'ALL';
10
+ /**
11
+ * Configuration data for a model's secondary index.
12
+ */
3
13
  export type ModelIndexData = {
4
14
  partitionKey: string;
5
15
  sortKeys: readonly unknown[];
6
16
  indexName: string;
7
17
  queryField: string | null;
18
+ /** The projection type for the GSI. Defaults to 'ALL' if not specified. */
19
+ projectionType?: GSIProjectionType;
20
+ /** Non-key attributes to include when projectionType is 'INCLUDE'. */
21
+ nonKeyAttributes?: readonly string[];
8
22
  };
9
23
  export type InternalModelIndexType = ModelIndexType<any, any, any, any> & {
10
24
  data: ModelIndexData;
@@ -13,6 +27,7 @@ export type ModelIndexType<ModelFieldKeys extends string, PK, SK = readonly [],
13
27
  sortKeys<FieldKeys extends ModelFieldKeys = ModelFieldKeys, const SK extends ReadonlyArray<Exclude<FieldKeys, PK>> = readonly []>(sortKeys: SK): ModelIndexType<FieldKeys, PK, SK, QueryField, K | 'sortKeys'>;
14
28
  name(name: string): ModelIndexType<ModelFieldKeys, PK, SK, QueryField, K | 'name'>;
15
29
  queryField<QF extends string | null = never, MF extends ModelFieldKeys = ModelFieldKeys>(field: QF): ModelIndexType<MF, PK, SK, QF, K | 'queryField'>;
30
+ projection<PT extends GSIProjectionType, NKA extends PT extends 'INCLUDE' ? readonly string[] : never = never>(type: PT, ...args: PT extends 'INCLUDE' ? [nonKeyAttributes: NKA] : []): ModelIndexType<ModelFieldKeys, PK, SK, QueryField, K | 'projection'>;
16
31
  }, K> & Brand<typeof brandName>;
17
32
  export declare function modelIndex<ModelFieldKeys extends string, PK extends ModelFieldKeys, SK = readonly [], QueryField = never>(partitionKeyFieldName: PK): ModelIndexType<ModelFieldKeys, PK, SK, QueryField, never>;
18
33
  export {};
@@ -7,6 +7,8 @@ function _modelIndex(partitionKeyFieldName) {
7
7
  sortKeys: [],
8
8
  indexName: '',
9
9
  queryField: '',
10
+ projectionType: 'ALL',
11
+ nonKeyAttributes: undefined,
10
12
  };
11
13
  const builder = {
12
14
  sortKeys(sortKeys) {
@@ -21,6 +23,13 @@ function _modelIndex(partitionKeyFieldName) {
21
23
  data.queryField = field;
22
24
  return this;
23
25
  },
26
+ projection(type, ...args) {
27
+ data.projectionType = type;
28
+ if (type === 'INCLUDE') {
29
+ data.nonKeyAttributes = args[0];
30
+ }
31
+ return this;
32
+ },
24
33
  ...brand(brandName),
25
34
  };
26
35
  return { ...builder, data };
@@ -1 +1 @@
1
- {"version":3,"file":"ModelIndex.mjs","sources":["../../src/ModelIndex.ts"],"sourcesContent":["import { brand } from './util';\nconst brandName = 'modelIndexType';\nfunction _modelIndex(partitionKeyFieldName) {\n const data = {\n partitionKey: partitionKeyFieldName,\n sortKeys: [],\n indexName: '',\n queryField: '',\n };\n const builder = {\n sortKeys(sortKeys) {\n data.sortKeys = sortKeys;\n return this;\n },\n name(name) {\n data.indexName = name;\n return this;\n },\n queryField(field) {\n data.queryField = field;\n return this;\n },\n ...brand(brandName),\n };\n return { ...builder, data };\n}\nexport function modelIndex(partitionKeyFieldName) {\n return _modelIndex(partitionKeyFieldName);\n}\n"],"names":[],"mappings":";;AACA,MAAM,SAAS,GAAG,gBAAgB;AAClC,SAAS,WAAW,CAAC,qBAAqB,EAAE;AAC5C,IAAI,MAAM,IAAI,GAAG;AACjB,QAAQ,YAAY,EAAE,qBAAqB;AAC3C,QAAQ,QAAQ,EAAE,EAAE;AACpB,QAAQ,SAAS,EAAE,EAAE;AACrB,QAAQ,UAAU,EAAE,EAAE;AACtB,KAAK;AACL,IAAI,MAAM,OAAO,GAAG;AACpB,QAAQ,QAAQ,CAAC,QAAQ,EAAE;AAC3B,YAAY,IAAI,CAAC,QAAQ,GAAG,QAAQ;AACpC,YAAY,OAAO,IAAI;AACvB,SAAS;AACT,QAAQ,IAAI,CAAC,IAAI,EAAE;AACnB,YAAY,IAAI,CAAC,SAAS,GAAG,IAAI;AACjC,YAAY,OAAO,IAAI;AACvB,SAAS;AACT,QAAQ,UAAU,CAAC,KAAK,EAAE;AAC1B,YAAY,IAAI,CAAC,UAAU,GAAG,KAAK;AACnC,YAAY,OAAO,IAAI;AACvB,SAAS;AACT,QAAQ,GAAG,KAAK,CAAC,SAAS,CAAC;AAC3B,KAAK;AACL,IAAI,OAAO,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE;AAC/B;AACO,SAAS,UAAU,CAAC,qBAAqB,EAAE;AAClD,IAAI,OAAO,WAAW,CAAC,qBAAqB,CAAC;AAC7C;;;;"}
1
+ {"version":3,"file":"ModelIndex.mjs","sources":["../../src/ModelIndex.ts"],"sourcesContent":["import { brand } from './util';\nconst brandName = 'modelIndexType';\nfunction _modelIndex(partitionKeyFieldName) {\n const data = {\n partitionKey: partitionKeyFieldName,\n sortKeys: [],\n indexName: '',\n queryField: '',\n projectionType: 'ALL',\n nonKeyAttributes: undefined,\n };\n const builder = {\n sortKeys(sortKeys) {\n data.sortKeys = sortKeys;\n return this;\n },\n name(name) {\n data.indexName = name;\n return this;\n },\n queryField(field) {\n data.queryField = field;\n return this;\n },\n projection(type, ...args) {\n data.projectionType = type;\n if (type === 'INCLUDE') {\n data.nonKeyAttributes = args[0];\n }\n return this;\n },\n ...brand(brandName),\n };\n return { ...builder, data };\n}\nexport function modelIndex(partitionKeyFieldName) {\n return _modelIndex(partitionKeyFieldName);\n}\n"],"names":[],"mappings":";;AACA,MAAM,SAAS,GAAG,gBAAgB;AAClC,SAAS,WAAW,CAAC,qBAAqB,EAAE;AAC5C,IAAI,MAAM,IAAI,GAAG;AACjB,QAAQ,YAAY,EAAE,qBAAqB;AAC3C,QAAQ,QAAQ,EAAE,EAAE;AACpB,QAAQ,SAAS,EAAE,EAAE;AACrB,QAAQ,UAAU,EAAE,EAAE;AACtB,QAAQ,cAAc,EAAE,KAAK;AAC7B,QAAQ,gBAAgB,EAAE,SAAS;AACnC,KAAK;AACL,IAAI,MAAM,OAAO,GAAG;AACpB,QAAQ,QAAQ,CAAC,QAAQ,EAAE;AAC3B,YAAY,IAAI,CAAC,QAAQ,GAAG,QAAQ;AACpC,YAAY,OAAO,IAAI;AACvB,SAAS;AACT,QAAQ,IAAI,CAAC,IAAI,EAAE;AACnB,YAAY,IAAI,CAAC,SAAS,GAAG,IAAI;AACjC,YAAY,OAAO,IAAI;AACvB,SAAS;AACT,QAAQ,UAAU,CAAC,KAAK,EAAE;AAC1B,YAAY,IAAI,CAAC,UAAU,GAAG,KAAK;AACnC,YAAY,OAAO,IAAI;AACvB,SAAS;AACT,QAAQ,UAAU,CAAC,IAAI,EAAE,GAAG,IAAI,EAAE;AAClC,YAAY,IAAI,CAAC,cAAc,GAAG,IAAI;AACtC,YAAY,IAAI,IAAI,KAAK,SAAS,EAAE;AACpC,gBAAgB,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC,CAAC;AAC/C;AACA,YAAY,OAAO,IAAI;AACvB,SAAS;AACT,QAAQ,GAAG,KAAK,CAAC,SAAS,CAAC;AAC3B,KAAK;AACL,IAAI,OAAO,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE;AAC/B;AACO,SAAS,UAAU,CAAC,qBAAqB,EAAE;AAClD,IAAI,OAAO,WAAW,CAAC,qBAAqB,CAAC;AAC7C;;;;"}
@@ -730,7 +730,7 @@ const secondaryIndexDefaultQueryField = (modelName, pk, sk) => {
730
730
  * and the value is an array of transformed Amplify @index directives with all supplied attributes
731
731
  */
732
732
  const transformedSecondaryIndexesForModel = (modelName, secondaryIndexes, modelFields, getRefType) => {
733
- const indexDirectiveWithAttributes = (partitionKey, sortKeys, indexName, queryField) => {
733
+ const indexDirectiveWithAttributes = (partitionKey, sortKeys, indexName, queryField, projectionType, nonKeyAttributes) => {
734
734
  for (const keyName of [partitionKey, ...sortKeys]) {
735
735
  const field = modelFields[keyName];
736
736
  if (isRefField(field)) {
@@ -740,7 +740,7 @@ const transformedSecondaryIndexesForModel = (modelName, secondaryIndexes, modelF
740
740
  }
741
741
  }
742
742
  }
743
- if (!sortKeys.length && !indexName && !queryField && queryField !== null) {
743
+ if (!sortKeys.length && !indexName && !queryField && queryField !== null && !projectionType) {
744
744
  return `@index(queryField: "${secondaryIndexDefaultQueryField(modelName, partitionKey)}")`;
745
745
  }
746
746
  const attributes = [];
@@ -759,11 +759,21 @@ const transformedSecondaryIndexesForModel = (modelName, secondaryIndexes, modelF
759
759
  else {
760
760
  attributes.push(`queryField: "${secondaryIndexDefaultQueryField(modelName, partitionKey, sortKeys)}"`);
761
761
  }
762
+ // Add projection attributes if specified
763
+ if (projectionType && projectionType !== 'ALL') {
764
+ if (projectionType === 'KEYS_ONLY') {
765
+ attributes.push(`projection: { type: KEYS_ONLY }`);
766
+ }
767
+ else if (projectionType === 'INCLUDE' && nonKeyAttributes?.length) {
768
+ const nonKeyAttrsStr = nonKeyAttributes.map(attr => `"${attr}"`).join(', ');
769
+ attributes.push(`projection: { type: INCLUDE, nonKeyAttributes: [${nonKeyAttrsStr}] }`);
770
+ }
771
+ }
762
772
  return `@index(${attributes.join(', ')})`;
763
773
  };
764
- return secondaryIndexes.reduce((acc, { data: { partitionKey, sortKeys, indexName, queryField } }) => {
774
+ return secondaryIndexes.reduce((acc, { data: { partitionKey, sortKeys, indexName, queryField, projectionType, nonKeyAttributes } }) => {
765
775
  acc[partitionKey] = acc[partitionKey] || [];
766
- acc[partitionKey].push(indexDirectiveWithAttributes(partitionKey, sortKeys, indexName, queryField));
776
+ acc[partitionKey].push(indexDirectiveWithAttributes(partitionKey, sortKeys, indexName, queryField, projectionType, nonKeyAttributes));
767
777
  return acc;
768
778
  }, {});
769
779
  };