From b89bff7218f490df041391e610e6bb3c3adec914 Mon Sep 17 00:00:00 2001
From: Michael Law <1365977+lawmicha@users.noreply.github.com>
Date: Wed, 15 May 2024 11:04:06 -0400
Subject: [PATCH] fix(data): swift code snippets for customize your data model
---
.../data/data-modeling/add-fields/index.mdx | 47 +++-
.../data/data-modeling/identifiers/index.mdx | 50 +++-
.../data-modeling/relationships/index.mdx | 240 ++++++++----------
.../data-modeling/secondary-index/index.mdx | 120 +++++++++
4 files changed, 324 insertions(+), 133 deletions(-)
diff --git a/src/pages/[platform]/build-a-backend/data/data-modeling/add-fields/index.mdx b/src/pages/[platform]/build-a-backend/data/data-modeling/add-fields/index.mdx
index 24b7acc7e37..334dbcb5326 100644
--- a/src/pages/[platform]/build-a-backend/data/data-modeling/add-fields/index.mdx
+++ b/src/pages/[platform]/build-a-backend/data/data-modeling/add-fields/index.mdx
@@ -87,6 +87,8 @@ a.schema({
});
```
+
+
To set or read the location field on the client side, you can expand a nested object and the type system will auto-infer the allowed values.
```ts
@@ -100,8 +102,28 @@ const { data: newPost, errors } = await client.models.Post.create({
console.log(newPost?.location?.lat, newPost?.location?.long);
```
+
+
+
+
+To set or read the location field on the client side, you can create a Post object with the location values.
+
+```swift
+let post = Post(
+ location: .init(
+ lat: 48.837006,
+ long: 8.28245))
+let createdPost = try await Amplify.API.mutate(request: .create(post)).get()
+print("\(createdPost)")
+```
+
+
+
## Specify an enum field type
-Enum has a similar developer experience as custom types: short-hand and long-form approaches. To make building a dropdown UI easier, you can also retrieve all available enum options from your client code.
+
+Enum has a similar developer experience as custom types: short-hand and long-form approaches.
+
+Short-hand approach
```ts
a.schema({
@@ -112,6 +134,8 @@ a.schema({
});
```
+Long-form approach
+
```ts
a.schema({
PrivacySetting: a.enum([
@@ -131,6 +155,8 @@ a.schema({
});
```
+
+
When creating a new item client-side, the enums are also type-enforced:
```ts
client.models.Post.create({
@@ -143,6 +169,23 @@ client.models.Post.create({
});
```
+
+
+
+
+Creating a new item client-side with the enum value:
+
+```swift
+let post = Post(
+ content: "hello",
+ privacySetting: .private)
+let createdPost = try await Amplify.API.mutate(request: .create(post)).get()
+```
+
+
+
+
+
### List enum values client-side
You can list available enum values client-side using the `client.enums..values()` API. For example, this allows you to display the available enum values within a dropdown UI.
@@ -152,6 +195,8 @@ const availableSettings = client.enums.PrivacySetting.values()
// availableSettings returns ["PRIVATE", "FRIENDS_ONLY", "PUBLIC"]
```
+
+
## Mark fields as required
By default, fields are optional. To mark a field as required, use the `.required()` modifier.
diff --git a/src/pages/[platform]/build-a-backend/data/data-modeling/identifiers/index.mdx b/src/pages/[platform]/build-a-backend/data/data-modeling/identifiers/index.mdx
index 77b2e1687b2..6667422c8ea 100644
--- a/src/pages/[platform]/build-a-backend/data/data-modeling/identifiers/index.mdx
+++ b/src/pages/[platform]/build-a-backend/data/data-modeling/identifiers/index.mdx
@@ -41,6 +41,8 @@ const schema = a.schema({
});
```
+
+
```ts
const client = generateClient();
@@ -48,6 +50,20 @@ const todo = await client.models.Todo.create({ content: 'Buy Milk', completed: f
console.log(`New Todo created: ${todo.id}`); // New Todo created: 5DB6B4CC-CD41-49F5-9844-57C0AB506B69
```
+
+
+
+
+```swift
+let todo = Todo(
+ content: "Buy Milk",
+ completed: false)
+let createdTodo = try await Amplify.API.mutate(request: .create(todo)).get()
+print("New Todo created: \(createdTodo)")
+```
+
+
+
If you want, you can use Amplify Data to define single-field and composite identifiers:
- Single-field identifier with a consumer-provided value (type: `id` or `string`, and must be marked `required`)
- Composite identifier with a set of consumer-provided values (type: `id` or `string`, and must be marked `required`)
@@ -66,12 +82,29 @@ const schema = a.schema({
.identifier(['todoId'])
.authorization(allow => [allow.publicApiKey()]),
});
+```
+
+```ts
const client = generateClient();
- const { data: todo, errors } = await client.models.Todo.create({ todoId: 'MyUniqueTodoId', content: 'Buy Milk', completed: false });
+const { data: todo, errors } = await client.models.Todo.create({ todoId: 'MyUniqueTodoId', content: 'Buy Milk', completed: false });
console.log(`New Todo created: ${todo.todoId}`); // New Todo created: MyUniqueTodoId
```
+
+
+
+
+```swift
+let todo = Todo(
+ todoId: "MyUniqueTodoId",
+ content: "Buy Milk",
+ completed: false)
+let createdTodo = try await Amplify.API.mutate(request: .create(todo)).get()
+print("New Todo created: \(createdTodo)")
+```
+
+
## Composite identifier
@@ -90,8 +123,23 @@ const schema = a.schema({
}).identifier(['tenantId', 'name'])
.authorization(allow => [allow.publicApiKey()]),
});
+```
+
+```ts
const client = generateClient();
const branch = await client.models.StoreBranch.get({ tenantId: '123', name: 'Downtown' }); // All identifier fields are required when retrieving an item
```
+
+
+
+```swift
+let queriedStoreBranch = try await Amplify.API.query(
+ request: .get(
+ StoreBranch.self,
+ byIdentifier: .identifier(
+ tenantId: "123",
+ name: "Downtown")))
+```
+
diff --git a/src/pages/[platform]/build-a-backend/data/data-modeling/relationships/index.mdx b/src/pages/[platform]/build-a-backend/data/data-modeling/relationships/index.mdx
index 026056b2d75..a55361eb696 100644
--- a/src/pages/[platform]/build-a-backend/data/data-modeling/relationships/index.mdx
+++ b/src/pages/[platform]/build-a-backend/data/data-modeling/relationships/index.mdx
@@ -114,24 +114,16 @@ Amplify.API.mutate(ModelMutation.create(team),
```swift
-Task {
- do {
- let team = Team(mantra: "Go Frontend!")
- let createTeamResult = try await Amplify.API.mutate(request: .create(team))
- guard case .success = createTeamResult else {
- print("API response: \(createTeamResult)")
- return
- }
-
- let member = Member(name: "Tim", team: try createTeamResult.get()) // Directly pass in the post instance
- let createMemberResult = try await Amplify.API.mutate(request: .create(member))
- guard case .success = createMemberResult else {
- print("API response: \(createMemberResult)")
- return
- }
- } catch {
- print("Create team or member failed", error)
- }
+do {
+ let team = Team(mantra: "Go Frontend!")
+ let createdTeam = try await Amplify.API.mutate(request: .create(team)).get()
+
+ let member = Member(
+ name: "Tim",
+ team: createdTeam) // Directly pass in the team instance
+ let createdMember = try await Amplify.API.mutate(request: .create(member))
+} catch {
+ print("Create team or member failed", error)
}
```
@@ -195,24 +187,14 @@ Amplify.API.mutate(ModelMutation.create(newTeam),
```swift
-Task {
- do {
- let newTeam = Team(mantra: "Go Fullstack!")
- let createNewTeamResult = try await Amplify.API.mutate(request: .create(newTeam))
- guard case .success = createNewTeamResult else {
- print("API response: \(createNewTeamResult)")
- return
- }
-
- let updatedMember = Member(id: existingMember.id, name: existingMember.name, team: try createNewTeamResult.get())
- let updateMemberResult = try await Amplify.API.mutate(request: .update(updatedMember))
- guard case .success = updateMemberResult else {
- print("API response: \(updateMemberResult)")
- return
- }
- } catch {
- print("Create team or member failed", error)
- }
+do {
+ let newTeam = Team(mantra: "Go Fullstack!")
+ let createdNewTeam = try await Amplify.API.mutate(request: .create(newTeam)).get()
+
+ existingMember.setTeam(createdNewTeam)
+ let updatedMember = try await Amplify.API.mutate(request: .update(existingMember)).get()
+} catch {
+ print("Create team or update member failed", error)
}
```
@@ -263,11 +245,11 @@ Amplify.API.mutate(ModelMutation.update(memberWithRemovedTeam),
```swift
-let memberWithRemovedTeam = Member(id: existingMember.id, name: existingMember.name, team: nil)
-let memberRemoveResult = try await Amplify.API.mutate(request: .update(memberWithRemovedTeam))
-guard case .success = memberRemoveResult else {
- print("API response: \(memberRemoveResult)")
- return
+do {
+ existingMember.setTeam(nil)
+ let memberRemovedTeam = try await Amplify.API.mutate(request: .update(existingMember)).get()
+} catch {
+ print("Failed to remove team from member", error)
}
```
@@ -377,21 +359,20 @@ Amplify.API.query(
```swift
-Task {
- do {
- let queryTeamResult = try await Amplify.API.query(request: .get(Team.self, byIdentifier: "MY_TEAM_ID"))
- guard case .success(let queryTeamOptional) = queryTeamResult,
- let queriedTeam = queryTeamOptional,
- let members = queriedTeam.members else {
- print("API response: \(queryTeamResult)")
- return
- }
-
- try await members.fetch()
-
- } catch {
- print("Failed to fetch cart or customer", error)
+do {
+ let queriedTeam = try await Amplify.API.query(
+ request: .get(
+ Team.self,
+ byIdentifier: team.identifier)).get()
+
+ guard let queriedTeam, let members = queriedTeam.members else {
+ print("Missing team or members")
+ return
}
+ try await members.fetch()
+ print("Number of members: \(members.count)")
+} catch {
+ print("Failed to fetch team or members", error)
}
```
@@ -432,27 +413,20 @@ Amplify.API.query(
```swift
-Task {
- do {
- let queryTeamResult = try await Amplify.API.query(request:
- .get(
- Team.self,
- byIdentifier: "MY_TEAM_ID",
- includes: { team in
- [team.members]
- }))
- guard case .success(let queryTeamOptional) = queryTeamResult,
- let queriedTeam = queryTeamOptional,
- let members = queriedTeam.members else {
- print("API response: \(queryTeamResult)")
- return
- }
-
- try await members.fetch()
-
- } catch {
- print("Failed to fetch cart or customer", error)
+do {
+ let queriedTeamWithMembers = try await Amplify.API.query(
+ request: .get(
+ Team.self,
+ byIdentifier: team.identifier,
+ includes: { team in [team.members]}))
+ .get()
+ guard let queriedTeamWithMembers, let members = queriedTeamWithMembers.members else {
+ print("Missing team or members")
+ return
}
+ print("Number of members: \(members.count)")
+} catch {
+ print("Failed to fetch team with members", error)
}
```
@@ -535,24 +509,16 @@ Amplify.API.mutate(ModelMutation.create(customer),
```swift
-Task {
- do {
- let customer = Customer(name: "Rene")
- let createCustomerResult = try await Amplify.API.mutate(request: .create(customer))
- guard case .success = createCustomerResult else {
- print("API response: \(createCustomerResult)")
- return
- }
-
- let cart = Cart(items: ["Tomato", "Ice", "Mint"], customer: try createCustomerResult.get())
- let createCartResult = try await Amplify.API.mutate(request: .create(cart))
- guard case .success = createCartResult else {
- print("API response: \(createCartResult)")
- return
- }
- } catch {
- print("Create customer or cart failed", error)
- }
+do {
+ let customer = Customer(name: "Rene")
+ let createdCustomer = try await Amplify.API.mutate(request: .create(customer)).get()
+
+ let cart = Cart(
+ items: ["Tomato", "Ice", "Mint"],
+ customer: createdCustomer)
+ let createdCart = try await Amplify.API.mutate(request: .create(cart)).get()
+} catch {
+ print("Create customer or cart failed", error)
}
```
@@ -618,24 +584,13 @@ Amplify.API.mutate(ModelMutation.create(newCustomer),
```swift
-Task {
- do {
- let newCustomer = Customer(name: "Ian")
- let createNewCustomerResult = try await Amplify.API.mutate(request: .create(newCustomer))
- guard case .success = createNewCustomerResult else {
- print("API response: \(createNewCustomerResult)")
- return
- }
-
- let updatedCart = Cart(id: existingCart.id, items: existingCart.items, customer: try createNewCustomerResult.get())
- let updateCartResult = try await Amplify.API.mutate(request: .update(updatedCart))
- guard case .success = updateCartResult else {
- print("API response: \(updateCartResult)")
- return
- }
- } catch {
- print("Create customer or cart failed", error)
- }
+do {
+ let newCustomer = Customer(name: "Rene")
+ let newCustomerCreated = try await Amplify.API.mutate(request: .create(newCustomer)).get()
+ existingCart.setCustomer(newCustomerCreated)
+ let updatedCart = try await Amplify.API.mutate(request: .update(existingCart)).get()
+} catch {
+ print("Create customer or cart failed", error)
}
```
@@ -658,10 +613,10 @@ final cartUpdateResponse = await Amplify.API.mutate(request: cartUpdateRequest).
### Delete a "Has One" relationship between records
-You can set the relationship field to `null` to delete a "Has One" relationship between records.
-
+You can set the relationship field to `null` to delete a "Has One" relationship between records.
+
```ts
await client.models.Cart.update({
id: project.id,
@@ -673,6 +628,8 @@ await client.models.Cart.update({
+You can set the relationship field to `null` to delete a "Has One" relationship between records.
+
```kt
val cartWithRemovedCustomer = existingCart.copyOfBuilder().customer(null).build()
@@ -686,12 +643,14 @@ Amplify.API.mutate(ModelMutation.update(cartWithRemovedCustomer),
+You can set the relationship field to `nil` to delete a "Has One" relationship between records.
+
```swift
-let cartWithRemovedCustomer = Cart(id: existingCart.id, items: existingCart.items, customer: nil)
-let cartRemoveRequest = try await Amplify.API.mutate(request: .update(cartWithRemovedCustomer))
-guard case .success = cartRemoveRequest else {
- print("API response: \(cartRemoveRequest)")
- return
+do {
+ existingCart.setCustomer(nil)
+ let cartWithCustomerRemoved = try await Amplify.API.mutate(request: .update(existingCart)).get()
+} catch {
+ print("Failed to remove customer from cart", error)
}
```
@@ -715,19 +674,18 @@ final cartRemoveResponse = await Amplify.API.mutate(request: cartRemoveRequest).
```swift
-Task {
- do {
- let queryCartResult = try await Amplify.API.query(request: .get(Cart.self, byIdentifier: "MY_CART_ID"))
- guard case .success(let queryCartOptional) = queryCartResult,
- let queriedCart = queryCartOptional,
- let customer = try await queriedCart.customer else {
- print("API response: \(queryCartResult)")
- return
- }
-
- } catch {
- print("Failed to fetch cart or customer", error)
+do {
+ guard let queriedCart = try await Amplify.API.query(
+ request: .get(
+ Cart.self,
+ byIdentifier: existingCart.identifier)).get() else {
+ print("Missing cart")
+ return
}
+
+ let customer = try await queriedCart.customer
+} catch {
+ print("Failed to fetch cart or customer", error)
}
```
@@ -898,7 +856,7 @@ const schema = a.schema({
On the client-side, you can fetch the related data with the following code:
-
+
```ts
const client = generateClient();
@@ -911,6 +869,26 @@ const { data: editor } = await post?.editor();
+
+
+```swift
+ do {
+ guard let queriedPost = try await Amplify.API.query(
+ request: .get(
+ Post.self,
+ byIdentifier: post.identifier)).get() else {
+ print("Missing post")
+ return
+ }
+
+ let loadedAuthor = try await queriedPost.author
+ let loadedEditor = try await queriedPost.editor
+} catch {
+ print("Failed to fetch post, author, or editor", error)
+}
+```
+
+
## Model relationships for models with sort keys in their identifier
In cases where your data model uses sort keys in the identifier, you need to also add reference fields and store the sort key fields in the related data model:
diff --git a/src/pages/[platform]/build-a-backend/data/data-modeling/secondary-index/index.mdx b/src/pages/[platform]/build-a-backend/data/data-modeling/secondary-index/index.mdx
index e203709abac..e1d9ebb1fce 100644
--- a/src/pages/[platform]/build-a-backend/data/data-modeling/secondary-index/index.mdx
+++ b/src/pages/[platform]/build-a-backend/data/data-modeling/secondary-index/index.mdx
@@ -47,6 +47,8 @@ export const schema = a.schema({
});
```
+
+
The example client query below allows you to query for "Customer" records based on their `accountRepresentativeId`:
```ts title="src/App.tsx"
@@ -63,6 +65,44 @@ const { data, errors } =
// highlight-end
```
+
+
+
+
+The example client query below creates a custom GraphQL request that allows you to query for "Customer" records based on their `accountRepresentativeId`:
+
+```swift
+struct PaginatedList: Decodable {
+ let items: [ModelType]
+ let nextToken: String?
+}
+let operationName = "listCustomer8ByAccountRepresentativeId"
+let document = """
+query ListCustomer8ByAccountRepresentativeId {
+ \(operationName)(accountRepresentativeId: "\(accountRepresentativeId)") {
+ items {
+ createdAt
+ accountRepresentativeId
+ id
+ name
+ phoneNumber
+ updatedAt
+ }
+ nextToken
+ }
+}
+"""
+
+let request = GraphQLRequest>(
+ document: document,
+ responseType: PaginatedList.self,
+ decodePath: operationName)
+
+let queriedCustomers = try await Amplify.API.query(
+ request: request).get()
+```
+
+
Amplify uses Amazon DynamoDB tables as the default data source for `a.model()`. For key-value databases, it is critical to model your access patterns with "secondary indexes". Use the `.index()` modifier to configure a secondary index.
@@ -92,6 +132,8 @@ export const schema = a.schema({
});
```
+
+
On the client side, you should find a new `listBy...` query that's named after hash key and sort keys. For example, in this case: `listByAccountRepresentativeIdAndName`. You can supply the filter as part of this new list query:
```ts title="src/App.tsx"
@@ -105,6 +147,43 @@ const { data, errors } =
});
```
+
+
+
+
+On the client side, you can create a custom GraphQL request based on the new `listBy...` query that's named after hash key and sort keys. You can supply the filter as part of this new list query:
+
+```swift
+struct PaginatedList: Decodable {
+ let items: [ModelType]
+ let nextToken: String?
+}
+let operationName = "listCustomer9ByAccountRepresentativeIdAndName"
+let document = """
+query ListCustomer8ByAccountRepresentativeId {
+ \(operationName)(accountRepresentativeId: "\(accountRepresentativeId)", name: {beginsWith: "\(name)"}) {
+ items {
+ accountRepresentativeId
+ createdAt
+ id
+ name
+ phoneNumber
+ updatedAt
+ }
+ nextToken
+ }
+}
+"""
+let request = GraphQLRequest>(
+ document: document,
+ responseType: PaginatedList.self,
+ decodePath: operationName)
+
+let queriedCustomers = try await Amplify.API.query(
+ request: request).get()
+```
+
+
## Customize the query field for secondary indexes
You can also customize the auto-generated query name under `client.models..listBy...` by setting the `queryField()` modifier.
@@ -126,6 +205,8 @@ const schema = a.schema({
});
```
+
+
In your client app code, you'll see query updated under the Data client:
```ts title="src/App.tsx"
@@ -138,6 +219,45 @@ const {
})
```
+
+
+
+
+In your client app code, you can use the updated query name.
+
+```swift
+struct PaginatedList: Decodable {
+ let items: [ModelType]
+ let nextToken: String?
+}
+let operationName = "listByRep"
+let document = """
+query ListByRep {
+ \(operationName)(accountRepresentativeId: "\(accountRepresentativeId)") {
+ items {
+ accountRepresentativeId
+ createdAt
+ id
+ name
+ phoneNumber
+ updatedAt
+ }
+ nextToken
+ }
+}
+"""
+
+let request = GraphQLRequest>(
+ document: document,
+ responseType: PaginatedList.self,
+ decodePath: operationName)
+
+let queriedCustomers = try await Amplify.API.query(
+ request: request).get()
+```
+
+
+
## Customize the name of secondary indexes
To customize the underlying DynamoDB's index name, you can optionally provide the `name()` modifier.