@aws-amplify/data-schema 1.3.7 → 1.3.9
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/dist/cjs/CombineSchema.js +5 -1
- package/dist/cjs/CombineSchema.js.map +1 -1
- package/dist/cjs/ModelSchema.js +8 -2
- package/dist/cjs/ModelSchema.js.map +1 -1
- package/dist/cjs/SchemaProcessor.js +269 -4
- package/dist/cjs/SchemaProcessor.js.map +1 -1
- package/dist/cjs/runtime/internals/server/generateModelsProperty.js +1 -1
- package/dist/cjs/runtime/internals/server/generateModelsProperty.js.map +1 -1
- package/dist/esm/CombineSchema.mjs +5 -1
- package/dist/esm/CombineSchema.mjs.map +1 -1
- package/dist/esm/ModelSchema.d.ts +6 -0
- package/dist/esm/ModelSchema.mjs +8 -2
- package/dist/esm/ModelSchema.mjs.map +1 -1
- package/dist/esm/SchemaProcessor.mjs +270 -5
- package/dist/esm/SchemaProcessor.mjs.map +1 -1
- package/dist/esm/runtime/client/index.d.ts +20 -10
- package/dist/esm/runtime/internals/server/generateModelsProperty.mjs +1 -1
- package/dist/esm/runtime/internals/server/generateModelsProperty.mjs.map +1 -1
- package/dist/meta/cjs.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/CombineSchema.ts +5 -1
- package/src/ModelSchema.ts +14 -2
- package/src/SchemaProcessor.ts +416 -5
- package/src/runtime/client/index.ts +40 -22
- package/src/runtime/internals/server/generateModelsProperty.ts +1 -0
package/package.json
CHANGED
package/src/CombineSchema.ts
CHANGED
|
@@ -31,10 +31,14 @@ function internalCombine<
|
|
|
31
31
|
SchemasTuple extends [...Schema],
|
|
32
32
|
>(schemas: SchemasTuple): CombinedModelSchema<Schema> {
|
|
33
33
|
validateDuplicateTypeNames(schemas);
|
|
34
|
-
|
|
34
|
+
const combined = {
|
|
35
35
|
...combinedSchemaBrand,
|
|
36
36
|
schemas: schemas,
|
|
37
37
|
};
|
|
38
|
+
for (const schema of combined.schemas) {
|
|
39
|
+
schema.context = combined;
|
|
40
|
+
}
|
|
41
|
+
return combined;
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
function validateDuplicateTypeNames<Schema extends GenericModelSchema<any>[]>(
|
package/src/ModelSchema.ts
CHANGED
|
@@ -77,6 +77,9 @@ export type InternalSchema = {
|
|
|
77
77
|
authorization: SchemaAuthorization<any, any, any>[];
|
|
78
78
|
configuration: SchemaConfiguration<any, any>;
|
|
79
79
|
};
|
|
80
|
+
context?: {
|
|
81
|
+
schemas: InternalSchema[];
|
|
82
|
+
};
|
|
80
83
|
};
|
|
81
84
|
|
|
82
85
|
export type BaseSchema<
|
|
@@ -90,6 +93,9 @@ export type BaseSchema<
|
|
|
90
93
|
: never;
|
|
91
94
|
};
|
|
92
95
|
transform: () => DerivedApiDefinition;
|
|
96
|
+
context?: {
|
|
97
|
+
schemas: GenericModelSchema<any>[];
|
|
98
|
+
};
|
|
93
99
|
};
|
|
94
100
|
|
|
95
101
|
export type GenericModelSchema<T extends ModelSchemaParamShape> =
|
|
@@ -315,7 +321,10 @@ function _rdsSchema<
|
|
|
315
321
|
data,
|
|
316
322
|
models,
|
|
317
323
|
transform(): DerivedApiDefinition {
|
|
318
|
-
const internalSchema: InternalSchema = {
|
|
324
|
+
const internalSchema: InternalSchema = {
|
|
325
|
+
data,
|
|
326
|
+
context: this.context,
|
|
327
|
+
} as InternalSchema;
|
|
319
328
|
|
|
320
329
|
return processSchema({ schema: internalSchema });
|
|
321
330
|
},
|
|
@@ -406,7 +415,10 @@ function _ddbSchema<
|
|
|
406
415
|
return {
|
|
407
416
|
data,
|
|
408
417
|
transform(): DerivedApiDefinition {
|
|
409
|
-
const internalSchema = {
|
|
418
|
+
const internalSchema = {
|
|
419
|
+
data,
|
|
420
|
+
context: this.context,
|
|
421
|
+
};
|
|
410
422
|
|
|
411
423
|
return processSchema({ schema: internalSchema });
|
|
412
424
|
},
|
package/src/SchemaProcessor.ts
CHANGED
|
@@ -6,7 +6,10 @@ import {
|
|
|
6
6
|
type BaseModelField,
|
|
7
7
|
ModelFieldType,
|
|
8
8
|
} from './ModelField';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
ModelRelationshipTypes,
|
|
11
|
+
type InternalRelationalField,
|
|
12
|
+
} from './ModelRelationalField';
|
|
10
13
|
import type { InternalModel } from './ModelType';
|
|
11
14
|
import type { InternalModelIndexType } from './ModelIndex';
|
|
12
15
|
import {
|
|
@@ -1075,6 +1078,29 @@ const extractFunctionSchemaAccess = (
|
|
|
1075
1078
|
return { schemaAuth, functionSchemaAccess };
|
|
1076
1079
|
};
|
|
1077
1080
|
|
|
1081
|
+
/**
|
|
1082
|
+
* Searches a schema and all related schemas (through `.combine()`) for the given type by name.
|
|
1083
|
+
*
|
|
1084
|
+
* @param schema
|
|
1085
|
+
* @param name
|
|
1086
|
+
* @returns
|
|
1087
|
+
*/
|
|
1088
|
+
const findCombinedSchemaType = (
|
|
1089
|
+
schema: InternalSchema,
|
|
1090
|
+
name: string,
|
|
1091
|
+
): unknown | undefined => {
|
|
1092
|
+
if (schema.context) {
|
|
1093
|
+
for (const contextualSchema of schema.context.schemas) {
|
|
1094
|
+
if (contextualSchema.data.types[name]) {
|
|
1095
|
+
return contextualSchema.data.types[name];
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
} else {
|
|
1099
|
+
return schema.data.types[name];
|
|
1100
|
+
}
|
|
1101
|
+
return undefined;
|
|
1102
|
+
};
|
|
1103
|
+
|
|
1078
1104
|
type GetRef =
|
|
1079
1105
|
| {
|
|
1080
1106
|
type: 'Model';
|
|
@@ -1093,12 +1119,14 @@ type GetRef =
|
|
|
1093
1119
|
* Returns a closure for retrieving reference type and definition from schema
|
|
1094
1120
|
*/
|
|
1095
1121
|
const getRefTypeForSchema = (schema: InternalSchema) => {
|
|
1096
|
-
const getRefType = (
|
|
1097
|
-
const typeDef = schema
|
|
1122
|
+
const getRefType = (name: string, referrerName?: string): GetRef => {
|
|
1123
|
+
const typeDef = findCombinedSchemaType(schema, name);
|
|
1098
1124
|
|
|
1099
1125
|
if (typeDef === undefined) {
|
|
1100
1126
|
throw new Error(
|
|
1101
|
-
|
|
1127
|
+
referrerName
|
|
1128
|
+
? `Invalid ref. ${referrerName} is referring to ${name} which is not defined in the schema`
|
|
1129
|
+
: `Invalid ref. ${name} is not defined in the schema`,
|
|
1102
1130
|
);
|
|
1103
1131
|
}
|
|
1104
1132
|
|
|
@@ -1119,7 +1147,9 @@ const getRefTypeForSchema = (schema: InternalSchema) => {
|
|
|
1119
1147
|
}
|
|
1120
1148
|
|
|
1121
1149
|
throw new Error(
|
|
1122
|
-
|
|
1150
|
+
referrerName
|
|
1151
|
+
? `Invalid ref. ${referrerName} is referring to ${name} which is neither a Model, Custom Operation, Custom Type, or Enum`
|
|
1152
|
+
: `Invalid ref. ${name} is neither a Model, Custom Operation, Custom Type, or Enum`,
|
|
1123
1153
|
);
|
|
1124
1154
|
};
|
|
1125
1155
|
|
|
@@ -1395,6 +1425,18 @@ const schemaPreprocessor = (
|
|
|
1395
1425
|
);
|
|
1396
1426
|
}
|
|
1397
1427
|
|
|
1428
|
+
const getInternalModel = (
|
|
1429
|
+
modelName: string,
|
|
1430
|
+
sourceName?: string,
|
|
1431
|
+
): InternalModel => {
|
|
1432
|
+
const model = getRefType(modelName, sourceName);
|
|
1433
|
+
if (!isInternalModel(model.def)) {
|
|
1434
|
+
throw new Error(`Expected to find model type with name ${modelName}`);
|
|
1435
|
+
}
|
|
1436
|
+
return model.def;
|
|
1437
|
+
};
|
|
1438
|
+
validateRelationships(typeName, fields, getInternalModel);
|
|
1439
|
+
|
|
1398
1440
|
const fieldLevelAuthRules = processFieldLevelAuthRules(
|
|
1399
1441
|
fields,
|
|
1400
1442
|
authFields,
|
|
@@ -1769,6 +1811,375 @@ function extractNestedCustomTypeNames(
|
|
|
1769
1811
|
return nestedCustomTypeNames;
|
|
1770
1812
|
}
|
|
1771
1813
|
|
|
1814
|
+
/**
|
|
1815
|
+
* Validates that defined relationships conform to the following rules.
|
|
1816
|
+
* - relationships are bidirectional
|
|
1817
|
+
* - hasOne has a belongsTo counterpart
|
|
1818
|
+
* - hasMany has a belongsTo counterpart
|
|
1819
|
+
* - belongsTo has either a hasOne or hasMany counterpart
|
|
1820
|
+
* - both sides of a relationship have identical `references` defined.
|
|
1821
|
+
* - the `references` match the primary key of the parent model
|
|
1822
|
+
* - references[0] is the primaryKey's partitionKey on the parent model
|
|
1823
|
+
* - references[1...n] are the primaryKey's sortKey(s) on the parent model
|
|
1824
|
+
* - types match (id / string / number)
|
|
1825
|
+
* - the `references` are fields defined on the child model
|
|
1826
|
+
* - field names match the named `references` arguments
|
|
1827
|
+
* - child model references fields types match those of the parent model's primaryKey
|
|
1828
|
+
* @param typeName source model's type name.
|
|
1829
|
+
* @param record map of field name to {@link ModelField}
|
|
1830
|
+
* @param getInternalModel given a model name, return an {@link InternalModel}
|
|
1831
|
+
*/
|
|
1832
|
+
function validateRelationships(
|
|
1833
|
+
typeName: string,
|
|
1834
|
+
record: Record<string, ModelField<any, any>>,
|
|
1835
|
+
getInternalModel: (
|
|
1836
|
+
modelName: string,
|
|
1837
|
+
referringModelName?: string,
|
|
1838
|
+
) => InternalModel,
|
|
1839
|
+
) {
|
|
1840
|
+
for (const [name, field] of Object.entries(record)) {
|
|
1841
|
+
// If the field's type is not a model, there's no relationship
|
|
1842
|
+
// to evaluate and we can skip this iteration.
|
|
1843
|
+
if (!isModelField(field)) {
|
|
1844
|
+
continue;
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
// Create a structure representing the relationship for validation.
|
|
1848
|
+
const relationship = getModelRelationship(
|
|
1849
|
+
typeName,
|
|
1850
|
+
{ name: name, def: field.data },
|
|
1851
|
+
getInternalModel,
|
|
1852
|
+
);
|
|
1853
|
+
|
|
1854
|
+
// Validate that the references defined in the relationship follow the
|
|
1855
|
+
// relational definition rules.
|
|
1856
|
+
validateRelationalReferences(relationship);
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
/**
|
|
1861
|
+
* Helper function that describes the relationship of a given connection field for use in logging or error messages.
|
|
1862
|
+
*
|
|
1863
|
+
* `Parent.child: Child @hasMany(references: ['parentId'])`
|
|
1864
|
+
* -- or --
|
|
1865
|
+
* `Child.parent: Parent @belongsTo(references: ['parentId'])`
|
|
1866
|
+
* @param sourceField The {@link ConnectionField} to describe.
|
|
1867
|
+
* @param sourceModelName The name of the model within which the sourceField is defined.
|
|
1868
|
+
* @returns a 'string' describing the relationship
|
|
1869
|
+
*/
|
|
1870
|
+
function describeConnectFieldRelationship(
|
|
1871
|
+
sourceField: ConnectionField,
|
|
1872
|
+
sourceModelName: string,
|
|
1873
|
+
): string {
|
|
1874
|
+
const associatedTypeDescription = sourceField.def.array
|
|
1875
|
+
? `[${sourceField.def.relatedModel}]`
|
|
1876
|
+
: sourceField.def.relatedModel;
|
|
1877
|
+
const referencesDescription =
|
|
1878
|
+
sourceField.def.references
|
|
1879
|
+
.reduce(
|
|
1880
|
+
(description, reference) => description + `'${reference}', `,
|
|
1881
|
+
'references: [',
|
|
1882
|
+
)
|
|
1883
|
+
.slice(0, -2) + ']';
|
|
1884
|
+
return `${sourceModelName}.${sourceField.name}: ${associatedTypeDescription} @${sourceField.def.type}(${referencesDescription})`;
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
/**
|
|
1888
|
+
* Validates that the types of child model's reference fields match the types of the parent model's identifier fields.
|
|
1889
|
+
* @param relationship The {@link ModelRelationship} to validate.
|
|
1890
|
+
*/
|
|
1891
|
+
function validateRelationalReferences(relationship: ModelRelationship) {
|
|
1892
|
+
const {
|
|
1893
|
+
parent,
|
|
1894
|
+
parentConnectionField,
|
|
1895
|
+
child,
|
|
1896
|
+
childConnectionField,
|
|
1897
|
+
references,
|
|
1898
|
+
} = relationship;
|
|
1899
|
+
const parentIdentifiers = getIndentifierTypes(parent);
|
|
1900
|
+
|
|
1901
|
+
const childReferenceTypes: ModelFieldType[] = [];
|
|
1902
|
+
// Iterate through the model schema defined 'references' to find each matching field on the Related model.
|
|
1903
|
+
// If a field by that name is not found, throw a validate error.
|
|
1904
|
+
// Accumulate the ModelFieldType for each reference field to validate matching types below.
|
|
1905
|
+
for (const reference of references) {
|
|
1906
|
+
const relatedReferenceType = child.data.fields[reference]?.data
|
|
1907
|
+
.fieldType as ModelFieldType;
|
|
1908
|
+
// reference field on related type with name passed to references not found. Time to throw a validation error.
|
|
1909
|
+
if (!relatedReferenceType) {
|
|
1910
|
+
const errorMessage =
|
|
1911
|
+
`reference field '${reference}' must be defined on ${parentConnectionField.def.relatedModel}. ` +
|
|
1912
|
+
describeConnectFieldRelationship(
|
|
1913
|
+
parentConnectionField,
|
|
1914
|
+
childConnectionField.def.relatedModel,
|
|
1915
|
+
) +
|
|
1916
|
+
' <-> ' +
|
|
1917
|
+
describeConnectFieldRelationship(
|
|
1918
|
+
childConnectionField,
|
|
1919
|
+
parentConnectionField.def.relatedModel,
|
|
1920
|
+
);
|
|
1921
|
+
throw new Error(errorMessage);
|
|
1922
|
+
}
|
|
1923
|
+
childReferenceTypes.push(relatedReferenceType);
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
if (parentIdentifiers.length !== childReferenceTypes.length) {
|
|
1927
|
+
throw new Error(
|
|
1928
|
+
`The identifiers defined on ${childConnectionField.def.relatedModel} must match the reference fields defined on ${parentConnectionField.def.relatedModel}.\n` +
|
|
1929
|
+
`${parentIdentifiers.length} identifiers defined on ${childConnectionField.def.relatedModel}.\n` +
|
|
1930
|
+
`${childReferenceTypes.length} reference fields found on ${parentConnectionField.def.relatedModel}`,
|
|
1931
|
+
);
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
const matchingModelFieldType = (
|
|
1935
|
+
a: ModelFieldType,
|
|
1936
|
+
b: ModelFieldType,
|
|
1937
|
+
): boolean => {
|
|
1938
|
+
// `String` and `Id` are considered equal types for when comparing
|
|
1939
|
+
// the child model's references fields with their counterparts within
|
|
1940
|
+
// the parent model's identifier (parent key) fields.
|
|
1941
|
+
const matching = [ModelFieldType.Id, ModelFieldType.String];
|
|
1942
|
+
return a === b || (matching.includes(a) && matching.includes(b));
|
|
1943
|
+
};
|
|
1944
|
+
|
|
1945
|
+
// Zip pairs of child model's reference field with corresponding parent model's identifier field.
|
|
1946
|
+
// Confirm that the types match. If they don't, throw a validation error.
|
|
1947
|
+
parentIdentifiers
|
|
1948
|
+
.map((identifier, index) => [identifier, childReferenceTypes[index]])
|
|
1949
|
+
.forEach(([parent, child]) => {
|
|
1950
|
+
if (!matchingModelFieldType(parent, child)) {
|
|
1951
|
+
throw new Error('Validate Error: types do not match');
|
|
1952
|
+
}
|
|
1953
|
+
});
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
/**
|
|
1957
|
+
* Internal convenience type that contains the name of the connection field along with
|
|
1958
|
+
* its {@link ModelFieldDef}.
|
|
1959
|
+
*/
|
|
1960
|
+
type ConnectionField = {
|
|
1961
|
+
name: string;
|
|
1962
|
+
def: ModelFieldDef;
|
|
1963
|
+
};
|
|
1964
|
+
|
|
1965
|
+
/**
|
|
1966
|
+
* An internal representation of a model relationship used by validation functions.
|
|
1967
|
+
* Use {@link getModelRelationship} to create this.
|
|
1968
|
+
* See {@link validateRelationalReferences} for validation example.
|
|
1969
|
+
*/
|
|
1970
|
+
type ModelRelationship = {
|
|
1971
|
+
/**
|
|
1972
|
+
* The model that is referred to by the child.
|
|
1973
|
+
*/
|
|
1974
|
+
parent: InternalModel;
|
|
1975
|
+
|
|
1976
|
+
/**
|
|
1977
|
+
* The field on the parent into which the child model(s) is/are populated.
|
|
1978
|
+
*/
|
|
1979
|
+
parentConnectionField: ConnectionField;
|
|
1980
|
+
|
|
1981
|
+
/**
|
|
1982
|
+
* The model that refers to the parent.
|
|
1983
|
+
*/
|
|
1984
|
+
child: InternalModel;
|
|
1985
|
+
|
|
1986
|
+
/**
|
|
1987
|
+
* The field on the child into which the parent is loaded.
|
|
1988
|
+
*/
|
|
1989
|
+
childConnectionField: ConnectionField;
|
|
1990
|
+
|
|
1991
|
+
/**
|
|
1992
|
+
* The field names on the child that refer to the parent's PK.
|
|
1993
|
+
*
|
|
1994
|
+
* Both sides of the relationship must identify these "references" fields which
|
|
1995
|
+
* names the child FK fields. So, regardless of which side of the relationship
|
|
1996
|
+
* is explored, if the relationship definition is valid, these fields will be
|
|
1997
|
+
* the same.
|
|
1998
|
+
*/
|
|
1999
|
+
references: string[];
|
|
2000
|
+
};
|
|
2001
|
+
|
|
2002
|
+
/**
|
|
2003
|
+
* Relationship definitions require bi-directionality.
|
|
2004
|
+
* Use this to generate a `ModelRelationshipTypes[]` containing acceptable counterparts on the
|
|
2005
|
+
* associated model.
|
|
2006
|
+
*
|
|
2007
|
+
* Given {@link ModelRelationshipTypes.hasOne} or {@link ModelRelationshipTypes.hasOne} returns [{@link ModelRelationshipTypes.belongsTo}]
|
|
2008
|
+
* Given {@link ModelRelationshipTypes.belongsTo} returns [{@link ModelRelationshipTypes.hasOne}, {@link ModelRelationshipTypes.belongsTo}]
|
|
2009
|
+
*
|
|
2010
|
+
* @param relationshipType {@link ModelRelationshipTypes} defined on source model's connection field.
|
|
2011
|
+
* @returns possible counterpart {@link ModelRelationshipTypes} as `ModelRelationshipTypes[]`
|
|
2012
|
+
*/
|
|
2013
|
+
function associatedRelationshipTypes(
|
|
2014
|
+
relationshipType: ModelRelationshipTypes,
|
|
2015
|
+
): ModelRelationshipTypes[] {
|
|
2016
|
+
switch (relationshipType) {
|
|
2017
|
+
case ModelRelationshipTypes.hasOne:
|
|
2018
|
+
case ModelRelationshipTypes.hasMany:
|
|
2019
|
+
return [ModelRelationshipTypes.belongsTo];
|
|
2020
|
+
case ModelRelationshipTypes.belongsTo:
|
|
2021
|
+
return [ModelRelationshipTypes.hasOne, ModelRelationshipTypes.hasMany];
|
|
2022
|
+
default:
|
|
2023
|
+
return []; // TODO: Remove this case on types are updated.
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
/**
|
|
2028
|
+
* Retrieves the types of the identifiers defined on a model.
|
|
2029
|
+
*
|
|
2030
|
+
* Note: if a field by the name `id` isn't found in the {@link InternalModel},
|
|
2031
|
+
* this assumes an implicitly generated identifier is used with the type.
|
|
2032
|
+
*
|
|
2033
|
+
* This function does not validate that a corresponding field exists for each of the
|
|
2034
|
+
* identifiers because this validation happens at compile time.
|
|
2035
|
+
* @param model {@link InternalModel} from which to retrieve identifier types.
|
|
2036
|
+
* @returns Array of {@link ModelFieldType} of the model's identifiers found.
|
|
2037
|
+
*/
|
|
2038
|
+
function getIndentifierTypes(model: InternalModel): ModelFieldType[] {
|
|
2039
|
+
return model.data.identifier.flatMap((fieldName) => {
|
|
2040
|
+
const field = model.data.fields[fieldName];
|
|
2041
|
+
if (field) {
|
|
2042
|
+
return [field.data.fieldType as ModelFieldType];
|
|
2043
|
+
} else if (fieldName === 'id') {
|
|
2044
|
+
// implicity generated ID
|
|
2045
|
+
return [ModelFieldType.Id];
|
|
2046
|
+
}
|
|
2047
|
+
return [];
|
|
2048
|
+
});
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
/**
|
|
2052
|
+
* Given a relationship definition within a source model (`sourceModelName`, `sourceConnectionField`) and
|
|
2053
|
+
* the associated model (`associatedModel`), this finds the connection field for the relationship defined on the
|
|
2054
|
+
* associated model. Invalid states, such a 0 or >1 matching connection fields result in an error.
|
|
2055
|
+
* @param sourceModelName
|
|
2056
|
+
* @param sourceConnectionField
|
|
2057
|
+
* @param associatedModel
|
|
2058
|
+
* @returns
|
|
2059
|
+
*/
|
|
2060
|
+
function getAssociatedConnectionField(
|
|
2061
|
+
sourceModelName: string,
|
|
2062
|
+
sourceConnectionField: ConnectionField,
|
|
2063
|
+
associatedModel: InternalModel,
|
|
2064
|
+
): ConnectionField {
|
|
2065
|
+
const associatedRelationshipOptions = associatedRelationshipTypes(
|
|
2066
|
+
sourceConnectionField.def.type,
|
|
2067
|
+
);
|
|
2068
|
+
// Iterate through the associated model's fields to find the associated connection field for the relationship defined on the source model.
|
|
2069
|
+
const associatedConnectionFieldCandidates = Object.entries(
|
|
2070
|
+
associatedModel.data.fields,
|
|
2071
|
+
).filter(([_key, connectionField]) => {
|
|
2072
|
+
// If the field isn't a model, it's not part of the relationship definition -- ignore the field.
|
|
2073
|
+
if (!isModelField(connectionField)) {
|
|
2074
|
+
return false;
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
// In order to find that associated connection field, we need to do some validation that we'll depend on further downstream.
|
|
2078
|
+
// 1. Field type matches the source model's type.
|
|
2079
|
+
// 2. A valid counterpart relational modifier is defined on the field. See `associatedRelationshipTypes` for more information.
|
|
2080
|
+
// 3. The reference arguments provided to the field match (element count + string comparison) references passed to the source connection field.
|
|
2081
|
+
return (
|
|
2082
|
+
connectionField.data.relatedModel === sourceModelName &&
|
|
2083
|
+
associatedRelationshipOptions.includes(connectionField.data.type) &&
|
|
2084
|
+
connectionField.data.references.length ===
|
|
2085
|
+
sourceConnectionField.def.references.length &&
|
|
2086
|
+
connectionField.data.references.every(
|
|
2087
|
+
(value, index) => value === sourceConnectionField.def.references[index],
|
|
2088
|
+
)
|
|
2089
|
+
);
|
|
2090
|
+
});
|
|
2091
|
+
|
|
2092
|
+
// We should have found exactly one connection field candidate. If that's not the case, we need to throw a validation error.
|
|
2093
|
+
if (associatedConnectionFieldCandidates.length != 1) {
|
|
2094
|
+
// const associatedModelDescription = sourceConnectionField.def.array
|
|
2095
|
+
// ? `[${sourceConnectionField.def.relatedModel}]`
|
|
2096
|
+
// : sourceConnectionField.def.relatedModel
|
|
2097
|
+
const sourceConnectionFieldDescription = describeConnectFieldRelationship(
|
|
2098
|
+
sourceConnectionField,
|
|
2099
|
+
sourceModelName,
|
|
2100
|
+
); // `${sourceModelName}.${sourceConnectionField.name}: ${associatedModelDescription} @${sourceConnectionField.def.type}(references: [${sourceConnectionField.def.references}])`
|
|
2101
|
+
const errorMessage =
|
|
2102
|
+
associatedConnectionFieldCandidates.length === 0
|
|
2103
|
+
? `Unable to find associated relationship definition in ${sourceConnectionField.def.relatedModel}`
|
|
2104
|
+
: `Found multiple relationship associations with ${associatedConnectionFieldCandidates.map((field) => `${sourceConnectionField.def.relatedModel}.${field[0]}`).join(', ')}`;
|
|
2105
|
+
throw new Error(`${errorMessage} for ${sourceConnectionFieldDescription}`);
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
const associatedConnectionField = associatedConnectionFieldCandidates[0];
|
|
2109
|
+
if (!isModelField(associatedConnectionField[1])) {
|
|
2110
|
+
// This shouldn't happen because we've validated that it's a model field above.
|
|
2111
|
+
// However it's necessary to narrow the type.
|
|
2112
|
+
// const associatedModelDescription = sourceConnectionField.def.array
|
|
2113
|
+
// ? `[${sourceConnectionField.def.relatedModel}]`
|
|
2114
|
+
// : sourceConnectionField.def.relatedModel
|
|
2115
|
+
const sourceConnectionFieldDescription = describeConnectFieldRelationship(
|
|
2116
|
+
sourceConnectionField,
|
|
2117
|
+
sourceModelName,
|
|
2118
|
+
);
|
|
2119
|
+
const errorMessage = `Cannot find counterpart to relationship defintion for ${sourceConnectionFieldDescription}`;
|
|
2120
|
+
throw new Error(errorMessage);
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
return {
|
|
2124
|
+
name: associatedConnectionField[0],
|
|
2125
|
+
def: associatedConnectionField[1].data,
|
|
2126
|
+
};
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
/**
|
|
2130
|
+
* Given either side of a relationship (source), this retrieves the other side (related)
|
|
2131
|
+
* and packages the information neatly into a {@link ModelRelationship} for validation purposes.
|
|
2132
|
+
*
|
|
2133
|
+
* @param sourceModelName
|
|
2134
|
+
* @param sourceConnectionField
|
|
2135
|
+
* @param getInternalModel
|
|
2136
|
+
* @returns a {@link ModelRelationship}
|
|
2137
|
+
*/
|
|
2138
|
+
function getModelRelationship(
|
|
2139
|
+
sourceModelName: string,
|
|
2140
|
+
sourceModelConnectionField: ConnectionField,
|
|
2141
|
+
getInternalModel: (
|
|
2142
|
+
modelName: string,
|
|
2143
|
+
referringModelName?: string,
|
|
2144
|
+
) => InternalModel,
|
|
2145
|
+
): ModelRelationship {
|
|
2146
|
+
const sourceModel = getInternalModel(sourceModelName, sourceModelName);
|
|
2147
|
+
const associatedModel = getInternalModel(
|
|
2148
|
+
sourceModelConnectionField.def.relatedModel,
|
|
2149
|
+
sourceModelName,
|
|
2150
|
+
);
|
|
2151
|
+
|
|
2152
|
+
const relatedModelConnectionField = getAssociatedConnectionField(
|
|
2153
|
+
sourceModelName,
|
|
2154
|
+
sourceModelConnectionField,
|
|
2155
|
+
associatedModel,
|
|
2156
|
+
);
|
|
2157
|
+
|
|
2158
|
+
switch (sourceModelConnectionField.def.type) {
|
|
2159
|
+
case ModelRelationshipTypes.hasOne:
|
|
2160
|
+
case ModelRelationshipTypes.hasMany:
|
|
2161
|
+
return {
|
|
2162
|
+
parent: sourceModel,
|
|
2163
|
+
parentConnectionField: sourceModelConnectionField,
|
|
2164
|
+
child: associatedModel,
|
|
2165
|
+
childConnectionField: relatedModelConnectionField,
|
|
2166
|
+
references: sourceModelConnectionField.def.references,
|
|
2167
|
+
};
|
|
2168
|
+
case ModelRelationshipTypes.belongsTo:
|
|
2169
|
+
return {
|
|
2170
|
+
parent: associatedModel,
|
|
2171
|
+
parentConnectionField: relatedModelConnectionField,
|
|
2172
|
+
child: sourceModel,
|
|
2173
|
+
childConnectionField: sourceModelConnectionField,
|
|
2174
|
+
references: sourceModelConnectionField.def.references,
|
|
2175
|
+
};
|
|
2176
|
+
default:
|
|
2177
|
+
throw new Error(
|
|
2178
|
+
`"${sourceModelConnectionField.def.type}" is not a valid relationship type.`,
|
|
2179
|
+
);
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
|
|
1772
2183
|
/**
|
|
1773
2184
|
* Returns API definition from ModelSchema or string schema
|
|
1774
2185
|
* @param arg - { schema }
|
|
@@ -19,6 +19,7 @@ import type {
|
|
|
19
19
|
NumericFilter,
|
|
20
20
|
BooleanFilters,
|
|
21
21
|
} from '../../util';
|
|
22
|
+
import { AmplifyServer } from '../bridge-types';
|
|
22
23
|
|
|
23
24
|
// temporarily export symbols from `data-schema-types` because in case part of the
|
|
24
25
|
// problem with the runtime -> data-schema migration comes down to a mismatch
|
|
@@ -434,33 +435,51 @@ interface ClientSecondaryIndexField {
|
|
|
434
435
|
|
|
435
436
|
type IndexQueryMethods<
|
|
436
437
|
Model extends ClientSchemaByEntityTypeBaseShape['models'][string],
|
|
438
|
+
Context extends ContextType = 'CLIENT',
|
|
437
439
|
> = {
|
|
438
440
|
[K in keyof Select<
|
|
439
441
|
Model['secondaryIndexes'],
|
|
440
442
|
ClientSecondaryIndexField
|
|
441
443
|
>]: IndexQueryMethod<
|
|
442
444
|
Model,
|
|
443
|
-
Select<Model['secondaryIndexes'], ClientSecondaryIndexField>[K]
|
|
445
|
+
Select<Model['secondaryIndexes'], ClientSecondaryIndexField>[K],
|
|
446
|
+
Context
|
|
444
447
|
>;
|
|
445
448
|
};
|
|
446
449
|
|
|
447
450
|
type IndexQueryMethod<
|
|
448
451
|
Model extends ClientSchemaByEntityTypeBaseShape['models'][string],
|
|
449
452
|
Method extends ClientSecondaryIndexField,
|
|
453
|
+
Context extends ContextType = 'CLIENT',
|
|
450
454
|
FlatModel extends Record<string, unknown> = ResolvedModel<Model['type']>,
|
|
451
|
-
> =
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
455
|
+
> = Context extends 'CLIENT' | 'COOKIES'
|
|
456
|
+
? <SelectionSet extends ReadonlyArray<ModelPath<FlatModel>> = never[]>(
|
|
457
|
+
input: Method['input'],
|
|
458
|
+
options?: {
|
|
459
|
+
filter?: ModelFilter<Model>;
|
|
460
|
+
sortDirection?: ModelSortDirection;
|
|
461
|
+
limit?: number;
|
|
462
|
+
nextToken?: string | null;
|
|
463
|
+
selectionSet?: SelectionSet;
|
|
464
|
+
authMode?: AuthMode;
|
|
465
|
+
authToken?: string;
|
|
466
|
+
headers?: CustomHeaders;
|
|
467
|
+
},
|
|
468
|
+
) => ListReturnValue<Prettify<ReturnValue<Model, FlatModel, SelectionSet>>>
|
|
469
|
+
: <SelectionSet extends ReadonlyArray<ModelPath<FlatModel>> = never[]>(
|
|
470
|
+
contextSpec: AmplifyServer.ContextSpec,
|
|
471
|
+
input: Method['input'],
|
|
472
|
+
options?: {
|
|
473
|
+
filter?: ModelFilter<Model>;
|
|
474
|
+
sortDirection?: ModelSortDirection;
|
|
475
|
+
limit?: number;
|
|
476
|
+
nextToken?: string | null;
|
|
477
|
+
selectionSet?: SelectionSet;
|
|
478
|
+
authMode?: AuthMode;
|
|
479
|
+
authToken?: string;
|
|
480
|
+
headers?: CustomHeaders;
|
|
481
|
+
},
|
|
482
|
+
) => ListReturnValue<Prettify<ReturnValue<Model, FlatModel, SelectionSet>>>;
|
|
464
483
|
|
|
465
484
|
type ModelTypesClient<
|
|
466
485
|
Model extends ClientSchemaByEntityTypeBaseShape['models'][string],
|
|
@@ -609,10 +628,9 @@ type ModelTypesSSRCookies<
|
|
|
609
628
|
type ModelTypesSSRRequest<
|
|
610
629
|
Model extends ClientSchemaByEntityTypeBaseShape['models'][string],
|
|
611
630
|
FlatModel extends Record<string, unknown> = ResolvedModel<Model['type']>,
|
|
612
|
-
> = IndexQueryMethods<Model> & {
|
|
631
|
+
> = IndexQueryMethods<Model, 'REQUEST'> & {
|
|
613
632
|
create: (
|
|
614
|
-
|
|
615
|
-
contextSpec: any,
|
|
633
|
+
contextSpec: AmplifyServer.ContextSpec,
|
|
616
634
|
model: Model['createType'],
|
|
617
635
|
options?: {
|
|
618
636
|
authMode?: AuthMode;
|
|
@@ -621,7 +639,7 @@ type ModelTypesSSRRequest<
|
|
|
621
639
|
},
|
|
622
640
|
) => SingularReturnValue<Model['type']>;
|
|
623
641
|
update: (
|
|
624
|
-
contextSpec:
|
|
642
|
+
contextSpec: AmplifyServer.ContextSpec,
|
|
625
643
|
model: Model['updateType'],
|
|
626
644
|
options?: {
|
|
627
645
|
authMode?: AuthMode;
|
|
@@ -630,7 +648,7 @@ type ModelTypesSSRRequest<
|
|
|
630
648
|
},
|
|
631
649
|
) => SingularReturnValue<Model['type']>;
|
|
632
650
|
delete: (
|
|
633
|
-
contextSpec:
|
|
651
|
+
contextSpec: AmplifyServer.ContextSpec,
|
|
634
652
|
identifier: Model['deleteType'],
|
|
635
653
|
options?: {
|
|
636
654
|
authMode?: AuthMode;
|
|
@@ -639,7 +657,7 @@ type ModelTypesSSRRequest<
|
|
|
639
657
|
},
|
|
640
658
|
) => SingularReturnValue<Model['type']>;
|
|
641
659
|
get<SelectionSet extends ReadonlyArray<ModelPath<FlatModel>> = never[]>(
|
|
642
|
-
contextSpec:
|
|
660
|
+
contextSpec: AmplifyServer.ContextSpec,
|
|
643
661
|
identifier: Model['identifier'],
|
|
644
662
|
options?: {
|
|
645
663
|
selectionSet?: SelectionSet;
|
|
@@ -649,7 +667,7 @@ type ModelTypesSSRRequest<
|
|
|
649
667
|
},
|
|
650
668
|
): SingularReturnValue<Prettify<ReturnValue<Model, FlatModel, SelectionSet>>>;
|
|
651
669
|
list<SelectionSet extends ReadonlyArray<ModelPath<FlatModel>> = never[]>(
|
|
652
|
-
contextSpec:
|
|
670
|
+
contextSpec: AmplifyServer.ContextSpec,
|
|
653
671
|
options?: ListCpkOptions<Model> & {
|
|
654
672
|
filter?: ModelFilter<Model>;
|
|
655
673
|
sortDirection?: ModelSortDirection;
|
|
@@ -731,7 +749,7 @@ export type CustomOperations<
|
|
|
731
749
|
...params: CustomOperationFnParams<OperationDefs[OpName]['args']>
|
|
732
750
|
) => SingularReturnValue<OperationDefs[OpName]['type']>;
|
|
733
751
|
REQUEST: (
|
|
734
|
-
contextSpec:
|
|
752
|
+
contextSpec: AmplifyServer.ContextSpec,
|
|
735
753
|
...params: CustomOperationFnParams<OperationDefs[OpName]['args']>
|
|
736
754
|
) => SingularReturnValue<OperationDefs[OpName]['type']>;
|
|
737
755
|
}[Context];
|