Skip to content

Commit b6b88f3

Browse files
authored
Merge pull request #112 from tlivings/patch-602
Patch 602
2 parents b751934 + 326b9d3 commit b6b88f3

17 files changed

+3276
-674
lines changed

.cursor/rules/components.mdc

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
# Component Creation Patterns
2+
3+
## Class Structure
4+
5+
### Extending GraphQLComponent
6+
- Always extend `GraphQLComponent` class
7+
- Implement constructor with options spread pattern
8+
- Use TypeScript for type safety
9+
10+
```typescript
11+
import GraphQLComponent from 'graphql-component';
12+
import { types } from './types';
13+
import { resolvers } from './resolvers';
14+
import MyDataSource from './datasource';
15+
16+
export default class MyComponent extends GraphQLComponent {
17+
constructor({ dataSources = [new MyDataSource()], ...options } = {}) {
18+
super({ types, resolvers, dataSources, ...options });
19+
}
20+
}
21+
```
22+
23+
### Constructor Pattern
24+
- Default empty object parameter: `= {}`
25+
- Default data sources with spread: `dataSources = [new MyDataSource()]`
26+
- Spread remaining options: `...options`
27+
- Pass all to super: `super({ types, resolvers, dataSources, ...options })`
28+
29+
### Component References (for Delegation)
30+
- Store component instances as properties when needed for delegation
31+
- Initialize imported components in constructor
32+
33+
```typescript
34+
export default class ListingComponent extends GraphQLComponent {
35+
propertyComponent: PropertyComponent;
36+
reviewsComponent: ReviewsComponent;
37+
38+
constructor(options) {
39+
const propertyComponent = new PropertyComponent();
40+
const reviewsComponent = new ReviewsComponent();
41+
42+
super({
43+
types,
44+
resolvers,
45+
imports: [propertyComponent, reviewsComponent],
46+
...options
47+
});
48+
49+
this.propertyComponent = propertyComponent;
50+
this.reviewsComponent = reviewsComponent;
51+
}
52+
}
53+
```
54+
55+
## File Organization
56+
57+
### Standard Structure
58+
```
59+
my-component/
60+
├── index.ts # Component class (default export)
61+
├── types.ts # Schema loader
62+
├── resolvers.ts # Resolver map (named export)
63+
├── schema.graphql # GraphQL SDL
64+
└── datasource.ts # Data source class (default export)
65+
```
66+
67+
### Schema Loading Pattern
68+
- Use fs.readFileSync for .graphql files
69+
- Export as named export `types`
70+
71+
```typescript
72+
// types.ts
73+
import fs from 'fs';
74+
import path from 'path';
75+
76+
export const types = fs.readFileSync(
77+
path.resolve(path.join(__dirname, 'schema.graphql')),
78+
'utf-8'
79+
);
80+
```
81+
82+
### Resolver Export Pattern
83+
- Export as named export `resolvers`
84+
- Use object literal format
85+
86+
```typescript
87+
// resolvers.ts
88+
export const resolvers = {
89+
Query: {
90+
myField(_, args, context) {
91+
return context.dataSources.MyDataSource.getData(args.id);
92+
}
93+
}
94+
};
95+
```
96+
97+
## Federation vs Composition
98+
99+
### Composition Components
100+
- Use `imports` to include other components
101+
- Use `delegateToSchema` for cross-component calls
102+
- No federation flag needed
103+
104+
```typescript
105+
const component = new GraphQLComponent({
106+
types,
107+
resolvers,
108+
imports: [childComponent1, childComponent2]
109+
});
110+
```
111+
112+
### Federation Components
113+
- Set `federation: true`
114+
- Include federation directives in schema
115+
- Implement `__resolveReference` resolvers
116+
117+
```typescript
118+
const component = new GraphQLComponent({
119+
types,
120+
resolvers,
121+
dataSources: [new MyDataSource()],
122+
federation: true // Enable federation
123+
});
124+
```
125+
126+
## Resolver Delegation
127+
128+
### Cross-Component Calls
129+
- Use `delegateToSchema` from `@graphql-tools/delegate`
130+
- Reference component schema via `this.componentName.schema`
131+
- Pass through context and info
132+
133+
```typescript
134+
import { delegateToSchema } from '@graphql-tools/delegate';
135+
136+
export const resolvers = {
137+
Listing: {
138+
property(root, args, context, info) {
139+
return delegateToSchema({
140+
schema: this.propertyComponent.schema,
141+
fieldName: 'propertyById',
142+
args: { id: root.id },
143+
context,
144+
info
145+
});
146+
}
147+
}
148+
};
149+
```
150+
151+
## Context Usage
152+
153+
### Accessing Data Sources
154+
- Use destructuring: `{ dataSources }` from context
155+
- Access by class name: `dataSources.MyDataSource`
156+
157+
```typescript
158+
const resolvers = {
159+
Query: {
160+
user(_, { id }, { dataSources }) {
161+
return dataSources.UserDataSource.getUser(id);
162+
}
163+
}
164+
};
165+
```
166+
167+
### Federation Resolvers
168+
- Include `__resolveReference` for entity resolution
169+
- Use typed parameters for clarity
170+
171+
```typescript
172+
const resolvers = {
173+
Property: {
174+
__resolveReference(ref: { id: string }, { dataSources }: ComponentContext) {
175+
return dataSources.PropertyDataSource.getPropertyById(ref.id);
176+
}
177+
}
178+
};
179+
```

