@effect-gql/federation 0.1.0 → 1.1.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/src/pipe-api.ts DELETED
@@ -1,263 +0,0 @@
1
- import { Effect } from "effect"
2
- import * as S from "effect/Schema"
3
- import type { DirectiveApplication } from "@effect-gql/core"
4
- import { FederatedSchemaBuilder } from "./federated-builder"
5
- import type { EntityRegistration } from "./types"
6
-
7
- // ============================================================================
8
- // Entity Registration
9
- // ============================================================================
10
-
11
- /**
12
- * Register an entity type with @key directive(s) and reference resolver.
13
- *
14
- * @example
15
- * ```typescript
16
- * FederatedSchemaBuilder.empty.pipe(
17
- * entity({
18
- * name: "User",
19
- * schema: UserSchema,
20
- * keys: [key({ fields: "id" })],
21
- * resolveReference: (ref) => UserService.findById(ref.id),
22
- * }),
23
- * )
24
- * ```
25
- */
26
- export const entity =
27
- <A, R>(config: EntityRegistration<A, R>) =>
28
- <R2>(builder: FederatedSchemaBuilder<R2>): FederatedSchemaBuilder<R | R2> =>
29
- builder.entity(config)
30
-
31
- // ============================================================================
32
- // Query/Mutation/Subscription
33
- // ============================================================================
34
-
35
- /**
36
- * Add a query field
37
- */
38
- export const query =
39
- <A, E, R, Args = void>(
40
- name: string,
41
- config: {
42
- type: S.Schema<A, any, any>
43
- args?: S.Schema<Args, any, any>
44
- description?: string
45
- directives?: readonly DirectiveApplication[]
46
- resolve: (args: Args) => Effect.Effect<A, E, R>
47
- }
48
- ) =>
49
- <R2>(builder: FederatedSchemaBuilder<R2>): FederatedSchemaBuilder<R | R2> =>
50
- builder.query(name, config)
51
-
52
- /**
53
- * Add a mutation field
54
- */
55
- export const mutation =
56
- <A, E, R, Args = void>(
57
- name: string,
58
- config: {
59
- type: S.Schema<A, any, any>
60
- args?: S.Schema<Args, any, any>
61
- description?: string
62
- directives?: readonly DirectiveApplication[]
63
- resolve: (args: Args) => Effect.Effect<A, E, R>
64
- }
65
- ) =>
66
- <R2>(builder: FederatedSchemaBuilder<R2>): FederatedSchemaBuilder<R | R2> =>
67
- builder.mutation(name, config)
68
-
69
- /**
70
- * Add a subscription field
71
- */
72
- export const subscription =
73
- <A, E, R, Args = void>(
74
- name: string,
75
- config: {
76
- type: S.Schema<A, any, any>
77
- args?: S.Schema<Args, any, any>
78
- description?: string
79
- directives?: readonly DirectiveApplication[]
80
- subscribe: (args: Args) => Effect.Effect<import("effect").Stream.Stream<A, E, R>, E, R>
81
- resolve?: (value: A, args: Args) => Effect.Effect<A, E, R>
82
- }
83
- ) =>
84
- <R2>(builder: FederatedSchemaBuilder<R2>): FederatedSchemaBuilder<R | R2> =>
85
- builder.subscription(name, config)
86
-
87
- // ============================================================================
88
- // Type Registration
89
- // ============================================================================
90
-
91
- /**
92
- * Register an object type (non-entity)
93
- */
94
- export const objectType =
95
- <A>(config: {
96
- name?: string
97
- schema: S.Schema<A, any, any>
98
- implements?: readonly string[]
99
- directives?: readonly DirectiveApplication[]
100
- }) =>
101
- <R>(builder: FederatedSchemaBuilder<R>): FederatedSchemaBuilder<R> =>
102
- builder.objectType(config)
103
-
104
- /**
105
- * Register an interface type
106
- */
107
- export const interfaceType =
108
- (config: {
109
- name?: string
110
- schema: S.Schema<any, any, any>
111
- resolveType?: (value: any) => string
112
- directives?: readonly DirectiveApplication[]
113
- }) =>
114
- <R>(builder: FederatedSchemaBuilder<R>): FederatedSchemaBuilder<R> =>
115
- builder.interfaceType(config)
116
-
117
- /**
118
- * Register an enum type
119
- */
120
- export const enumType =
121
- (config: {
122
- name: string
123
- values: readonly string[]
124
- description?: string
125
- directives?: readonly DirectiveApplication[]
126
- }) =>
127
- <R>(builder: FederatedSchemaBuilder<R>): FederatedSchemaBuilder<R> =>
128
- builder.enumType(config)
129
-
130
- /**
131
- * Register a union type
132
- */
133
- export const unionType =
134
- (config: {
135
- name: string
136
- types: readonly string[]
137
- resolveType?: (value: any) => string
138
- directives?: readonly DirectiveApplication[]
139
- }) =>
140
- <R>(builder: FederatedSchemaBuilder<R>): FederatedSchemaBuilder<R> =>
141
- builder.unionType(config)
142
-
143
- /**
144
- * Register an input type
145
- */
146
- export const inputType =
147
- (config: {
148
- name?: string
149
- schema: S.Schema<any, any, any>
150
- description?: string
151
- directives?: readonly DirectiveApplication[]
152
- }) =>
153
- <R>(builder: FederatedSchemaBuilder<R>): FederatedSchemaBuilder<R> =>
154
- builder.inputType(config)
155
-
156
- /**
157
- * Add a computed/relational field to an object type
158
- */
159
- export const field =
160
- <Parent, A, E, R, Args = void>(
161
- typeName: string,
162
- fieldName: string,
163
- config: {
164
- type: S.Schema<A, any, any>
165
- args?: S.Schema<Args, any, any>
166
- description?: string
167
- directives?: readonly DirectiveApplication[]
168
- resolve: (parent: Parent, args: Args) => Effect.Effect<A, E, R>
169
- }
170
- ) =>
171
- <R2>(builder: FederatedSchemaBuilder<R2>): FederatedSchemaBuilder<R | R2> =>
172
- builder.field(typeName, fieldName, config)
173
-
174
- // ============================================================================
175
- // Field-Level Federation Directive Helpers
176
- // ============================================================================
177
-
178
- /**
179
- * Create a field configuration with @external directive
180
- */
181
- export const externalField = <A>(config: {
182
- type: S.Schema<A, any, any>
183
- description?: string
184
- }): {
185
- type: S.Schema<A, any, any>
186
- description?: string
187
- directives: readonly DirectiveApplication[]
188
- resolve: (parent: any) => Effect.Effect<A, never, never>
189
- } => ({
190
- type: config.type,
191
- description: config.description,
192
- directives: [{ name: "external" }],
193
- resolve: (parent: any) => Effect.succeed(parent),
194
- })
195
-
196
- /**
197
- * Create a field configuration with @requires directive
198
- */
199
- export const requiresField = <A, E, R, Parent = any>(config: {
200
- type: S.Schema<A, any, any>
201
- fields: string
202
- description?: string
203
- resolve: (parent: Parent) => Effect.Effect<A, E, R>
204
- }): {
205
- type: S.Schema<A, any, any>
206
- description?: string
207
- directives: readonly DirectiveApplication[]
208
- resolve: (parent: Parent) => Effect.Effect<A, E, R>
209
- } => ({
210
- type: config.type,
211
- description: config.description,
212
- directives: [{ name: "requires", args: { fields: config.fields } }],
213
- resolve: config.resolve,
214
- })
215
-
216
- /**
217
- * Create a field configuration with @provides directive
218
- */
219
- export const providesField = <A, E, R, Parent = any>(config: {
220
- type: S.Schema<A, any, any>
221
- fields: string
222
- description?: string
223
- resolve: (parent: Parent) => Effect.Effect<A, E, R>
224
- }): {
225
- type: S.Schema<A, any, any>
226
- description?: string
227
- directives: readonly DirectiveApplication[]
228
- resolve: (parent: Parent) => Effect.Effect<A, E, R>
229
- } => ({
230
- type: config.type,
231
- description: config.description,
232
- directives: [{ name: "provides", args: { fields: config.fields } }],
233
- resolve: config.resolve,
234
- })
235
-
236
- /**
237
- * Create a field configuration with @override directive
238
- */
239
- export const overrideField = <A, E, R, Parent = any>(config: {
240
- type: S.Schema<A, any, any>
241
- from: string
242
- label?: string
243
- description?: string
244
- resolve: (parent: Parent) => Effect.Effect<A, E, R>
245
- }): {
246
- type: S.Schema<A, any, any>
247
- description?: string
248
- directives: readonly DirectiveApplication[]
249
- resolve: (parent: Parent) => Effect.Effect<A, E, R>
250
- } => ({
251
- type: config.type,
252
- description: config.description,
253
- directives: [
254
- {
255
- name: "override",
256
- args: {
257
- from: config.from,
258
- ...(config.label !== undefined ? { label: config.label } : {}),
259
- },
260
- },
261
- ],
262
- resolve: config.resolve,
263
- })
package/src/scalars.ts DELETED
@@ -1,59 +0,0 @@
1
- import { GraphQLScalarType, Kind, type ValueNode } from "@effect-gql/core"
2
-
3
- /**
4
- * Parse a literal value from the AST to a JavaScript value
5
- */
6
- function parseLiteralToValue(ast: ValueNode): unknown {
7
- switch (ast.kind) {
8
- case Kind.STRING:
9
- case Kind.BOOLEAN:
10
- return ast.value
11
- case Kind.INT:
12
- return parseInt(ast.value, 10)
13
- case Kind.FLOAT:
14
- return parseFloat(ast.value)
15
- case Kind.NULL:
16
- return null
17
- case Kind.LIST:
18
- return ast.values.map(parseLiteralToValue)
19
- case Kind.OBJECT: {
20
- const obj: Record<string, unknown> = {}
21
- for (const field of ast.fields) {
22
- obj[field.name.value] = parseLiteralToValue(field.value)
23
- }
24
- return obj
25
- }
26
- default:
27
- return undefined
28
- }
29
- }
30
-
31
- /**
32
- * The _Any scalar is used for entity representations in the _entities query.
33
- * It accepts any JSON value representing an entity with __typename and key fields.
34
- */
35
- export const AnyScalar = new GraphQLScalarType({
36
- name: "_Any",
37
- description:
38
- "The _Any scalar is used to pass representations of entities from external services.",
39
- serialize: (value) => value,
40
- parseValue: (value) => value,
41
- parseLiteral: parseLiteralToValue,
42
- })
43
-
44
- /**
45
- * The _FieldSet scalar represents a selection of fields.
46
- * It's used in directive arguments like @key(fields: "id") and @requires(fields: "weight").
47
- */
48
- export const FieldSetScalar = new GraphQLScalarType({
49
- name: "_FieldSet",
50
- description: "A string representing a selection of fields.",
51
- serialize: (value) => value,
52
- parseValue: (value) => value,
53
- parseLiteral: (ast) => {
54
- if (ast.kind === Kind.STRING) {
55
- return ast.value
56
- }
57
- return undefined
58
- },
59
- })
package/src/types.ts DELETED
@@ -1,114 +0,0 @@
1
- import { Effect } from "effect"
2
- import * as S from "effect/Schema"
3
-
4
- /**
5
- * Configuration for a @key directive
6
- */
7
- export interface KeyDirective {
8
- /** FieldSet selection string (e.g., "id" or "sku package") */
9
- readonly fields: string
10
- /** Whether this key can be used to resolve the entity. Default: true */
11
- readonly resolvable?: boolean
12
- }
13
-
14
- /**
15
- * All Federation 2.x directive types
16
- */
17
- export type FederationDirective =
18
- | { readonly _tag: "key"; readonly fields: string; readonly resolvable?: boolean }
19
- | { readonly _tag: "external" }
20
- | { readonly _tag: "requires"; readonly fields: string }
21
- | { readonly _tag: "provides"; readonly fields: string }
22
- | { readonly _tag: "shareable" }
23
- | { readonly _tag: "inaccessible" }
24
- | { readonly _tag: "override"; readonly from: string; readonly label?: string }
25
- | { readonly _tag: "interfaceObject" }
26
- | { readonly _tag: "tag"; readonly name: string }
27
-
28
- /**
29
- * Entity representation sent to _entities query
30
- * Contains __typename plus the key fields
31
- */
32
- export interface EntityRepresentation {
33
- readonly __typename: string
34
- readonly [key: string]: unknown
35
- }
36
-
37
- /**
38
- * Configuration for registering an entity type
39
- */
40
- export interface EntityRegistration<A, R = never> {
41
- /** Type name */
42
- readonly name: string
43
- /** Effect Schema for the entity type */
44
- readonly schema: S.Schema<A, any, any>
45
- /** Key directive configurations (at least one required) */
46
- readonly keys: readonly KeyDirective[]
47
- /** Additional directives to apply to the type */
48
- readonly directives?: readonly FederationDirective[]
49
- /**
50
- * Reference resolver - given key fields, return the full entity.
51
- * The representation contains __typename plus all fields from any matching @key.
52
- */
53
- readonly resolveReference: (
54
- representation: Partial<A> & { __typename: string }
55
- ) => Effect.Effect<A | null, any, R>
56
- }
57
-
58
- /**
59
- * Configuration for the FederatedSchemaBuilder
60
- */
61
- export interface FederatedSchemaConfig {
62
- /** Federation specification version (default: "2.3") */
63
- readonly version?: string
64
- }
65
-
66
- /**
67
- * Result of building a federated schema
68
- */
69
- export interface FederatedSchemaResult {
70
- /** The GraphQL schema with Federation queries */
71
- readonly schema: import("graphql").GraphQLSchema
72
- /** The Federation-compliant SDL with directive annotations */
73
- readonly sdl: string
74
- }
75
-
76
- /**
77
- * Convert a FederationDirective to a DirectiveApplication
78
- */
79
- export function toDirectiveApplication(
80
- directive: FederationDirective
81
- ): import("@effect-gql/core").DirectiveApplication {
82
- switch (directive._tag) {
83
- case "key":
84
- return {
85
- name: "key",
86
- args: {
87
- fields: directive.fields,
88
- ...(directive.resolvable !== undefined ? { resolvable: directive.resolvable } : {}),
89
- },
90
- }
91
- case "external":
92
- return { name: "external" }
93
- case "requires":
94
- return { name: "requires", args: { fields: directive.fields } }
95
- case "provides":
96
- return { name: "provides", args: { fields: directive.fields } }
97
- case "shareable":
98
- return { name: "shareable" }
99
- case "inaccessible":
100
- return { name: "inaccessible" }
101
- case "override":
102
- return {
103
- name: "override",
104
- args: {
105
- from: directive.from,
106
- ...(directive.label !== undefined ? { label: directive.label } : {}),
107
- },
108
- }
109
- case "interfaceObject":
110
- return { name: "interfaceObject" }
111
- case "tag":
112
- return { name: "tag", args: { name: directive.name } }
113
- }
114
- }