Open agrabz opened 10 months ago
@agrabz Thanks for opening the issue. Our team will investigate and provide updates on the issue as soon as we have more information.
Hi @agrabz,
Thanks for the details, as you may have noticed the Amplify List contains a lot of functionality that abstracts away the next token, allow query depth control, and connected models traversal. As for your request, we will have to investigate how we can better provide the lower level alternative like you saw in the JS experience.
The API will most likely look like this:
import AWSPluginsCore
public struct ListQueryResult<ModelType: Model>: Decodable {
public let items: [ModelType]
public let nextToken: String?
}
extension GraphQLRequest {
public static func listQuery<M: Model>(_ modelType: M.Type,
where predicate: QueryPredicate? = nil,
limit: Int? = nil,
nextToken: String? = nil,
authType: AWSAuthorizationType? = nil) -> GraphQLRequest<ListQueryResult<M>> {
var documentBuilder = ModelBasedGraphQLDocumentBuilder(modelSchema: modelType.schema,
operationType: .query,
primaryKeysOnly: true)
documentBuilder.add(decorator: DirectiveNameDecorator(type: .list))
if let predicate = predicate {
documentBuilder.add(decorator: FilterDecorator(filter: predicate.graphQLFilter(for: modelType.schema)))
}
documentBuilder.add(decorator: PaginationDecorator(limit: limit, nextToken: nextToken))
documentBuilder.add(decorator: AuthRuleDecorator(.query, authType: authType))
let document = documentBuilder.build()
let awsPluginOptions = AWSPluginOptions(authType: authType, modelName: modelType.schema.name)
let requestOptions = GraphQLRequest<ListQueryResult<M>>.Options(pluginOptions: awsPluginOptions)
return GraphQLRequest<ListQueryResult<M>>(document: document.stringValue,
variables: document.variables,
responseType: ListQueryResult<M>.self,
decodePath: document.name,
options: requestOptions)
}
}
Usage:
let graphQLResponse = try await Amplify.API.query(request: .listQuery(Post3.self))
guard case let .success(listQueryResults) = graphQLResponse else {
XCTFail("Missing successful response")
return
}
XCTAssertTrue(!listQueryResults.items.isEmpty)
XCTAssertTrue(!listQueryResults.nextToken.isEmpty)
One of the challenges we face with adding this to our library publicly is that the re-use of the code generated Model types, although makes it easier to use as part of the decoding type, also holds the high level types (LazyReference and Amplify.List type mentioned in the beginning) we built to facilitate the lazy loading and pagination support. Calling the listQuery
(code snippet above) and then trying to traverse to the connected models will not have the expected outcome. Using the .list
API that we provide hooks into the rest of the functionality we've built.
However, if the traversal to connected models isn't something you need right now, you can get unblocked with the code snippet above. The caveat is that this code uses "public but internal" interfaces that may change in the future versions of Amplify. If you'd like to create a more simple version but closer to your data modeling use cases, you can hardcode the document string in the GraphQL request and reuse ListQueryResult above. When hardcoding the document string, make sure to include the nextToken and the required fields of your data model. You can see some examples of the construction here https://docs.amplify.aws/swift/build-a-backend/graphqlapi/advanced-workflows/
This has been identified as a feature request. If this feature is important to you, we strongly encourage you to give a 👍 reaction on the request. This helps us prioritize new features most important to you. Thank you!
Describe the bug
I'll need to pull my data from different backends (the goal on the long term is to use Amplify only, but that's going to be a longer migration process), so I need to make sure that I'm really not dependent on any backend.
It's anyway a common concept that the app code should not depend on any SDK- or backend-signatures, therefore when I retrieve an
Amplify.List
ofMyStuff
objects (whereMyStuff
was generated withcodegen
), I convert them to an(Swift)Foundation.Array
ofMyStuffDTO
(whereMyStuffDTO
was coded manually and is more or less a copy ofMyStuff
). With this conversion I can ensure that my application doesn't depend on one single SDK and backend and I can add any additional datasources in the future with ease, i.e. following the same conversion and always use MyStuffDTO in the app, independently from the backend (Amplify or 3rd party).The way how I resolved pagination in my other apps is either to store the
offset/skip
+top
pair, or thenextToken
as part of a technical object called for examplePagination
and send them with every request. However I see that Amplify took a really different approach and does not expose thenextToken
as a public result of the functionAmplify.API.query(request: )
. This makes my usual conversion approach really hard. I can come up with a workaround, but it's hard to resolve this problem by not violating the principle of "client should not depend on backend technology".I found that there's a similar function that does return the nextToken:
Amplify.API.query(request: .syncQuery(modelType: )
However I'm not sure how to use it. Is it documented somewhere maybe? I couldn't find its docs. When I use it as any of the below ways, it fails with the same error:Amplify.API.query(request: .syncQuery(modelType: MyStuff.self))
Amplify.API.query(request: .syncQuery(modelSchema: MyStuff.schema))
Error:
GraphQLResponseError<PaginatedList<AnyModel>>: GraphQL service returned a successful response containing errors: [Amplify.GraphQLError(message: "Validation error of type FieldUndefined: Field \'syncMyStuffs\' in type \'Query\' is undefined @ \'syncMyStuffs\'", locations: Optional([Amplify.GraphQLError.Location(line: 2, column: 3)]), path: nil, extensions: nil)]
Reading this might sound at this point a feature request, BUT I found the JavaScript docs, I also checked the Walkthrough (the expandable section in the page): Paginate list queries, which in my understanding states that the same function in JS does return the nextToken and should work out of the box. This is to me a major discrepancy between the Amplify JS and Swift libraries, hence the bug type. And of course because of the above mentioned "client should not depend on backend tech" principle, that I have to follow.
I have the below questions?
Amplify.API.query(request: .syncQuery(modelType: MyStuff.self))
Amplify.API.query(request: .syncQuery(modelSchema: MyStuff.schema))
Steps To Reproduce
Expected behavior
Function
Amplify.API.query(request: , limit: 10)
should return nextToken AND then be capable to handle nextToken as input.Functions:
Amplify.API.query(request: .syncQuery(modelType: MyStuff.self, limit 10))
Amplify.API.query(request: .syncQuery(modelSchema: MyStuff.schema))
2.1. Should have documentation. 2.2. Should not return error.There should be some way provided by Amplify Swift library to allow iOS devs to trace pagination state.
Amplify Framework Version
latest
Amplify Categories
API
Dependency manager
Swift PM
Swift version
5.9
CLI version
12.7.1
Xcode version
15.0
Relevant log output
No response
Is this a regression?
No
Regression additional context
No response
Platforms
iOS
OS Version
iOS 17.1
Device
iPhone 15 Pro simulator
Specific to simulators
No
Additional context
I see many
assertionFailure
in the Amplify code forhasNextPage
andgetNextPage
, which is really not nice. An error should be thrown instead, or nil returned or literally anything else.