.cursor/rules/datasources.mdc

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
# Data Source Patterns
2+
3+
## Two Data Access Patterns
4+
5+
### Pattern 1: Injected Data Sources (Recommended)
6+
- Pass via constructor `dataSources` option
7+
- Access via `context.dataSources.name`
8+
- Automatic context injection via proxy
9+
- Easy testing with `dataSourceOverrides`
10+
11+
### Pattern 2: Private Data Sources (Alternative)
12+
- Create as component instance properties
13+
- Access via `this.dataSourceName` in resolvers
14+
- Resolvers are bound to component instance
15+
- Manual context passing required
16+
- **Limitation**: No `dataSourceOverrides` support
17+
- **Limitation**: No runtime configuration flexibility
18+
19+
## Implementation Rules
20+
21+
### Injected Data Sources
22+
- Always implement `DataSourceDefinition<T>` and `IDataSource`
23+
- Include `name` property (string) for identification
24+
- Context parameter MUST be first in all methods
25+
26+
```typescript
27+
class MyDataSource implements DataSourceDefinition<MyDataSource>, IDataSource {
28+
name = 'MyDataSource'; // Required
29+
30+
// Context MUST be first parameter
31+
async getData(context: ComponentContext, id: string) {
32+
return { id };
33+
}
34+
}
35+
```
36+
37+
### Private Data Sources
38+
- No special interfaces required
39+
- Store as component properties
40+
- Use regular functions (not arrow functions) in resolvers for `this` binding
41+
42+
```typescript
43+
class MyComponent extends GraphQLComponent {
44+
private myDataSource: MyDataSource;
45+
46+
constructor(options = {}) {
47+
super({
48+
resolvers: {
49+
Query: {
50+
// Use regular function for 'this' binding
51+
data(_, { id }, context) {
52+
return this.myDataSource.getData(id, context);
53+
}
54+
}
55+
},
56+
...options
57+
});
58+
59+
this.myDataSource = new MyDataSource();
60+
}
61+
}
62+
```
63+
64+
### Typing Pattern
65+
- Use generic self-reference: `DataSourceDefinition<MyDataSource>`
66+
- Import `ComponentContext` from the main library
67+
- Define interfaces for return types when complex
68+
69+
```typescript
70+
import { DataSourceDefinition, ComponentContext, IDataSource } from 'graphql-component';
71+
72+
interface User {
73+
id: string;
74+
name: string;
75+
}
76+
77+
class UserDataSource implements DataSourceDefinition<UserDataSource>, IDataSource {
78+
name = 'users';
79+
80+
async getUser(context: ComponentContext, id: string): Promise<User> {
81+
// Implementation
82+
}
83+
}
84+
```
85+
86+
## Usage in Components
87+
88+
### Constructor Pattern
89+
- Use default data sources with spread operator
90+
- Allow override through constructor options
91+
92+
```typescript
93+
export default class MyComponent extends GraphQLComponent {
94+
constructor({ dataSources = [new MyDataSource()], ...options } = {}) {
95+
super({ types, resolvers, dataSources, ...options });
96+
}
97+
}
98+
```
99+
100+
### Resolver Usage
101+
- **NEVER** pass context manually to data source methods
102+
- Context is automatically injected by proxy
103+
- Access via `context.dataSources.DataSourceName`
104+
105+
```typescript
106+
const resolvers = {
107+
Query: {
108+
user(_, { id }, context) {
109+
// ✅ Correct - context injected automatically
110+
return context.dataSources.users.getUser(id);
111+
112+
// ❌ Wrong - don't pass context manually
113+
// return context.dataSources.users.getUser(context, id);
114+
}
115+
}
116+
};
117+
```
118+
119+
## Testing Patterns
120+
121+
### Basic Data Source Testing
122+
```typescript
123+
test('data source injection', async (t) => {
124+
const component = new GraphQLComponent({
125+
types: `type Query { test: String }`,
126+
dataSources: [new TestDataSource()]
127+
});
128+
129+
const context = await component.context({ testValue: 'test' });
130+
const result = context.dataSources.TestDataSource.getData('arg');
131+
132+
t.equal(result, 'expected', 'data source method works');
133+
t.end();
134+
});
135+
```
136+
137+
### Override Testing
138+
```typescript
139+
test('data source overrides', async (t) => {
140+
const mockDataSource = new MockDataSource();
141+
142+
const component = new GraphQLComponent({
143+
imports: [originalComponent],
144+
dataSourceOverrides: [mockDataSource]
145+
});
146+
147+
const context = await component.context({});
148+
// Original data source is replaced by mock
149+
});
150+
```
151+
152+
## File Organization
153+
154+
### Structure
155+
```
156+
component/
157+
├── datasource.ts # Data source implementation
158+
├── index.ts # Component class
159+
├── resolvers.ts # Resolver functions
160+
├── schema.graphql # GraphQL schema
161+
└── types.ts # Schema loader
162+
```
163+
164+
### Export Pattern
165+
- Default export the data source class
166+
- Keep implementation in separate file from component

0 commit comments

Comments
 (0)