-
Notifications
You must be signed in to change notification settings - Fork 4
chore: add initial snippet for @inject directive #75
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# @inject | ||
|
||
The `@inject` directive enables dependency injection in GraphQL schemas by allowing one field to inject expansion variables into other fields within the same request. | ||
|
||
## How @inject Works | ||
|
||
1. A field annotated with `@inject` is resolved first when any target field is accessed | ||
2. The injecting field must return an object with key-value pairs | ||
3. These pairs become expansion variables available to target fields | ||
4. Target fields can access these variables using the standard expansion variable syntax | ||
5. The `on` argument specifies which fields have access to the injected variables | ||
|
||
## Schema Structure | ||
|
||
```graphql | ||
# Extract expansion variables from X into any selection of `Query.*` | ||
_inject_x: JSON | ||
@inject(on: [{ expose: true, types: "Query", fields: ".*" }]) | ||
@materializer( | ||
query: "products" | ||
arguments: { name: "category", const: "electronics" } | ||
) | ||
``` | ||
|
||
## Snippets | ||
|
||
- [user-context](user-context) - Demonstrates simple user context injection for regional e-commerce operations with role-based filtering and currency conversion. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
# Inject user context | ||
|
||
This example demonstrates how to use the `@inject` directive.It provides a powerful way to extract context information (like user preferences, regional settings, and role) and make it available as expansion variables to multiple fields in a single request. This enables clean, reusable context patterns without repetitive parameter passing. | ||
|
||
- **Injection field**: `_inject_user_context` - automatically resolved without parameters | ||
- **Target fields**: `orders`, `products`, `recommendations` - can access injected variables | ||
- **Context variables**: `preferred_region`, `currency`, `role`, `language`, `default_user_id` | ||
|
||
## How It Works | ||
|
||
1. **Context Extraction**: The `_inject_user_context` field provides default user context | ||
2. **Automatic Injection**: Target fields automatically receive context as expansion variables | ||
3. **Flexible Usage**: Target fields can use optional `userId` parameters to override defaults | ||
4. **Shared Context**: Multiple operations in one request share the same injected context | ||
|
||
**Note**: The injection field cannot have required parameters - it must be resolvable without arguments. | ||
|
||
## Schema Structure | ||
|
||
```graphql | ||
_inject_user_context: JSON | ||
@inject(on: [{ expose: true, types: "Query", fields: "orders|products|recommendations" }]) | ||
@value(script: { ... }) # Returns default context | ||
``` | ||
|
||
## Example operations | ||
|
||
### Using Default Context | ||
|
||
```graphql | ||
query UserDashboardDefault { | ||
orders(limit: 3) { # Uses injected context | ||
id | ||
customerName | ||
total | ||
} | ||
products(category: "electronics") { # Uses injected context | ||
id | ||
name | ||
price | ||
} | ||
} | ||
``` | ||
|
||
### Overriding with Explicit Parameters | ||
|
||
```graphql | ||
query UserDashboardExplicit($userId: ID!) { | ||
orders(userId: $userId, limit: 3) { # Overrides default userId | ||
id | ||
customerName | ||
total | ||
} | ||
} | ||
``` | ||
|
||
## Try it out | ||
|
||
Deploy the schema from `injection/user-context`: | ||
|
||
```bash | ||
stepzen deploy | ||
``` | ||
|
||
### Sample Operations | ||
|
||
1. **Get Orders by UserID:** | ||
|
||
```bash | ||
stepzen request -f operations.graphql --operation-name=UserOrdersExplicit --var userId=1 | ||
``` | ||
|
||
2. **Get Products by UserID:** | ||
|
||
```bash | ||
stepzen request -f operations.graphql --operation-name=UserProductsExplicit --var userId=2 --var category="electronics" | ||
``` | ||
|
||
3. **Get Recommendations by userId:** | ||
|
||
```bash | ||
stepzen request -f operations.graphql --operation-name=UserRecommendationsExplicit --var userId=2 --var count=2 | ||
``` | ||
|
||
4. **multiple injected operations:** | ||
|
||
```bash | ||
stepzen request -f operations.graphql --operation-name=UserDashboardDefault | ||
``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,219 @@ | ||
# A simple user context injection example to demonstrate the @inject directive. | ||
# This shows how user context can be (extracted and) injected into multiple operations. | ||
|
||
type Order { | ||
id: ID! | ||
customerName: String! | ||
total: Float! | ||
status: String! | ||
region: String! | ||
} | ||
|
||
type Product { | ||
id: ID! | ||
name: String! | ||
category: String! | ||
price: Float! | ||
region: String! | ||
inStock: Boolean! | ||
} | ||
|
||
type Recommendation { | ||
productId: ID! | ||
productName: String! | ||
score: Float! | ||
reason: String! | ||
} | ||
|
||
type Query { | ||
""" | ||
default user context becomes available as expansion variables to any field matching the visibility pattern. | ||
""" | ||
_inject_user_context: JSON | ||
@inject( | ||
on: [ | ||
{ | ||
expose: true | ||
types: "Query" | ||
fields: "orders|products|recommendations" | ||
} | ||
] | ||
) | ||
@value( | ||
script: { | ||
language: ECMASCRIPT | ||
src: """ | ||
function getValue() { | ||
// In real applications, this could come from headers or other sources. | ||
return { | ||
"preferred_region": "US_WEST", | ||
"currency": "USD", | ||
"role": "premium", | ||
"language": "en", | ||
"default_user_id": "1" | ||
}; | ||
} | ||
getValue() | ||
""" | ||
} | ||
) | ||
|
||
""" | ||
Get orders filtered by user's preferred region and role. | ||
Uses injected expansion variables: preferred_region, role, default_user_id etc., | ||
""" | ||
orders(userId: ID, limit: Int = 10): [Order] | ||
@rest( | ||
endpoint: "stepzen:empty" | ||
ecmascript: """ | ||
function transformREST(s) { | ||
// Access injected expansion variables | ||
var region = get('preferred_region'); | ||
var role = get('role'); | ||
var defaultUserId = get('default_user_id'); | ||
var userId = get('userId') || defaultUserId; | ||
var limit = get('limit'); | ||
|
||
// Mock orders data | ||
var allOrders = [ | ||
{id: "1", customerName: "Acme Corp", total: 1500.0, status: "completed", region: "US_WEST", userId: "1"}, | ||
{id: "2", customerName: "Tech Solutions", total: 850.0, status: "pending", region: "US_EAST", userId: "2"}, | ||
{id: "3", customerName: "Euro Marketing", total: 1200.0, status: "completed", region: "EU_WEST", userId: "2"}, | ||
{id: "4", customerName: "Asia Dynamics", total: 2500.0, status: "processing", region: "ASIA", userId: "3"}, | ||
{id: "5", customerName: "West Coast Inc", total: 1800.0, status: "completed", region: "US_WEST", userId: "1"}, | ||
{id: "6", customerName: "London Ltd", total: 950.0, status: "pending", region: "EU_WEST", userId: "2"} | ||
]; | ||
|
||
// Filter by user ID first | ||
var userOrders = allOrders.filter(function(order) { | ||
return order.userId === userId; | ||
}); | ||
|
||
// Filter by user's preferred region | ||
var filteredOrders = userOrders.filter(function(order) { | ||
return order.region === region; | ||
}); | ||
|
||
// Role-based filtering | ||
if (role === "standard") { | ||
filteredOrders = filteredOrders.filter(function(order) { | ||
return order.status === "completed"; | ||
}); | ||
} | ||
|
||
// Apply limit | ||
if (limit && limit > 0) { | ||
filteredOrders = filteredOrders.slice(0, limit); | ||
} | ||
|
||
return JSON.stringify(filteredOrders); | ||
} | ||
""" | ||
) | ||
|
||
""" | ||
Get products available in user's region with currency conversion. | ||
Uses injected expansion variables: preferred_region, currency etc., | ||
""" | ||
products(userId: ID, category: String): [Product] | ||
@rest( | ||
endpoint: "stepzen:empty" | ||
ecmascript: """ | ||
function transformREST(s) { | ||
var region = get('preferred_region'); | ||
var currency = get('currency'); | ||
var defaultUserId = get('default_user_id'); | ||
var userId = get('userId') || defaultUserId; | ||
var category = get('category'); | ||
|
||
// Mock products data | ||
var allProducts = [ | ||
{id: "p1", name: "Laptop Pro", category: "electronics", price: 1299.99, region: "US_WEST", inStock: true}, | ||
{id: "p2", name: "Office Chair", category: "furniture", price: 299.99, region: "US_WEST", inStock: true}, | ||
{id: "p3", name: "EU Laptop", category: "electronics", price: 1199.99, region: "EU_WEST", inStock: true}, | ||
{id: "p4", name: "EU Desk", category: "furniture", price: 399.99, region: "EU_WEST", inStock: false}, | ||
{id: "p5", name: "Asia Tablet", category: "electronics", price: 599.99, region: "ASIA", inStock: true}, | ||
{id: "p6", name: "Monitor 4K", category: "electronics", price: 499.99, region: "US_WEST", inStock: true} | ||
]; | ||
|
||
// Filter by user's preferred region | ||
var filteredProducts = allProducts.filter(function(product) { | ||
return product.region === region; | ||
}); | ||
|
||
// Filter by category if provided | ||
if (category) { | ||
filteredProducts = filteredProducts.filter(function(product) { | ||
return product.category === category; | ||
}); | ||
} | ||
|
||
// Convert currency for EUR users | ||
if (currency === "EUR") { | ||
filteredProducts = filteredProducts.map(function(product) { | ||
return Object.assign({}, product, { | ||
price: Math.round(product.price * 0.85 * 100) / 100 | ||
}); | ||
}); | ||
} | ||
|
||
return JSON.stringify(filteredProducts); | ||
} | ||
""" | ||
) | ||
|
||
""" | ||
Get personalized product recommendations based on user context. | ||
Uses injected expansion variables: preferred_region, role, language etc., | ||
""" | ||
recommendations(userId: ID, count: Int = 5): [Recommendation] | ||
@rest( | ||
endpoint: "stepzen:empty" | ||
ecmascript: """ | ||
function transformREST(s) { | ||
var region = get('preferred_region'); | ||
var role = get('role'); | ||
var language = get('language'); | ||
var defaultUserId = get('default_user_id'); | ||
var userId = get('userId') || defaultUserId; | ||
var count = get('count') || 5; | ||
|
||
// Mock recommendations based on region (from injected context) | ||
var recommendations = []; | ||
|
||
if (region === "US_WEST") { | ||
recommendations = [ | ||
{productId: "p1", productName: "Laptop Pro", score: 0.95, reason: "Popular in your region"}, | ||
{productId: "p6", productName: "Monitor 4K", score: 0.88, reason: "Great for productivity"}, | ||
{productId: "p2", productName: "Office Chair", score: 0.82, reason: "Highly rated locally"} | ||
]; | ||
} else if (region === "EU_WEST") { | ||
recommendations = [ | ||
{productId: "p3", productName: "EU Laptop", score: 0.92, reason: "EU optimized model"}, | ||
{productId: "p4", productName: "EU Desk", score: 0.79, reason: "Matches local preferences"}, | ||
{productId: "p7", productName: "EU Monitor", score: 0.85, reason: "Energy efficient"} | ||
]; | ||
} else if (region === "ASIA") { | ||
recommendations = [ | ||
{productId: "p5", productName: "Asia Tablet", score: 0.90, reason: "Regional bestseller"}, | ||
{productId: "p8", productName: "Wireless Mouse", score: 0.84, reason: "Compact design"}, | ||
{productId: "p9", productName: "Keyboard Pro", score: 0.81, reason: "Multi-language support"} | ||
]; | ||
} | ||
|
||
// Premium users get enhanced recommendations (from injected context) | ||
if (role === "premium") { | ||
recommendations.forEach(function(rec) { | ||
rec.score += 0.05; | ||
rec.reason = "Premium: " + rec.reason; | ||
}); | ||
} | ||
|
||
// Limit count | ||
recommendations = recommendations.slice(0, count); | ||
|
||
return JSON.stringify(recommendations); | ||
} | ||
""" | ||
) | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
schema | ||
@sdl( | ||
files: ["api.graphql"] | ||
# Visibility controls how fields are exposed to GraphQL introspection | ||
# and field references through @materializer, @inject, etc. | ||
# | ||
# Only expose the main query fields that users should interact with. | ||
# The _inject_user_context field is hidden from external schema but | ||
# still accessible for injection into the target fields. | ||
visibility: [ | ||
{ | ||
expose: true | ||
types: "Query" | ||
fields: "orders|products|recommendations" | ||
} | ||
] | ||
) { | ||
query: Query | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
from X
, what's X here?