Initial commit
This commit is contained in:
@@ -0,0 +1,303 @@
|
||||
# Advanced Features Reference
|
||||
|
||||
## Contents
|
||||
- [Vector Similarity Search](#vector-similarity-search)
|
||||
- [Full-Text Search](#full-text-search)
|
||||
- [Cloud Functions Integration](#cloud-functions-integration)
|
||||
- [Data Seeding & Bulk Operations](#data-seeding--bulk-operations)
|
||||
|
||||
---
|
||||
|
||||
## Vector Similarity Search
|
||||
|
||||
Semantic search using Vertex AI embeddings and PostgreSQL's `pgvector`.
|
||||
|
||||
### Schema Setup
|
||||
|
||||
```graphql
|
||||
type Movie @table {
|
||||
id: UUID! @default(expr: "uuidV4()")
|
||||
title: String!
|
||||
description: String
|
||||
# Vector field for embeddings - size must match model output (768 for gecko)
|
||||
descriptionEmbedding: Vector! @col(size: 768)
|
||||
}
|
||||
```
|
||||
|
||||
### Generate Embeddings in Mutations
|
||||
|
||||
Use `_embed` server value to auto-generate embeddings via Vertex AI:
|
||||
|
||||
```graphql
|
||||
mutation CreateMovieWithEmbedding($title: String!, $description: String!)
|
||||
@auth(level: USER) {
|
||||
movie_insert(data: {
|
||||
title: $title,
|
||||
description: $description,
|
||||
descriptionEmbedding_embed: {
|
||||
model: "textembedding-gecko@003",
|
||||
text: $description
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Similarity Search Query
|
||||
|
||||
Data Connect generates `_similarity` fields for Vector columns:
|
||||
|
||||
```graphql
|
||||
query SearchMovies($query: String!) @auth(level: PUBLIC) {
|
||||
movies_descriptionEmbedding_similarity(
|
||||
compare_embed: { model: "textembedding-gecko@003", text: $query },
|
||||
method: L2, # L2, COSINE, or INNER_PRODUCT
|
||||
within: 2.0, # Max distance threshold
|
||||
limit: 5
|
||||
) {
|
||||
id
|
||||
title
|
||||
description
|
||||
_metadata { distance } # See how close each result is
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Similarity Parameters
|
||||
|
||||
| Parameter | Description |
|
||||
|-----------|-------------|
|
||||
| `compare` | Raw Vector to compare against |
|
||||
| `compare_embed` | Generate embedding from text via Vertex AI |
|
||||
| `method` | Distance function: `L2`, `COSINE`, `INNER_PRODUCT` |
|
||||
| `within` | Max distance (results further are excluded) |
|
||||
| `where` | Additional filters |
|
||||
| `limit` | Max results to return |
|
||||
|
||||
### Custom Embeddings
|
||||
|
||||
Pass pre-computed vectors directly:
|
||||
|
||||
```graphql
|
||||
mutation StoreCustomEmbedding($id: UUID!, $embedding: Vector!) @auth(level: USER) {
|
||||
movie_update(id: $id, data: { descriptionEmbedding: $embedding })
|
||||
}
|
||||
|
||||
query SearchWithCustomVector($vector: Vector!) @auth(level: PUBLIC) {
|
||||
movies_descriptionEmbedding_similarity(
|
||||
compare: $vector,
|
||||
method: COSINE,
|
||||
limit: 10
|
||||
) { id title }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Full-Text Search
|
||||
|
||||
Fast keyword/phrase search using PostgreSQL's full-text capabilities.
|
||||
|
||||
### Enable with @searchable
|
||||
|
||||
```graphql
|
||||
type Movie @table {
|
||||
title: String! @searchable
|
||||
description: String @searchable(language: "english")
|
||||
genre: String @searchable
|
||||
}
|
||||
```
|
||||
|
||||
### Search Query
|
||||
|
||||
Data Connect generates `_search` fields:
|
||||
|
||||
```graphql
|
||||
query SearchMovies($query: String!) @auth(level: PUBLIC) {
|
||||
movies_search(
|
||||
query: $query,
|
||||
queryFormat: QUERY, # QUERY, PLAIN, PHRASE, or ADVANCED
|
||||
limit: 20
|
||||
) {
|
||||
id title description
|
||||
_metadata { relevance } # Relevance score
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Query Formats
|
||||
|
||||
| Format | Description |
|
||||
|--------|-------------|
|
||||
| `QUERY` | Web-style (default): quotes, AND, OR supported |
|
||||
| `PLAIN` | Match all words, any order |
|
||||
| `PHRASE` | Match exact phrase |
|
||||
| `ADVANCED` | Full tsquery syntax |
|
||||
|
||||
### Tuning Results
|
||||
|
||||
```graphql
|
||||
query SearchWithThreshold($query: String!) @auth(level: PUBLIC) {
|
||||
movies_search(
|
||||
query: $query,
|
||||
relevanceThreshold: 0.05, # Min relevance score
|
||||
where: { genre: { eq: "Action" }},
|
||||
orderBy: [{ releaseYear: DESC }]
|
||||
) { id title }
|
||||
}
|
||||
```
|
||||
|
||||
### Supported Languages
|
||||
|
||||
`english` (default), `french`, `german`, `spanish`, `italian`, `portuguese`, `dutch`, `danish`, `finnish`, `norwegian`, `swedish`, `russian`, `arabic`, `hindi`, `simple`
|
||||
|
||||
---
|
||||
|
||||
## Cloud Functions Integration
|
||||
|
||||
Trigger Cloud Functions when mutations execute.
|
||||
|
||||
### Basic Trigger (Node.js)
|
||||
|
||||
```typescript
|
||||
import { onMutationExecuted } from "firebase-functions/dataconnect";
|
||||
import { logger } from "firebase-functions";
|
||||
|
||||
export const onUserCreate = onMutationExecuted(
|
||||
{
|
||||
service: "myService",
|
||||
connector: "default",
|
||||
operation: "CreateUser",
|
||||
region: "us-central1" // Must match Data Connect location
|
||||
},
|
||||
(event) => {
|
||||
const variables = event.data.payload.variables;
|
||||
const returnedData = event.data.payload.data;
|
||||
|
||||
logger.info("User created:", returnedData);
|
||||
// Send welcome email, sync to analytics, etc.
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
### Basic Trigger (Python)
|
||||
|
||||
```python
|
||||
from firebase_functions import dataconnect_fn, logger
|
||||
|
||||
@dataconnect_fn.on_mutation_executed(
|
||||
service="myService",
|
||||
connector="default",
|
||||
operation="CreateUser"
|
||||
)
|
||||
def on_user_create(event: dataconnect_fn.Event):
|
||||
variables = event.data.payload.variables
|
||||
returned_data = event.data.payload.data
|
||||
logger.info("User created:", returned_data)
|
||||
```
|
||||
|
||||
### Event Data
|
||||
|
||||
```typescript
|
||||
// event.authType: "app_user" | "unauthenticated" | "admin"
|
||||
// event.authId: Firebase Auth UID (for app_user)
|
||||
// event.data.payload.variables: mutation input variables
|
||||
// event.data.payload.data: mutation response data
|
||||
// event.data.payload.errors: any errors that occurred
|
||||
```
|
||||
|
||||
### Filtering with Wildcards
|
||||
|
||||
```typescript
|
||||
// Trigger on all User* mutations
|
||||
export const onUserMutation = onMutationExecuted(
|
||||
{ operation: "User*" },
|
||||
(event) => { /* ... */ }
|
||||
);
|
||||
|
||||
// Capture operation name
|
||||
export const onAnyMutation = onMutationExecuted(
|
||||
{ service: "myService", operation: "{operationName}" },
|
||||
(event) => {
|
||||
console.log("Operation:", event.params.operationName);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
### Use Cases
|
||||
|
||||
- **Data sync**: Replicate to Firestore, BigQuery, external APIs
|
||||
- **Notifications**: Send emails, push notifications on events
|
||||
- **Async workflows**: Image processing, data aggregation
|
||||
- **Audit logging**: Track all data changes
|
||||
|
||||
> ⚠️ **Avoid infinite loops**: Don't trigger mutations that would fire the same trigger. Use filters to exclude self-triggered events.
|
||||
|
||||
---
|
||||
|
||||
## Data Seeding & Bulk Operations
|
||||
|
||||
### Local Prototyping with _insertMany
|
||||
|
||||
```graphql
|
||||
mutation SeedMovies @transaction {
|
||||
movie_insertMany(data: [
|
||||
{ id: "uuid-1", title: "Movie 1", genre: "Action" },
|
||||
{ id: "uuid-2", title: "Movie 2", genre: "Drama" },
|
||||
{ id: "uuid-3", title: "Movie 3", genre: "Comedy" }
|
||||
])
|
||||
}
|
||||
```
|
||||
|
||||
### Reset Data with _upsertMany
|
||||
|
||||
```graphql
|
||||
mutation ResetData {
|
||||
movie_upsertMany(data: [
|
||||
{ id: "uuid-1", title: "Movie 1", genre: "Action" },
|
||||
{ id: "uuid-2", title: "Movie 2", genre: "Drama" }
|
||||
])
|
||||
}
|
||||
```
|
||||
|
||||
### Clear All Data
|
||||
|
||||
```graphql
|
||||
mutation ClearMovies {
|
||||
movie_deleteMany(all: true)
|
||||
}
|
||||
```
|
||||
|
||||
### Production: Admin SDK Bulk Operations
|
||||
|
||||
```typescript
|
||||
import { initializeApp } from 'firebase-admin/app';
|
||||
import { getDataConnect } from 'firebase-admin/data-connect';
|
||||
|
||||
const app = initializeApp();
|
||||
const dc = getDataConnect({ location: "us-central1", serviceId: "my-service" });
|
||||
|
||||
const movies = [
|
||||
{ id: "uuid-1", title: "Movie 1", genre: "Action" },
|
||||
{ id: "uuid-2", title: "Movie 2", genre: "Drama" }
|
||||
];
|
||||
|
||||
// Bulk insert
|
||||
await dc.insertMany("movie", movies);
|
||||
|
||||
// Bulk upsert
|
||||
await dc.upsertMany("movie", movies);
|
||||
|
||||
// Single operations
|
||||
await dc.insert("movie", movies[0]);
|
||||
await dc.upsert("movie", movies[0]);
|
||||
```
|
||||
|
||||
### Emulator Data Persistence
|
||||
|
||||
```bash
|
||||
# Export emulator data
|
||||
npx -y firebase-tools@latest emulators:export ./seed-data
|
||||
|
||||
# Start with saved data
|
||||
npx -y firebase-tools@latest emulators:start --only dataconnect --import=./seed-data
|
||||
```
|
||||
Reference in New Issue
Block a user