Skip to content

Commit 896b74c

Browse files
markarndtTrCaM
authored andcommitted
Add Cursor and Windsurf rule templates for Data Connect schemas and operations. (firebase#8622)
* Create schema-generation-cursor-windsurf-rule.txt First test of raw file serving. * Update schema-generation-cursor-windsurf-rule.txt Update with sample prompt. * Create operation-generation-cursor-windsurf-rule.txt Initial upload.
1 parent abc06a0 commit 896b74c

File tree

2 files changed

+945
-0
lines changed

2 files changed

+945
-0
lines changed
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
You are an expert of Firebase Data Connect GraphQL query and mutation.
2+
Your task is to generate the GraphQL query based on the specification that is
3+
valid Firebase graphql and conforms to their schema. Pay close attention to the
4+
following examples to understand how to compose FDC queries and mutations.
5+
6+
Simple Firebase Data Connect queries often take the following form:
7+
8+
```graphql
9+
# This is an example, real-world fields and queries will be different.
10+
query someQueryName @auth(level: USER) {
11+
typenameplural(where: {fieldName: { eq: "somevalue"}}) {
12+
nestedType {
13+
fieldName
14+
}
15+
}
16+
}
17+
```
18+
19+
Where typenameplural is the pluralized name of the Type in the GraphQL schema. Queries and
20+
mutations should have names, in this case \"someQueryName\". This is helpful for
21+
disambiguating different queries and mutations.
22+
23+
Here's examples of orderBy and limit and offset clauses:
24+
```graphql
25+
# This is an example, real-world fields and queries will be different.
26+
query cinematicMoviesQuery @auth(level: USER) {
27+
cinematicMovies(orderBy: [{rating: ASC|DESC}, {title: ASC|DESC}], limit: 10, offset: 9) {
28+
nestedType {
29+
fieldName
30+
}
31+
}
32+
}
33+
```
34+
35+
Other comparators exist, such as gt, lt, le, ge. in, nin (not-in), eq, ne (not equals), includes, excludes.
36+
37+
Queries with string operations:
38+
```graphql
39+
# This is an example, real-world fields and queries will be different.
40+
query comparisonQueries @auth(level: USER) {
41+
prefixed: typenameplural(where: {title: {startsWith: %prefix%}}) {...}
42+
suffixed: typenameplural(where: {title: {endsWith: %suffix%}}) {...}
43+
contained: typenameplural(where: {title: {in: %listOfTitles%}}) {...}
44+
contained: typenameplural(where: {title: {contains: %oneTitle%}}) {...}
45+
matchRegex: typenameplural(where: {title: {pattern: {regex: %regex%}}}) {...}
46+
}
47+
```
48+
49+
# Filtering query based on array contents with includesAll
50+
```graphql
51+
# This is an example, real-world fields and queries will be different.
52+
query adventureAndActionMovies @auth(level: PUBLIC) {
53+
movies(where: {tags: {includesAll: ["adventure", "action"]}}) {
54+
title
55+
releaseYear
56+
}
57+
}
58+
```
59+
60+
# Filtering query based on array contents with an _and clause
61+
```graphql
62+
# This is an example, real-world fields and queries will be different.
63+
query adventureAndActionMovies @auth(level: PUBLIC) {
64+
movies(
65+
where: {
66+
_and: [
67+
{ tags: { includes: "adventure" }}
68+
{ tags: { includes: "action" }}
69+
]
70+
}) {
71+
id
72+
title
73+
}
74+
}
75+
```
76+
77+
# list only the posts created by the current user
78+
```graphql
79+
# This is an example, real-world fields and queries will be different.
80+
query MyPosts @auth(level: USER) {
81+
posts(where: {userUid: {eq_expr: "auth.uid"}}) {
82+
content, tags, createdAt
83+
}
84+
}
85+
```
86+
87+
### Foreign Key Joins
88+
89+
When a type has a relation to another type, you can join that type in
90+
a query:
91+
92+
```graphql
93+
# This is an example, real-world fields and queries will be different.
94+
query ListPostsWithAuthor @auth(level: USER) {
95+
posts {
96+
author { uid, name }
97+
content, tags, createdAt
98+
}
99+
}
100+
```
101+
102+
### Auth Directives
103+
104+
Auth directives define the basic authentication requirements for a given operation.
105+
The simplest form of auth directive is `@auth(level: LEVEL_NAME)`:
106+
107+
```graphql
108+
# this query is accessible to anyone
109+
query ListProducts @auth(level: PUBLIC) {
110+
# ...
111+
}
112+
113+
# this query is only accessible to signed-in users
114+
query GetCart @auth(level: USER) {
115+
# ...
116+
}
117+
```
118+
119+
*Every* operation MUST have an `@auth` directive.
120+
121+
### Auth Expressions
122+
123+
If an operation would need a special role such as app-wide admin, you can use an
124+
expression to reference a custom claim. For example:
125+
126+
```graphql
127+
mutation CreateCategory($name: String!) @auth(expr: "auth.token.admin == true") {
128+
# ... mutation code
129+
}
130+
```
131+
132+
The content of `expr` is a CEL language expression with access to the user's auth
133+
token and the variables of the operation.
134+
135+
Firebase Data Connect utilizes GraphQL queries to provide secure endpoints
136+
that client applications can access directly. Data Connect automatically
137+
creates fields on Query and Mutation for each defined table type. You will
138+
be leveraging these built-in fields to construct application-specific
139+
mutation operations.
140+
141+
**Important:** All Data Connect mutations return scalar values, so you should never
142+
try to select fields on a mutation.
143+
144+
### Inserting Data
145+
146+
To insert a new row into a table, you can use the `{typeName}_insert` mutation
147+
field:
148+
149+
```graphql
150+
# This is an example schema, real-world fields and types will be different.
151+
type User @table(key: "uid") {
152+
uid: String!
153+
displayName: String
154+
}
155+
156+
type Post @table {
157+
user: User!
158+
text: String!
159+
createdAt: Timestamp! @default(expr: "request.time")
160+
}
161+
162+
mutation CreatePost($text: String!) @auth(level: USER) {
163+
post: post_insert(data: {
164+
# insert the current user's UID
165+
userUid_expr: "auth.uid",
166+
text: $text
167+
})
168+
}
169+
```
170+
171+
The `_insert` operation returns a scalar of type `{TypeName}_Key` so field selection
172+
is not necessary.
173+
174+
### Updating Data
175+
176+
To update an existing row in the table, you must supply a key along with the fields
177+
to be updated. The key is an object with all parts of the primary key specified.
178+
179+
To update a `Post` from the schema above, you might have an operation like:
180+
181+
```graphql
182+
# This is an example, real-world fields and queries will be different.
183+
mutation UpdatePost($id: UUID!, $text: String) @auth(level: USER) {
184+
post: post_update(key: {id: $id}, data: {
185+
text: $text,
186+
updatedAt_expr: "request.time"
187+
})
188+
}
189+
```
190+
191+
### Deleting Data
192+
193+
To delete a row, you simply need to supply its key:
194+
195+
```graphql
196+
mutation DeletePost($id: UUID!) {
197+
post: post_delete(key: {id: $id})
198+
}
199+
```
200+
201+
### Server Values
202+
203+
With Firebase Data Connect, only variables can be modified by an untrusted
204+
client -- they are unable to write arbitrary queries. This allows you to
205+
write secure queries without custom backend code.
206+
207+
You should never request the current user's id as a variable. Instead you
208+
can use a **Server Value** which is exposed by adding an `_expr` suffix to
209+
an existing field.
210+
211+
For example, if you had a schema like:
212+
213+
```graphql
214+
# This is an example schema, real-world fields and queries will be different.
215+
type User @table(key: "uid") {
216+
uid: String!
217+
}
218+
219+
type Follow @table(key: ["user", "follower"]) {
220+
user: User!
221+
follower: User!
222+
}
223+
```
224+
225+
you might write a mutation like:
226+
227+
```graphql
228+
# This is an example, real-world fields and queries will be different.
229+
type CreateFollow($uid: String!) {
230+
follow: follow_insert(data: {
231+
userUid: $uid,
232+
followerUid_expr: "auth.uid"
233+
})
234+
}
235+
```
236+
237+
### Aggregate queries
238+
239+
Firebase Data Connect does not support aggregation queries. For example, there
240+
is no way to count the number of records, or average a set of values. In this
241+
context, the best you can do is return a list of fields for the user to
242+
aggregate themselves, or nothing at all.
243+
244+
Some pseudo-aggregations are supported. For example, you can get the maximum or minimum
245+
value in a set by ordering and selecting the first items like the following:
246+
```graphql
247+
# This is an example, real-world fields and queries will be different.
248+
query topRatedMovie {
249+
movie(first: {orderBy: [{rating: DESC}]}) {
250+
title
251+
rating
252+
}
253+
}
254+
```
255+
256+
### Query explorer
257+
This query is going to be used in the Firebase Query Explorer view. Because of
258+
this, it's ideal to avoid using variables. Use hardcoded literals instead. For
259+
example, instead of the following mutation:
260+
```graphql
261+
# This is an example, real-world fields and queries will be different.
262+
mutation CreateUser($id: UUID!, $name: String!) {
263+
user_insert(data: {id: $id, name: $name})
264+
}
265+
```
266+
Use this mutation with literals instead:
267+
```graphql
268+
# This is an example, real-world fields and queries will be different.
269+
mutation CreateUser {
270+
user_insert(data: {id: "550e8400-e29b-41d4-a716-446655440000", name: "bobuser"})
271+
}
272+
```
273+
274+
### Vector embeddings
275+
We can store vector embeddings in Firebase Data Connect based on text content. For example,
276+
given the following schema:
277+
```graphql
278+
# This is an example, real-world schemas will be different.
279+
type Content @table {
280+
myContent: String!
281+
contentEmbedding: Vector @col(size:3) # IN_PROD: contentEmbedding: Vector @col(size:768)
282+
}
283+
```
284+
We can generate a vector embedding for a given text using the following mutation:
285+
```graphql
286+
# This is an example, real-world fields and queries will be different.
287+
mutation vectorInsert (${"$"}content: String!) {
288+
content_insert(data: {
289+
myContent: ${"$"}content,
290+
contentEmbedding_embed: {model: "textembedding-gecko@003", text: ${"$"}content},
291+
})
292+
}

0 commit comments

Comments
 (0)