@effect-gql/federation 0.1.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +100 -0
- package/index.cjs +618 -0
- package/index.cjs.map +1 -0
- package/index.d.cts +577 -0
- package/index.d.ts +577 -0
- package/index.js +570 -0
- package/index.js.map +1 -0
- package/package.json +14 -27
- package/dist/directives.d.ts +0 -136
- package/dist/directives.d.ts.map +0 -1
- package/dist/directives.js +0 -171
- package/dist/directives.js.map +0 -1
- package/dist/entities.d.ts +0 -31
- package/dist/entities.d.ts.map +0 -1
- package/dist/entities.js +0 -76
- package/dist/entities.js.map +0 -1
- package/dist/federated-builder.d.ts +0 -182
- package/dist/federated-builder.d.ts.map +0 -1
- package/dist/federated-builder.js +0 -442
- package/dist/federated-builder.js.map +0 -1
- package/dist/index.d.ts +0 -7
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -40
- package/dist/index.js.map +0 -1
- package/dist/pipe-api.d.ts +0 -163
- package/dist/pipe-api.d.ts.map +0 -1
- package/dist/pipe-api.js +0 -127
- package/dist/pipe-api.js.map +0 -1
- package/dist/scalars.d.ts +0 -12
- package/dist/scalars.d.ts.map +0 -1
- package/dist/scalars.js +0 -59
- package/dist/scalars.js.map +0 -1
- package/dist/types.d.ts +0 -89
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -41
- package/dist/types.js.map +0 -1
- package/src/directives.ts +0 -170
- package/src/entities.ts +0 -90
- package/src/federated-builder.ts +0 -593
- package/src/index.ts +0 -47
- package/src/pipe-api.ts +0 -263
- package/src/scalars.ts +0 -59
- package/src/types.ts +0 -114
package/README.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Effect GraphQL
|
|
2
|
+
|
|
3
|
+
A GraphQL framework for Effect-TS that brings full type safety, composability, and functional programming to your GraphQL servers.
|
|
4
|
+
|
|
5
|
+
> **Note:** This is an experimental prototype exploring the integration between Effect Schema, Effect's service system, and GraphQL.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Type-Safe End-to-End** - Define schemas once with Effect Schema, get TypeScript types and GraphQL types automatically
|
|
10
|
+
- **Effect-Powered Resolvers** - Resolvers are Effect programs with built-in error handling and service injection
|
|
11
|
+
- **Immutable Builder** - Fluent, pipe-able API for composing schemas from reusable parts
|
|
12
|
+
- **Service Integration** - Use Effect's Layer system for dependency injection
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @effect-gql/core effect graphql
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { Effect, Layer } from "effect"
|
|
24
|
+
import * as S from "effect/Schema"
|
|
25
|
+
import { GraphQLSchemaBuilder, execute } from "@effect-gql/core"
|
|
26
|
+
|
|
27
|
+
// Define your schema with Effect Schema
|
|
28
|
+
const UserSchema = S.Struct({
|
|
29
|
+
id: S.String,
|
|
30
|
+
name: S.String,
|
|
31
|
+
email: S.String,
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
// Build your GraphQL schema
|
|
35
|
+
const schema = GraphQLSchemaBuilder.empty
|
|
36
|
+
.objectType({ name: "User", schema: UserSchema })
|
|
37
|
+
.query("users", {
|
|
38
|
+
type: S.Array(UserSchema),
|
|
39
|
+
resolve: () => Effect.succeed([
|
|
40
|
+
{ id: "1", name: "Alice", email: "alice@example.com" },
|
|
41
|
+
{ id: "2", name: "Bob", email: "bob@example.com" },
|
|
42
|
+
]),
|
|
43
|
+
})
|
|
44
|
+
.query("user", {
|
|
45
|
+
type: UserSchema,
|
|
46
|
+
args: S.Struct({ id: S.String }),
|
|
47
|
+
resolve: (args) => Effect.succeed({
|
|
48
|
+
id: args.id,
|
|
49
|
+
name: "Alice",
|
|
50
|
+
email: "alice@example.com",
|
|
51
|
+
}),
|
|
52
|
+
})
|
|
53
|
+
.buildSchema()
|
|
54
|
+
|
|
55
|
+
// Execute a query
|
|
56
|
+
const result = await Effect.runPromise(
|
|
57
|
+
execute(schema, Layer.empty)(`
|
|
58
|
+
query {
|
|
59
|
+
users { id name email }
|
|
60
|
+
}
|
|
61
|
+
`)
|
|
62
|
+
)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Documentation
|
|
66
|
+
|
|
67
|
+
For full documentation, guides, and API reference, visit the [documentation site](https://nrf110.github.io/effect-gql/).
|
|
68
|
+
|
|
69
|
+
## Development
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Install dependencies
|
|
73
|
+
npm install
|
|
74
|
+
|
|
75
|
+
# Build the project
|
|
76
|
+
npm run build
|
|
77
|
+
|
|
78
|
+
# Run tests
|
|
79
|
+
npm test
|
|
80
|
+
|
|
81
|
+
# Development mode with watch
|
|
82
|
+
npm run dev
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Contributing
|
|
86
|
+
|
|
87
|
+
Contributions are welcome! Here's how you can help:
|
|
88
|
+
|
|
89
|
+
1. **Report bugs** - Open an issue describing the problem and steps to reproduce
|
|
90
|
+
2. **Suggest features** - Open an issue describing your idea
|
|
91
|
+
3. **Submit PRs** - Fork the repo, make your changes, and open a pull request
|
|
92
|
+
|
|
93
|
+
Please ensure your code:
|
|
94
|
+
- Passes all existing tests (`npm test`)
|
|
95
|
+
- Includes tests for new functionality
|
|
96
|
+
- Follows the existing code style
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
|
|
100
|
+
MIT
|
package/index.cjs
ADDED
|
@@ -0,0 +1,618 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var effect = require('effect');
|
|
4
|
+
var S = require('effect/Schema');
|
|
5
|
+
var core = require('@effect-gql/core');
|
|
6
|
+
|
|
7
|
+
function _interopNamespace(e) {
|
|
8
|
+
if (e && e.__esModule) return e;
|
|
9
|
+
var n = Object.create(null);
|
|
10
|
+
if (e) {
|
|
11
|
+
Object.keys(e).forEach(function (k) {
|
|
12
|
+
if (k !== 'default') {
|
|
13
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
14
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
get: function () { return e[k]; }
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
n.default = e;
|
|
22
|
+
return Object.freeze(n);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
var S__namespace = /*#__PURE__*/_interopNamespace(S);
|
|
26
|
+
|
|
27
|
+
// src/federated-builder.ts
|
|
28
|
+
function parseLiteralToValue(ast) {
|
|
29
|
+
switch (ast.kind) {
|
|
30
|
+
case core.Kind.STRING:
|
|
31
|
+
case core.Kind.BOOLEAN:
|
|
32
|
+
return ast.value;
|
|
33
|
+
case core.Kind.INT:
|
|
34
|
+
return parseInt(ast.value, 10);
|
|
35
|
+
case core.Kind.FLOAT:
|
|
36
|
+
return parseFloat(ast.value);
|
|
37
|
+
case core.Kind.NULL:
|
|
38
|
+
return null;
|
|
39
|
+
case core.Kind.LIST:
|
|
40
|
+
return ast.values.map(parseLiteralToValue);
|
|
41
|
+
case core.Kind.OBJECT: {
|
|
42
|
+
const obj = {};
|
|
43
|
+
for (const field2 of ast.fields) {
|
|
44
|
+
obj[field2.name.value] = parseLiteralToValue(field2.value);
|
|
45
|
+
}
|
|
46
|
+
return obj;
|
|
47
|
+
}
|
|
48
|
+
default:
|
|
49
|
+
return void 0;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
var AnyScalar = new core.GraphQLScalarType({
|
|
53
|
+
name: "_Any",
|
|
54
|
+
description: "The _Any scalar is used to pass representations of entities from external services.",
|
|
55
|
+
serialize: (value) => value,
|
|
56
|
+
parseValue: (value) => value,
|
|
57
|
+
parseLiteral: parseLiteralToValue
|
|
58
|
+
});
|
|
59
|
+
var FieldSetScalar = new core.GraphQLScalarType({
|
|
60
|
+
name: "_FieldSet",
|
|
61
|
+
description: "A string representing a selection of fields.",
|
|
62
|
+
serialize: (value) => value,
|
|
63
|
+
parseValue: (value) => value,
|
|
64
|
+
parseLiteral: (ast) => {
|
|
65
|
+
if (ast.kind === core.Kind.STRING) {
|
|
66
|
+
return ast.value;
|
|
67
|
+
}
|
|
68
|
+
return void 0;
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
function createEntityUnion(entities, typeRegistry) {
|
|
72
|
+
const types = Array.from(entities.keys()).map((name) => typeRegistry.get(name)).filter(Boolean);
|
|
73
|
+
if (types.length === 0) {
|
|
74
|
+
throw new Error("At least one entity must be registered to create _Entity union");
|
|
75
|
+
}
|
|
76
|
+
return new core.GraphQLUnionType({
|
|
77
|
+
name: "_Entity",
|
|
78
|
+
description: "Union of all types that have @key directives",
|
|
79
|
+
types: () => types,
|
|
80
|
+
resolveType: (value) => value.__typename
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function createEntitiesResolver(entities) {
|
|
84
|
+
return async (_parent, args, context) => {
|
|
85
|
+
const effects = args.representations.map((representation) => {
|
|
86
|
+
const entityName = representation.__typename;
|
|
87
|
+
const entity2 = entities.get(entityName);
|
|
88
|
+
if (!entity2) {
|
|
89
|
+
return effect.Effect.fail(new Error(`Unknown entity type: ${entityName}`));
|
|
90
|
+
}
|
|
91
|
+
return entity2.resolveReference(representation).pipe(
|
|
92
|
+
effect.Effect.map((result) => {
|
|
93
|
+
if (result !== null && typeof result === "object") {
|
|
94
|
+
return { ...result, __typename: entityName };
|
|
95
|
+
}
|
|
96
|
+
return result;
|
|
97
|
+
}),
|
|
98
|
+
// Catch individual entity resolution errors and return null
|
|
99
|
+
effect.Effect.catchAll(
|
|
100
|
+
(error) => effect.Effect.logError(`Failed to resolve entity ${entityName}`, error).pipe(effect.Effect.as(null))
|
|
101
|
+
)
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
return effect.Runtime.runPromise(context.runtime)(effect.Effect.all(effects, { concurrency: "unbounded" }));
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function createServiceType() {
|
|
108
|
+
return new core.GraphQLObjectType({
|
|
109
|
+
name: "_Service",
|
|
110
|
+
description: "Provides SDL for the subgraph schema",
|
|
111
|
+
fields: {
|
|
112
|
+
sdl: {
|
|
113
|
+
type: core.GraphQLString,
|
|
114
|
+
description: "The SDL representing the subgraph schema"
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
function createServiceResolver(sdl) {
|
|
120
|
+
return () => ({ sdl });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// src/types.ts
|
|
124
|
+
function toDirectiveApplication(directive) {
|
|
125
|
+
switch (directive._tag) {
|
|
126
|
+
case "key":
|
|
127
|
+
return {
|
|
128
|
+
name: "key",
|
|
129
|
+
args: {
|
|
130
|
+
fields: directive.fields,
|
|
131
|
+
...directive.resolvable !== void 0 ? { resolvable: directive.resolvable } : {}
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
case "external":
|
|
135
|
+
return { name: "external" };
|
|
136
|
+
case "requires":
|
|
137
|
+
return { name: "requires", args: { fields: directive.fields } };
|
|
138
|
+
case "provides":
|
|
139
|
+
return { name: "provides", args: { fields: directive.fields } };
|
|
140
|
+
case "shareable":
|
|
141
|
+
return { name: "shareable" };
|
|
142
|
+
case "inaccessible":
|
|
143
|
+
return { name: "inaccessible" };
|
|
144
|
+
case "override":
|
|
145
|
+
return {
|
|
146
|
+
name: "override",
|
|
147
|
+
args: {
|
|
148
|
+
from: directive.from,
|
|
149
|
+
...directive.label !== void 0 ? { label: directive.label } : {}
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
case "interfaceObject":
|
|
153
|
+
return { name: "interfaceObject" };
|
|
154
|
+
case "tag":
|
|
155
|
+
return { name: "tag", args: { name: directive.name } };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// src/federated-builder.ts
|
|
160
|
+
var FederatedSchemaBuilder = class _FederatedSchemaBuilder {
|
|
161
|
+
constructor(state) {
|
|
162
|
+
this.state = state;
|
|
163
|
+
}
|
|
164
|
+
pipe() {
|
|
165
|
+
return effect.Pipeable.pipeArguments(this, arguments);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Create an empty federated schema builder
|
|
169
|
+
*/
|
|
170
|
+
static empty = new _FederatedSchemaBuilder({
|
|
171
|
+
coreBuilder: core.GraphQLSchemaBuilder.empty,
|
|
172
|
+
entities: /* @__PURE__ */ new Map(),
|
|
173
|
+
version: "2.3"
|
|
174
|
+
});
|
|
175
|
+
/**
|
|
176
|
+
* Create a builder with custom configuration
|
|
177
|
+
*/
|
|
178
|
+
static create(config = {}) {
|
|
179
|
+
return new _FederatedSchemaBuilder({
|
|
180
|
+
coreBuilder: core.GraphQLSchemaBuilder.empty,
|
|
181
|
+
entities: /* @__PURE__ */ new Map(),
|
|
182
|
+
version: config.version ?? "2.3"
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Create a new builder with updated state
|
|
187
|
+
*/
|
|
188
|
+
with(updates) {
|
|
189
|
+
return new _FederatedSchemaBuilder({
|
|
190
|
+
...this.state,
|
|
191
|
+
...updates
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Get the underlying core builder for advanced usage
|
|
196
|
+
*/
|
|
197
|
+
get coreBuilder() {
|
|
198
|
+
return this.state.coreBuilder;
|
|
199
|
+
}
|
|
200
|
+
// ============================================================================
|
|
201
|
+
// Entity Registration
|
|
202
|
+
// ============================================================================
|
|
203
|
+
/**
|
|
204
|
+
* Register an entity type with @key directive(s) and reference resolver.
|
|
205
|
+
*
|
|
206
|
+
* Entities are the core building block of Apollo Federation. They represent
|
|
207
|
+
* types that can be resolved across subgraph boundaries using their key fields.
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* ```typescript
|
|
211
|
+
* builder.entity({
|
|
212
|
+
* name: "User",
|
|
213
|
+
* schema: UserSchema,
|
|
214
|
+
* keys: [key({ fields: "id" })],
|
|
215
|
+
* resolveReference: (ref) => UserService.findById(ref.id),
|
|
216
|
+
* })
|
|
217
|
+
* ```
|
|
218
|
+
*/
|
|
219
|
+
entity(config) {
|
|
220
|
+
const { name, schema, keys, directives } = config;
|
|
221
|
+
const typeDirectives = [
|
|
222
|
+
// Add @key directives
|
|
223
|
+
...keys.map(
|
|
224
|
+
(k) => ({
|
|
225
|
+
name: "key",
|
|
226
|
+
args: {
|
|
227
|
+
fields: k.fields,
|
|
228
|
+
...k.resolvable !== void 0 ? { resolvable: k.resolvable } : {}
|
|
229
|
+
}
|
|
230
|
+
})
|
|
231
|
+
),
|
|
232
|
+
// Add additional directives
|
|
233
|
+
...directives?.map(toDirectiveApplication) ?? []
|
|
234
|
+
];
|
|
235
|
+
const newCoreBuilder = this.state.coreBuilder.objectType({
|
|
236
|
+
name,
|
|
237
|
+
schema,
|
|
238
|
+
directives: typeDirectives
|
|
239
|
+
});
|
|
240
|
+
const newEntities = new Map(this.state.entities);
|
|
241
|
+
newEntities.set(name, config);
|
|
242
|
+
return this.with({
|
|
243
|
+
coreBuilder: newCoreBuilder,
|
|
244
|
+
entities: newEntities
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
// ============================================================================
|
|
248
|
+
// Delegate to Core Builder
|
|
249
|
+
// ============================================================================
|
|
250
|
+
/**
|
|
251
|
+
* Add a query field
|
|
252
|
+
*/
|
|
253
|
+
query(name, config) {
|
|
254
|
+
return this.with({
|
|
255
|
+
coreBuilder: this.state.coreBuilder.query(name, config)
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Add a mutation field
|
|
260
|
+
*/
|
|
261
|
+
mutation(name, config) {
|
|
262
|
+
return this.with({
|
|
263
|
+
coreBuilder: this.state.coreBuilder.mutation(name, config)
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Add a subscription field
|
|
268
|
+
*/
|
|
269
|
+
subscription(name, config) {
|
|
270
|
+
return this.with({
|
|
271
|
+
coreBuilder: this.state.coreBuilder.subscription(name, config)
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Register an object type (non-entity)
|
|
276
|
+
*/
|
|
277
|
+
objectType(config) {
|
|
278
|
+
return this.with({
|
|
279
|
+
coreBuilder: this.state.coreBuilder.objectType(config)
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Register an interface type
|
|
284
|
+
*/
|
|
285
|
+
interfaceType(config) {
|
|
286
|
+
return this.with({
|
|
287
|
+
coreBuilder: this.state.coreBuilder.interfaceType(config)
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Register an enum type
|
|
292
|
+
*/
|
|
293
|
+
enumType(config) {
|
|
294
|
+
return this.with({
|
|
295
|
+
coreBuilder: this.state.coreBuilder.enumType(config)
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Register a union type
|
|
300
|
+
*/
|
|
301
|
+
unionType(config) {
|
|
302
|
+
return this.with({
|
|
303
|
+
coreBuilder: this.state.coreBuilder.unionType(config)
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Register an input type
|
|
308
|
+
*/
|
|
309
|
+
inputType(config) {
|
|
310
|
+
return this.with({
|
|
311
|
+
coreBuilder: this.state.coreBuilder.inputType(config)
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Add a computed/relational field to an object type
|
|
316
|
+
*/
|
|
317
|
+
field(typeName, fieldName, config) {
|
|
318
|
+
return this.with({
|
|
319
|
+
coreBuilder: this.state.coreBuilder.field(
|
|
320
|
+
typeName,
|
|
321
|
+
fieldName,
|
|
322
|
+
config
|
|
323
|
+
)
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
// ============================================================================
|
|
327
|
+
// Schema Building
|
|
328
|
+
// ============================================================================
|
|
329
|
+
/**
|
|
330
|
+
* Build the federated GraphQL schema with _entities and _service queries.
|
|
331
|
+
*
|
|
332
|
+
* Returns both the executable schema and the Federation-compliant SDL.
|
|
333
|
+
*/
|
|
334
|
+
buildFederatedSchema() {
|
|
335
|
+
let builderForSchema = this.state.coreBuilder;
|
|
336
|
+
const needsPlaceholder = !this.hasQueryFields();
|
|
337
|
+
if (needsPlaceholder) {
|
|
338
|
+
builderForSchema = builderForSchema.query("_placeholder", {
|
|
339
|
+
type: S__namespace.String,
|
|
340
|
+
resolve: () => effect.Effect.succeed("placeholder")
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
const baseSchema = builderForSchema.buildSchema();
|
|
344
|
+
const typeRegistry = /* @__PURE__ */ new Map();
|
|
345
|
+
const typeMap = baseSchema.getTypeMap();
|
|
346
|
+
for (const [name, type] of Object.entries(typeMap)) {
|
|
347
|
+
const isObjectType = type.constructor.name === "GraphQLObjectType";
|
|
348
|
+
if (isObjectType && !name.startsWith("__")) {
|
|
349
|
+
typeRegistry.set(name, type);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
const entityUnion = this.state.entities.size > 0 ? createEntityUnion(this.state.entities, typeRegistry) : null;
|
|
353
|
+
const serviceType = createServiceType();
|
|
354
|
+
const federationQueryFields = {};
|
|
355
|
+
if (entityUnion) {
|
|
356
|
+
federationQueryFields._entities = {
|
|
357
|
+
type: new core.GraphQLNonNull(new core.GraphQLList(entityUnion)),
|
|
358
|
+
args: {
|
|
359
|
+
representations: {
|
|
360
|
+
type: new core.GraphQLNonNull(new core.GraphQLList(new core.GraphQLNonNull(AnyScalar)))
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
resolve: createEntitiesResolver(this.state.entities)
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
const sdl = this.generateFederatedSDL(baseSchema, needsPlaceholder);
|
|
367
|
+
federationQueryFields._service = {
|
|
368
|
+
type: new core.GraphQLNonNull(serviceType),
|
|
369
|
+
resolve: createServiceResolver(sdl)
|
|
370
|
+
};
|
|
371
|
+
const baseQueryType = baseSchema.getQueryType();
|
|
372
|
+
const baseQueryFields = baseQueryType?.getFields() ?? {};
|
|
373
|
+
const queryType = new core.GraphQLObjectType({
|
|
374
|
+
name: "Query",
|
|
375
|
+
fields: () => {
|
|
376
|
+
const fields = {};
|
|
377
|
+
for (const [name, field2] of Object.entries(baseQueryFields)) {
|
|
378
|
+
if (name === "_placeholder") continue;
|
|
379
|
+
fields[name] = {
|
|
380
|
+
type: field2.type,
|
|
381
|
+
args: field2.args.reduce(
|
|
382
|
+
(acc, arg) => {
|
|
383
|
+
acc[arg.name] = {
|
|
384
|
+
type: arg.type,
|
|
385
|
+
description: arg.description,
|
|
386
|
+
defaultValue: arg.defaultValue
|
|
387
|
+
};
|
|
388
|
+
return acc;
|
|
389
|
+
},
|
|
390
|
+
{}
|
|
391
|
+
),
|
|
392
|
+
description: field2.description,
|
|
393
|
+
resolve: field2.resolve,
|
|
394
|
+
extensions: field2.extensions
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
Object.assign(fields, federationQueryFields);
|
|
398
|
+
return fields;
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
const types = [AnyScalar, FieldSetScalar, serviceType];
|
|
402
|
+
if (entityUnion) {
|
|
403
|
+
types.push(entityUnion);
|
|
404
|
+
}
|
|
405
|
+
for (const [name, type] of Object.entries(typeMap)) {
|
|
406
|
+
if (!name.startsWith("__") && name !== "Query") {
|
|
407
|
+
types.push(type);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
const schema = new core.GraphQLSchema({
|
|
411
|
+
query: queryType,
|
|
412
|
+
mutation: baseSchema.getMutationType() ?? void 0,
|
|
413
|
+
subscription: baseSchema.getSubscriptionType() ?? void 0,
|
|
414
|
+
types
|
|
415
|
+
});
|
|
416
|
+
return { schema, sdl };
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Check if the core builder has any query fields registered
|
|
420
|
+
*/
|
|
421
|
+
hasQueryFields() {
|
|
422
|
+
try {
|
|
423
|
+
const schema = this.state.coreBuilder.buildSchema();
|
|
424
|
+
const queryType = schema.getQueryType();
|
|
425
|
+
return queryType !== null && queryType !== void 0;
|
|
426
|
+
} catch {
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Build a standard (non-federated) schema.
|
|
432
|
+
* Useful for testing or running without a gateway.
|
|
433
|
+
*/
|
|
434
|
+
buildSchema() {
|
|
435
|
+
return this.state.coreBuilder.buildSchema();
|
|
436
|
+
}
|
|
437
|
+
// ============================================================================
|
|
438
|
+
// SDL Generation
|
|
439
|
+
// ============================================================================
|
|
440
|
+
/**
|
|
441
|
+
* Generate Federation-compliant SDL with directive annotations.
|
|
442
|
+
*/
|
|
443
|
+
generateFederatedSDL(schema, excludePlaceholder = false) {
|
|
444
|
+
const lines = [
|
|
445
|
+
`extend schema @link(url: "https://specs.apollo.dev/federation/v${this.state.version}", import: ["@key", "@shareable", "@external", "@requires", "@provides", "@override", "@inaccessible", "@interfaceObject", "@tag"])`,
|
|
446
|
+
""
|
|
447
|
+
];
|
|
448
|
+
let baseSDL = core.printSchema(schema);
|
|
449
|
+
if (excludePlaceholder) {
|
|
450
|
+
baseSDL = baseSDL.replace(/\s*_placeholder:\s*String\n?/g, "");
|
|
451
|
+
}
|
|
452
|
+
const annotatedSDL = this.annotateSDLWithDirectives(baseSDL, schema);
|
|
453
|
+
lines.push(annotatedSDL);
|
|
454
|
+
return lines.join("\n");
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Annotate SDL types with their federation directives from extensions.
|
|
458
|
+
*/
|
|
459
|
+
annotateSDLWithDirectives(sdl, schema) {
|
|
460
|
+
const typeMap = schema.getTypeMap();
|
|
461
|
+
let result = sdl;
|
|
462
|
+
for (const [typeName, type] of Object.entries(typeMap)) {
|
|
463
|
+
if (typeName.startsWith("__")) continue;
|
|
464
|
+
const directives = type.extensions?.directives;
|
|
465
|
+
if (!directives || directives.length === 0) continue;
|
|
466
|
+
const directiveStr = directives.map(formatDirective).join(" ");
|
|
467
|
+
const typePattern = new RegExp(
|
|
468
|
+
`(type\\s+${typeName}(?:\\s+implements\\s+[^{]+)?)(\\s*\\{)`,
|
|
469
|
+
"g"
|
|
470
|
+
);
|
|
471
|
+
result = result.replace(typePattern, `$1 ${directiveStr}$2`);
|
|
472
|
+
const interfacePattern = new RegExp(`(interface\\s+${typeName})(\\s*\\{)`, "g");
|
|
473
|
+
result = result.replace(interfacePattern, `$1 ${directiveStr}$2`);
|
|
474
|
+
const enumPattern = new RegExp(`(enum\\s+${typeName})(\\s*\\{)`, "g");
|
|
475
|
+
result = result.replace(enumPattern, `$1 ${directiveStr}$2`);
|
|
476
|
+
const unionPattern = new RegExp(`(union\\s+${typeName})(\\s*=)`, "g");
|
|
477
|
+
result = result.replace(unionPattern, `$1 ${directiveStr}$2`);
|
|
478
|
+
const inputPattern = new RegExp(`(input\\s+${typeName})(\\s*\\{)`, "g");
|
|
479
|
+
result = result.replace(inputPattern, `$1 ${directiveStr}$2`);
|
|
480
|
+
}
|
|
481
|
+
for (const [typeName, type] of Object.entries(typeMap)) {
|
|
482
|
+
if (typeName.startsWith("__")) continue;
|
|
483
|
+
if (!(type instanceof core.GraphQLObjectType)) continue;
|
|
484
|
+
const fields = type.getFields();
|
|
485
|
+
for (const [fieldName, field2] of Object.entries(fields)) {
|
|
486
|
+
const fieldDirectives = field2.extensions?.directives;
|
|
487
|
+
if (!fieldDirectives || fieldDirectives.length === 0) continue;
|
|
488
|
+
const directiveStr = fieldDirectives.map(formatDirective).join(" ");
|
|
489
|
+
const typeBlockPattern = new RegExp(
|
|
490
|
+
`(type\\s+${typeName}[^{]*\\{[\\s\\S]*?)(${fieldName}(?:\\([^)]*\\))?:\\s*[^\\n]+?)([\\n}])`,
|
|
491
|
+
"g"
|
|
492
|
+
);
|
|
493
|
+
result = result.replace(typeBlockPattern, `$1$2 ${directiveStr}$3`);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
return result;
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
function formatDirective(directive) {
|
|
500
|
+
if (!directive.args || Object.keys(directive.args).length === 0) {
|
|
501
|
+
return `@${directive.name}`;
|
|
502
|
+
}
|
|
503
|
+
const args = Object.entries(directive.args).map(([key2, value]) => {
|
|
504
|
+
if (typeof value === "string") {
|
|
505
|
+
return `${key2}: "${value}"`;
|
|
506
|
+
}
|
|
507
|
+
return `${key2}: ${JSON.stringify(value)}`;
|
|
508
|
+
}).join(", ");
|
|
509
|
+
return `@${directive.name}(${args})`;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// src/directives.ts
|
|
513
|
+
var key = (config) => ({
|
|
514
|
+
_tag: "key",
|
|
515
|
+
fields: config.fields,
|
|
516
|
+
resolvable: config.resolvable
|
|
517
|
+
});
|
|
518
|
+
var shareable = () => ({
|
|
519
|
+
_tag: "shareable"
|
|
520
|
+
});
|
|
521
|
+
var inaccessible = () => ({
|
|
522
|
+
_tag: "inaccessible"
|
|
523
|
+
});
|
|
524
|
+
var interfaceObject = () => ({
|
|
525
|
+
_tag: "interfaceObject"
|
|
526
|
+
});
|
|
527
|
+
var tag = (name) => ({
|
|
528
|
+
_tag: "tag",
|
|
529
|
+
name
|
|
530
|
+
});
|
|
531
|
+
var external = () => ({
|
|
532
|
+
_tag: "external"
|
|
533
|
+
});
|
|
534
|
+
var requires = (config) => ({
|
|
535
|
+
_tag: "requires",
|
|
536
|
+
fields: config.fields
|
|
537
|
+
});
|
|
538
|
+
var provides = (config) => ({
|
|
539
|
+
_tag: "provides",
|
|
540
|
+
fields: config.fields
|
|
541
|
+
});
|
|
542
|
+
var override = (config) => ({
|
|
543
|
+
_tag: "override",
|
|
544
|
+
from: config.from,
|
|
545
|
+
label: config.label
|
|
546
|
+
});
|
|
547
|
+
var entity = (config) => (builder) => builder.entity(config);
|
|
548
|
+
var query = (name, config) => (builder) => builder.query(name, config);
|
|
549
|
+
var mutation = (name, config) => (builder) => builder.mutation(name, config);
|
|
550
|
+
var subscription = (name, config) => (builder) => builder.subscription(name, config);
|
|
551
|
+
var objectType = (config) => (builder) => builder.objectType(config);
|
|
552
|
+
var interfaceType = (config) => (builder) => builder.interfaceType(config);
|
|
553
|
+
var enumType = (config) => (builder) => builder.enumType(config);
|
|
554
|
+
var unionType = (config) => (builder) => builder.unionType(config);
|
|
555
|
+
var inputType = (config) => (builder) => builder.inputType(config);
|
|
556
|
+
var field = (typeName, fieldName, config) => (builder) => builder.field(typeName, fieldName, config);
|
|
557
|
+
var externalField = (config) => ({
|
|
558
|
+
type: config.type,
|
|
559
|
+
description: config.description,
|
|
560
|
+
directives: [{ name: "external" }],
|
|
561
|
+
resolve: (parent) => effect.Effect.succeed(parent)
|
|
562
|
+
});
|
|
563
|
+
var requiresField = (config) => ({
|
|
564
|
+
type: config.type,
|
|
565
|
+
description: config.description,
|
|
566
|
+
directives: [{ name: "requires", args: { fields: config.fields } }],
|
|
567
|
+
resolve: config.resolve
|
|
568
|
+
});
|
|
569
|
+
var providesField = (config) => ({
|
|
570
|
+
type: config.type,
|
|
571
|
+
description: config.description,
|
|
572
|
+
directives: [{ name: "provides", args: { fields: config.fields } }],
|
|
573
|
+
resolve: config.resolve
|
|
574
|
+
});
|
|
575
|
+
var overrideField = (config) => ({
|
|
576
|
+
type: config.type,
|
|
577
|
+
description: config.description,
|
|
578
|
+
directives: [
|
|
579
|
+
{
|
|
580
|
+
name: "override",
|
|
581
|
+
args: {
|
|
582
|
+
from: config.from,
|
|
583
|
+
...config.label !== void 0 ? { label: config.label } : {}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
],
|
|
587
|
+
resolve: config.resolve
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
exports.AnyScalar = AnyScalar;
|
|
591
|
+
exports.FederatedSchemaBuilder = FederatedSchemaBuilder;
|
|
592
|
+
exports.FieldSetScalar = FieldSetScalar;
|
|
593
|
+
exports.entity = entity;
|
|
594
|
+
exports.enumType = enumType;
|
|
595
|
+
exports.external = external;
|
|
596
|
+
exports.externalField = externalField;
|
|
597
|
+
exports.field = field;
|
|
598
|
+
exports.inaccessible = inaccessible;
|
|
599
|
+
exports.inputType = inputType;
|
|
600
|
+
exports.interfaceObject = interfaceObject;
|
|
601
|
+
exports.interfaceType = interfaceType;
|
|
602
|
+
exports.key = key;
|
|
603
|
+
exports.mutation = mutation;
|
|
604
|
+
exports.objectType = objectType;
|
|
605
|
+
exports.override = override;
|
|
606
|
+
exports.overrideField = overrideField;
|
|
607
|
+
exports.provides = provides;
|
|
608
|
+
exports.providesField = providesField;
|
|
609
|
+
exports.query = query;
|
|
610
|
+
exports.requires = requires;
|
|
611
|
+
exports.requiresField = requiresField;
|
|
612
|
+
exports.shareable = shareable;
|
|
613
|
+
exports.subscription = subscription;
|
|
614
|
+
exports.tag = tag;
|
|
615
|
+
exports.toDirectiveApplication = toDirectiveApplication;
|
|
616
|
+
exports.unionType = unionType;
|
|
617
|
+
//# sourceMappingURL=index.cjs.map
|
|
618
|
+
//# sourceMappingURL=index.cjs.map
|