Skip to content

Commit 8bfe654

Browse files
chore: add initial snippet for @Inject directive (#75)
* chore: add initial snippet for @Inject directive Signed-off-by: asararatnakar <[email protected]> * update the comment to explain the schema in README as per comment Signed-off-by: asararatnakar <[email protected]> --------- Signed-off-by: asararatnakar <[email protected]>
1 parent 433f831 commit 8bfe654

File tree

8 files changed

+631
-0
lines changed

8 files changed

+631
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ The snippets are generally broken up into functional areas, with each folder cov
5454
### Custom Directives
5555

5656
- [@dbquery](dbquery/README.md) - Use `@dbquery` for connecting to databases, including pagination and filtering.
57+
- [@inject](injection/README.md) - Use `@inject` for dependency injection, extracting context variables and making them available to multiple fields.
5758
- [@materializer](materializer) - Use of `@materializer` to extend types by linking disparate backends into a single unified view.
5859
- @rest - Connects to REST APIs
5960
- [rest](rest/README.md) - Use of `@rest` for connecting to REST endpoints, including pagination.

injection/README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# @inject
2+
3+
The `@inject` directive enables dependency injection in GraphQL schemas by allowing one field to inject expansion variables into other fields within the same request.
4+
5+
## How @inject Works
6+
7+
1. A field annotated with `@inject` is resolved first when any target field is accessed
8+
2. The injecting field must return an object with key-value pairs
9+
3. These pairs become expansion variables available to target fields
10+
4. Target fields can access these variables using the standard expansion variable syntax
11+
5. The `on` argument specifies which fields have access to the injected variables
12+
13+
## Schema Structure
14+
15+
```graphql
16+
# Inject electronic product (JSON) data as expansion variables available to any Query field
17+
_inject_electronic_products: JSON
18+
@inject(on: [{ expose: true, types: "Query", fields: ".*" }])
19+
@materializer(
20+
query: "products"
21+
arguments: { name: "category", const: "electronics" }
22+
)
23+
```
24+
25+
## Snippets
26+
27+
- [user-context](user-context) - Demonstrates simple user context injection for regional e-commerce operations with role-based filtering and currency conversion.

injection/user-context/README.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Inject user context
2+
3+
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.
4+
5+
- **Injection field**: `_inject_user_context` - automatically resolved without parameters
6+
- **Target fields**: `orders`, `products`, `recommendations` - can access injected variables
7+
- **Context variables**: `preferred_region`, `currency`, `role`, `language`, `default_user_id`
8+
9+
## How It Works
10+
11+
1. **Context Extraction**: The `_inject_user_context` field provides default user context
12+
2. **Automatic Injection**: Target fields automatically receive context as expansion variables
13+
3. **Flexible Usage**: Target fields can use optional `userId` parameters to override defaults
14+
4. **Shared Context**: Multiple operations in one request share the same injected context
15+
16+
**Note**: The injection field cannot have required parameters - it must be resolvable without arguments.
17+
18+
## Schema Structure
19+
20+
```graphql
21+
_inject_user_context: JSON
22+
@inject(on: [{ expose: true, types: "Query", fields: "orders|products|recommendations" }])
23+
@value(script: { ... }) # Returns default context
24+
```
25+
26+
## Example operations
27+
28+
### Using Default Context
29+
30+
```graphql
31+
query UserDashboardDefault {
32+
orders(limit: 3) { # Uses injected context
33+
id
34+
customerName
35+
total
36+
}
37+
products(category: "electronics") { # Uses injected context
38+
id
39+
name
40+
price
41+
}
42+
}
43+
```
44+
45+
### Overriding with Explicit Parameters
46+
47+
```graphql
48+
query UserDashboardExplicit($userId: ID!) {
49+
orders(userId: $userId, limit: 3) { # Overrides default userId
50+
id
51+
customerName
52+
total
53+
}
54+
}
55+
```
56+
57+
## Try it out
58+
59+
Deploy the schema from `injection/user-context`:
60+
61+
```bash
62+
stepzen deploy
63+
```
64+
65+
### Sample Operations
66+
67+
1. **Get Orders by UserID:**
68+
69+
```bash
70+
stepzen request -f operations.graphql --operation-name=UserOrdersExplicit --var userId=1
71+
```
72+
73+
2. **Get Products by UserID:**
74+
75+
```bash
76+
stepzen request -f operations.graphql --operation-name=UserProductsExplicit --var userId=2 --var category="electronics"
77+
```
78+
79+
3. **Get Recommendations by userId:**
80+
81+
```bash
82+
stepzen request -f operations.graphql --operation-name=UserRecommendationsExplicit --var userId=2 --var count=2
83+
```
84+
85+
4. **multiple injected operations:**
86+
87+
```bash
88+
stepzen request -f operations.graphql --operation-name=UserDashboardDefault
89+
```

injection/user-context/api.graphql

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
# A simple user context injection example to demonstrate the @inject directive.
2+
# This shows how user context can be (extracted and) injected into multiple operations.
3+
4+
type Order {
5+
id: ID!
6+
customerName: String!
7+
total: Float!
8+
status: String!
9+
region: String!
10+
}
11+
12+
type Product {
13+
id: ID!
14+
name: String!
15+
category: String!
16+
price: Float!
17+
region: String!
18+
inStock: Boolean!
19+
}
20+
21+
type Recommendation {
22+
productId: ID!
23+
productName: String!
24+
score: Float!
25+
reason: String!
26+
}
27+
28+
type Query {
29+
"""
30+
default user context becomes available as expansion variables to any field matching the visibility pattern.
31+
"""
32+
_inject_user_context: JSON
33+
@inject(
34+
on: [
35+
{
36+
expose: true
37+
types: "Query"
38+
fields: "orders|products|recommendations"
39+
}
40+
]
41+
)
42+
@value(
43+
script: {
44+
language: ECMASCRIPT
45+
src: """
46+
function getValue() {
47+
// In real applications, this could come from headers or other sources.
48+
return {
49+
"preferred_region": "US_WEST",
50+
"currency": "USD",
51+
"role": "premium",
52+
"language": "en",
53+
"default_user_id": "1"
54+
};
55+
}
56+
getValue()
57+
"""
58+
}
59+
)
60+
61+
"""
62+
Get orders filtered by user's preferred region and role.
63+
Uses injected expansion variables: preferred_region, role, default_user_id etc.,
64+
"""
65+
orders(userId: ID, limit: Int = 10): [Order]
66+
@rest(
67+
endpoint: "stepzen:empty"
68+
ecmascript: """
69+
function transformREST(s) {
70+
// Access injected expansion variables
71+
var region = get('preferred_region');
72+
var role = get('role');
73+
var defaultUserId = get('default_user_id');
74+
var userId = get('userId') || defaultUserId;
75+
var limit = get('limit');
76+
77+
// Mock orders data
78+
var allOrders = [
79+
{id: "1", customerName: "Acme Corp", total: 1500.0, status: "completed", region: "US_WEST", userId: "1"},
80+
{id: "2", customerName: "Tech Solutions", total: 850.0, status: "pending", region: "US_EAST", userId: "2"},
81+
{id: "3", customerName: "Euro Marketing", total: 1200.0, status: "completed", region: "EU_WEST", userId: "2"},
82+
{id: "4", customerName: "Asia Dynamics", total: 2500.0, status: "processing", region: "ASIA", userId: "3"},
83+
{id: "5", customerName: "West Coast Inc", total: 1800.0, status: "completed", region: "US_WEST", userId: "1"},
84+
{id: "6", customerName: "London Ltd", total: 950.0, status: "pending", region: "EU_WEST", userId: "2"}
85+
];
86+
87+
// Filter by user ID first
88+
var userOrders = allOrders.filter(function(order) {
89+
return order.userId === userId;
90+
});
91+
92+
// Filter by user's preferred region
93+
var filteredOrders = userOrders.filter(function(order) {
94+
return order.region === region;
95+
});
96+
97+
// Role-based filtering
98+
if (role === "standard") {
99+
filteredOrders = filteredOrders.filter(function(order) {
100+
return order.status === "completed";
101+
});
102+
}
103+
104+
// Apply limit
105+
if (limit && limit > 0) {
106+
filteredOrders = filteredOrders.slice(0, limit);
107+
}
108+
109+
return JSON.stringify(filteredOrders);
110+
}
111+
"""
112+
)
113+
114+
"""
115+
Get products available in user's region with currency conversion.
116+
Uses injected expansion variables: preferred_region, currency etc.,
117+
"""
118+
products(userId: ID, category: String): [Product]
119+
@rest(
120+
endpoint: "stepzen:empty"
121+
ecmascript: """
122+
function transformREST(s) {
123+
var region = get('preferred_region');
124+
var currency = get('currency');
125+
var defaultUserId = get('default_user_id');
126+
var userId = get('userId') || defaultUserId;
127+
var category = get('category');
128+
129+
// Mock products data
130+
var allProducts = [
131+
{id: "p1", name: "Laptop Pro", category: "electronics", price: 1299.99, region: "US_WEST", inStock: true},
132+
{id: "p2", name: "Office Chair", category: "furniture", price: 299.99, region: "US_WEST", inStock: true},
133+
{id: "p3", name: "EU Laptop", category: "electronics", price: 1199.99, region: "EU_WEST", inStock: true},
134+
{id: "p4", name: "EU Desk", category: "furniture", price: 399.99, region: "EU_WEST", inStock: false},
135+
{id: "p5", name: "Asia Tablet", category: "electronics", price: 599.99, region: "ASIA", inStock: true},
136+
{id: "p6", name: "Monitor 4K", category: "electronics", price: 499.99, region: "US_WEST", inStock: true}
137+
];
138+
139+
// Filter by user's preferred region
140+
var filteredProducts = allProducts.filter(function(product) {
141+
return product.region === region;
142+
});
143+
144+
// Filter by category if provided
145+
if (category) {
146+
filteredProducts = filteredProducts.filter(function(product) {
147+
return product.category === category;
148+
});
149+
}
150+
151+
// Convert currency for EUR users
152+
if (currency === "EUR") {
153+
filteredProducts = filteredProducts.map(function(product) {
154+
return Object.assign({}, product, {
155+
price: Math.round(product.price * 0.85 * 100) / 100
156+
});
157+
});
158+
}
159+
160+
return JSON.stringify(filteredProducts);
161+
}
162+
"""
163+
)
164+
165+
"""
166+
Get personalized product recommendations based on user context.
167+
Uses injected expansion variables: preferred_region, role, language etc.,
168+
"""
169+
recommendations(userId: ID, count: Int = 5): [Recommendation]
170+
@rest(
171+
endpoint: "stepzen:empty"
172+
ecmascript: """
173+
function transformREST(s) {
174+
var region = get('preferred_region');
175+
var role = get('role');
176+
var language = get('language');
177+
var defaultUserId = get('default_user_id');
178+
var userId = get('userId') || defaultUserId;
179+
var count = get('count') || 5;
180+
181+
// Mock recommendations based on region (from injected context)
182+
var recommendations = [];
183+
184+
if (region === "US_WEST") {
185+
recommendations = [
186+
{productId: "p1", productName: "Laptop Pro", score: 0.95, reason: "Popular in your region"},
187+
{productId: "p6", productName: "Monitor 4K", score: 0.88, reason: "Great for productivity"},
188+
{productId: "p2", productName: "Office Chair", score: 0.82, reason: "Highly rated locally"}
189+
];
190+
} else if (region === "EU_WEST") {
191+
recommendations = [
192+
{productId: "p3", productName: "EU Laptop", score: 0.92, reason: "EU optimized model"},
193+
{productId: "p4", productName: "EU Desk", score: 0.79, reason: "Matches local preferences"},
194+
{productId: "p7", productName: "EU Monitor", score: 0.85, reason: "Energy efficient"}
195+
];
196+
} else if (region === "ASIA") {
197+
recommendations = [
198+
{productId: "p5", productName: "Asia Tablet", score: 0.90, reason: "Regional bestseller"},
199+
{productId: "p8", productName: "Wireless Mouse", score: 0.84, reason: "Compact design"},
200+
{productId: "p9", productName: "Keyboard Pro", score: 0.81, reason: "Multi-language support"}
201+
];
202+
}
203+
204+
// Premium users get enhanced recommendations (from injected context)
205+
if (role === "premium") {
206+
recommendations.forEach(function(rec) {
207+
rec.score += 0.05;
208+
rec.reason = "Premium: " + rec.reason;
209+
});
210+
}
211+
212+
// Limit count
213+
recommendations = recommendations.slice(0, count);
214+
215+
return JSON.stringify(recommendations);
216+
}
217+
"""
218+
)
219+
}

injection/user-context/index.graphql

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
schema
2+
@sdl(
3+
files: ["api.graphql"]
4+
# Visibility controls how fields are exposed to GraphQL introspection
5+
# and field references through @materializer, @inject, etc.
6+
#
7+
# Only expose the main query fields that users should interact with.
8+
# The _inject_user_context field is hidden from external schema but
9+
# still accessible for injection into the target fields.
10+
visibility: [
11+
{
12+
expose: true
13+
types: "Query"
14+
fields: "orders|products|recommendations"
15+
}
16+
]
17+
) {
18+
query: Query
19+
}

0 commit comments

Comments
 (0)