@effect-gql/core 1.0.0 → 1.1.1

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/index.cjs CHANGED
@@ -6,7 +6,6 @@ var S2 = require('effect/Schema');
6
6
  var AST = require('effect/SchemaAST');
7
7
  var DataLoader = require('dataloader');
8
8
  var platform = require('@effect/platform');
9
- var graphqlWs = require('graphql-ws');
10
9
 
11
10
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
11
 
@@ -837,7 +836,14 @@ var GraphQLSchemaBuilder = class _GraphQLSchemaBuilder {
837
836
  * Register an object type from a schema
838
837
  */
839
838
  objectType(config) {
840
- const { schema, implements: implementsInterfaces, directives, cacheControl, fields } = config;
839
+ const {
840
+ schema,
841
+ description,
842
+ implements: implementsInterfaces,
843
+ directives,
844
+ cacheControl,
845
+ fields
846
+ } = config;
841
847
  const name = config.name ?? getSchemaName(schema);
842
848
  if (!name) {
843
849
  throw new Error(
@@ -845,7 +851,14 @@ var GraphQLSchemaBuilder = class _GraphQLSchemaBuilder {
845
851
  );
846
852
  }
847
853
  const newTypes = new Map(this.state.types);
848
- newTypes.set(name, { name, schema, implements: implementsInterfaces, directives, cacheControl });
854
+ newTypes.set(name, {
855
+ name,
856
+ schema,
857
+ description,
858
+ implements: implementsInterfaces,
859
+ directives,
860
+ cacheControl
861
+ });
849
862
  let newObjectFields = this.state.objectFields;
850
863
  if (fields) {
851
864
  newObjectFields = new Map(this.state.objectFields);
@@ -1214,6 +1227,7 @@ var GraphQLSchemaBuilder = class _GraphQLSchemaBuilder {
1214
1227
  const implementedInterfaces = typeReg.implements?.map((name) => interfaceRegistry.get(name)).filter(Boolean) ?? [];
1215
1228
  const graphqlType = new graphql.GraphQLObjectType({
1216
1229
  name: typeName,
1230
+ description: typeReg.description,
1217
1231
  fields: () => {
1218
1232
  const baseFields = schemaToFields(typeReg.schema, sharedCtx);
1219
1233
  const additionalFields = this.state.objectFields.get(typeName);
@@ -1489,7 +1503,8 @@ var LoaderRegistry = class {
1489
1503
  this.Service,
1490
1504
  effect.Effect.gen(function* () {
1491
1505
  const instances = {};
1492
- for (const [name, def] of Object.entries(self.definitions)) {
1506
+ for (const name of Object.keys(self.definitions)) {
1507
+ const def = self.definitions[name];
1493
1508
  instances[name] = yield* createDataLoader(def);
1494
1509
  }
1495
1510
  return instances;
@@ -1509,6 +1524,9 @@ var LoaderRegistry = class {
1509
1524
  /**
1510
1525
  * Load a single value by key.
1511
1526
  * This is the most common operation in resolvers.
1527
+ *
1528
+ * @internal The internal cast is safe because LoaderInstances<Defs> guarantees
1529
+ * that loaders[name] is a DataLoader with the correct key/value types.
1512
1530
  */
1513
1531
  load(name, key) {
1514
1532
  const self = this;
@@ -1521,6 +1539,9 @@ var LoaderRegistry = class {
1521
1539
  /**
1522
1540
  * Load multiple values by keys.
1523
1541
  * All keys are batched into a single request.
1542
+ *
1543
+ * @internal The internal cast is safe because LoaderInstances<Defs> guarantees
1544
+ * that loaders[name] is a DataLoader with the correct key/value types.
1524
1545
  */
1525
1546
  loadMany(name, keys) {
1526
1547
  const self = this;
@@ -1537,35 +1558,39 @@ var LoaderRegistry = class {
1537
1558
  });
1538
1559
  }
1539
1560
  };
1561
+ function createSingleDataLoader(def, context) {
1562
+ return new DataLoader__default.default(async (keys) => {
1563
+ const items = await effect.Effect.runPromise(def.batch(keys).pipe(effect.Effect.provide(context)));
1564
+ return keys.map((key) => {
1565
+ const item = items.find((i) => def.key(i) === key);
1566
+ if (!item) return new Error(`Not found: ${key}`);
1567
+ return item;
1568
+ });
1569
+ });
1570
+ }
1571
+ function createGroupedDataLoader(def, context) {
1572
+ return new DataLoader__default.default(async (keys) => {
1573
+ const items = await effect.Effect.runPromise(def.batch(keys).pipe(effect.Effect.provide(context)));
1574
+ const map = /* @__PURE__ */ new Map();
1575
+ for (const item of items) {
1576
+ const key = def.groupBy(item);
1577
+ let arr = map.get(key);
1578
+ if (!arr) {
1579
+ arr = [];
1580
+ map.set(key, arr);
1581
+ }
1582
+ arr.push(item);
1583
+ }
1584
+ return keys.map((key) => map.get(key) ?? []);
1585
+ });
1586
+ }
1540
1587
  function createDataLoader(def) {
1541
1588
  return effect.Effect.gen(function* () {
1542
1589
  const context = yield* effect.Effect.context();
1543
1590
  if (def._tag === "single") {
1544
- const loader = new DataLoader__default.default(async (keys) => {
1545
- const items = await effect.Effect.runPromise(def.batch(keys).pipe(effect.Effect.provide(context)));
1546
- return keys.map((key) => {
1547
- const item = items.find((i) => def.key(i) === key);
1548
- if (!item) return new Error(`Not found: ${key}`);
1549
- return item;
1550
- });
1551
- });
1552
- return loader;
1591
+ return createSingleDataLoader(def, context);
1553
1592
  } else {
1554
- const loader = new DataLoader__default.default(async (keys) => {
1555
- const items = await effect.Effect.runPromise(def.batch(keys).pipe(effect.Effect.provide(context)));
1556
- const map = /* @__PURE__ */ new Map();
1557
- for (const item of items) {
1558
- const key = def.groupBy(item);
1559
- let arr = map.get(key);
1560
- if (!arr) {
1561
- arr = [];
1562
- map.set(key, arr);
1563
- }
1564
- arr.push(item);
1565
- }
1566
- return keys.map((key) => map.get(key) ?? []);
1567
- });
1568
- return loader;
1593
+ return createGroupedDataLoader(def, context);
1569
1594
  }
1570
1595
  });
1571
1596
  }
@@ -1789,7 +1814,7 @@ var GraphQLRouterConfigFromEnv = effect.Config.all({
1789
1814
  );
1790
1815
 
1791
1816
  // src/server/graphiql.ts
1792
- var graphiqlHtml = (endpoint) => `<!DOCTYPE html>
1817
+ var graphiqlHtml = (endpoint, subscriptionEndpoint) => `<!DOCTYPE html>
1793
1818
  <html lang="en">
1794
1819
  <head>
1795
1820
  <meta charset="utf-8" />
@@ -1817,6 +1842,7 @@ var graphiqlHtml = (endpoint) => `<!DOCTYPE html>
1817
1842
  <script>
1818
1843
  const fetcher = GraphiQL.createFetcher({
1819
1844
  url: ${JSON.stringify(endpoint)},
1845
+ subscriptionUrl: ${JSON.stringify(subscriptionEndpoint ?? endpoint)},
1820
1846
  });
1821
1847
  ReactDOM.createRoot(document.getElementById('graphiql')).render(
1822
1848
  React.createElement(GraphiQL, { fetcher })
@@ -2492,19 +2518,32 @@ var defaultErrorHandler = (cause) => (process.env.NODE_ENV !== "production" ? ef
2492
2518
  ).pipe(effect.Effect.orDie)
2493
2519
  )
2494
2520
  );
2495
- var parseGraphQLQuery = (query2, extensionsService) => effect.Effect.gen(function* () {
2521
+ var GraphQLRequestBodySchema = effect.Schema.Struct({
2522
+ query: effect.Schema.String,
2523
+ variables: effect.Schema.optionalWith(effect.Schema.Record({ key: effect.Schema.String, value: effect.Schema.Unknown }), {
2524
+ as: "Option"
2525
+ }),
2526
+ operationName: effect.Schema.optionalWith(effect.Schema.String, { as: "Option" })
2527
+ });
2528
+ var decodeRequestBody = platform.HttpIncomingMessage.schemaBodyJson(GraphQLRequestBodySchema);
2529
+ var parseGraphQLQuery = (query2, extensionsService) => {
2496
2530
  try {
2497
2531
  const document = graphql.parse(query2);
2498
- return { ok: true, document };
2532
+ return effect.Effect.succeed({ ok: true, document });
2499
2533
  } catch (parseError) {
2500
- const extensionData = yield* extensionsService.get();
2501
- const response = yield* platform.HttpServerResponse.json({
2502
- errors: [{ message: String(parseError) }],
2503
- extensions: Object.keys(extensionData).length > 0 ? extensionData : void 0
2504
- }).pipe(effect.Effect.orDie);
2505
- return { ok: false, response };
2534
+ return extensionsService.get().pipe(
2535
+ effect.Effect.flatMap(
2536
+ (extensionData) => platform.HttpServerResponse.json({
2537
+ errors: [{ message: String(parseError) }],
2538
+ extensions: Object.keys(extensionData).length > 0 ? extensionData : void 0
2539
+ }).pipe(
2540
+ effect.Effect.orDie,
2541
+ effect.Effect.map((response) => ({ ok: false, response }))
2542
+ )
2543
+ )
2544
+ );
2506
2545
  }
2507
- });
2546
+ };
2508
2547
  var runComplexityValidation = (body, schema, fieldComplexities, complexityConfig) => {
2509
2548
  if (!complexityConfig) {
2510
2549
  return effect.Effect.void;
@@ -2524,8 +2563,9 @@ var runComplexityValidation = (body, schema, fieldComplexities, complexityConfig
2524
2563
  )
2525
2564
  );
2526
2565
  };
2527
- var executeGraphQLQuery = (schema, document, variables, operationName, runtime) => effect.Effect.gen(function* () {
2528
- const executeResult = yield* effect.Effect.try({
2566
+ var isPromiseLike = (value) => value !== null && typeof value === "object" && "then" in value && typeof value.then === "function";
2567
+ var executeGraphQLQuery = (schema, document, variables, operationName, runtime) => {
2568
+ const tryExecute = effect.Effect.try({
2529
2569
  try: () => graphql.execute({
2530
2570
  schema,
2531
2571
  document,
@@ -2535,33 +2575,34 @@ var executeGraphQLQuery = (schema, document, variables, operationName, runtime)
2535
2575
  }),
2536
2576
  catch: (error) => new Error(String(error))
2537
2577
  });
2538
- if (executeResult && typeof executeResult === "object" && "then" in executeResult) {
2539
- return yield* effect.Effect.promise(
2540
- () => executeResult
2541
- );
2542
- }
2543
- return executeResult;
2544
- });
2545
- var computeCacheControlHeader = (document, operationName, schema, cacheHints, cacheControlConfig) => effect.Effect.gen(function* () {
2578
+ return tryExecute.pipe(
2579
+ effect.Effect.flatMap((executeResult) => {
2580
+ if (isPromiseLike(executeResult)) {
2581
+ return effect.Effect.promise(() => executeResult);
2582
+ }
2583
+ return effect.Effect.succeed(executeResult);
2584
+ })
2585
+ );
2586
+ };
2587
+ var computeCacheControlHeader = (document, operationName, schema, cacheHints, cacheControlConfig) => {
2546
2588
  if (cacheControlConfig?.enabled === false || cacheControlConfig?.calculateHttpHeaders === false) {
2547
- return void 0;
2589
+ return effect.Effect.succeed(void 0);
2548
2590
  }
2549
2591
  const operations = document.definitions.filter(
2550
2592
  (d) => d.kind === graphql.Kind.OPERATION_DEFINITION
2551
2593
  );
2552
2594
  const operation = operationName ? operations.find((o) => o.name?.value === operationName) : operations[0];
2553
2595
  if (!operation || operation.operation === "mutation") {
2554
- return void 0;
2596
+ return effect.Effect.succeed(void 0);
2555
2597
  }
2556
- const cachePolicy = yield* computeCachePolicy({
2598
+ return computeCachePolicy({
2557
2599
  document,
2558
2600
  operation,
2559
2601
  schema,
2560
2602
  cacheHints,
2561
2603
  config: cacheControlConfig ?? {}
2562
- });
2563
- return toCacheControlHeader(cachePolicy);
2564
- });
2604
+ }).pipe(effect.Effect.map(toCacheControlHeader));
2605
+ };
2565
2606
  var buildGraphQLResponse = (result, extensionData, cacheControlHeader) => {
2566
2607
  const finalResult = Object.keys(extensionData).length > 0 ? {
2567
2608
  ...result,
@@ -2599,7 +2640,12 @@ var makeGraphQLRouter = (schema, layer, options = {}) => {
2599
2640
  const extensionsService = yield* makeExtensionsService();
2600
2641
  const runtime = yield* effect.Effect.runtime();
2601
2642
  const request = yield* platform.HttpServerRequest.HttpServerRequest;
2602
- const body = yield* request.json;
2643
+ const parsedBody = yield* decodeRequestBody(request);
2644
+ const body = {
2645
+ query: parsedBody.query,
2646
+ variables: parsedBody.variables._tag === "Some" ? parsedBody.variables.value : void 0,
2647
+ operationName: parsedBody.operationName._tag === "Some" ? parsedBody.operationName.value : void 0
2648
+ };
2603
2649
  const parseResult = yield* parseGraphQLQuery(body.query, extensionsService);
2604
2650
  if (!parseResult.ok) {
2605
2651
  return parseResult.response;
@@ -2608,7 +2654,7 @@ var makeGraphQLRouter = (schema, layer, options = {}) => {
2608
2654
  yield* runParseHooks(extensions, body.query, document).pipe(
2609
2655
  effect.Effect.provideService(ExtensionsService, extensionsService)
2610
2656
  );
2611
- const validationRules = resolvedConfig.introspection ? void 0 : [...graphql.specifiedRules, graphql.NoSchemaIntrospectionCustomRule];
2657
+ const validationRules = resolvedConfig.introspection ? void 0 : graphql.specifiedRules.concat(graphql.NoSchemaIntrospectionCustomRule);
2612
2658
  const validationErrors = graphql.validate(schema, document, validationRules);
2613
2659
  yield* runValidateHooks(extensions, document, validationErrors).pipe(
2614
2660
  effect.Effect.provideService(ExtensionsService, extensionsService)
@@ -2669,9 +2715,12 @@ var makeGraphQLRouter = (schema, layer, options = {}) => {
2669
2715
  platform.HttpRouter.post(resolvedConfig.path, graphqlHandler)
2670
2716
  );
2671
2717
  if (resolvedConfig.graphiql) {
2672
- const { path, endpoint } = resolvedConfig.graphiql;
2718
+ const { path, endpoint, subscriptionEndpoint } = resolvedConfig.graphiql;
2673
2719
  router = router.pipe(
2674
- platform.HttpRouter.get(path, platform.HttpServerResponse.html(graphiqlHtml(endpoint)))
2720
+ platform.HttpRouter.get(
2721
+ path,
2722
+ platform.HttpServerResponse.html(graphiqlHtml(endpoint, subscriptionEndpoint))
2723
+ )
2675
2724
  );
2676
2725
  }
2677
2726
  return router;
@@ -2846,27 +2895,45 @@ var runConnectionLifecycle = (socket, wsServer, extra) => effect.Effect.gen(func
2846
2895
  effect.Effect.catchAllCause(() => effect.Effect.void),
2847
2896
  effect.Effect.scoped
2848
2897
  );
2898
+ var importGraphqlWs = effect.Effect.tryPromise({
2899
+ try: () => import('graphql-ws'),
2900
+ catch: () => new Error(
2901
+ "graphql-ws is required for WebSocket subscriptions. Install it with: npm install graphql-ws"
2902
+ )
2903
+ });
2849
2904
  var makeGraphQLWSHandler = (schema, layer, options) => {
2850
2905
  const complexityConfig = options?.complexity;
2851
2906
  const fieldComplexities = options?.fieldComplexities ?? /* @__PURE__ */ new Map();
2852
- const serverOptions = {
2853
- schema,
2854
- context: async (ctx) => {
2855
- const extra = ctx.extra;
2856
- return {
2857
- runtime: extra.runtime,
2858
- ...extra.connectionParams
2859
- };
2860
- },
2861
- subscribe: async (args) => graphql.subscribe(args),
2862
- onConnect: makeOnConnectHandler(options),
2863
- onDisconnect: makeOnDisconnectHandler(options),
2864
- onSubscribe: makeOnSubscribeHandler(options, schema, complexityConfig, fieldComplexities),
2865
- onComplete: makeOnCompleteHandler(options),
2866
- onError: makeOnErrorHandler(options)
2907
+ let wsServerPromise = null;
2908
+ const getOrCreateServer = async () => {
2909
+ if (!wsServerPromise) {
2910
+ wsServerPromise = effect.Effect.runPromise(importGraphqlWs).then(({ makeServer }) => {
2911
+ const serverOptions = {
2912
+ schema,
2913
+ context: async (ctx) => {
2914
+ const extra = ctx.extra;
2915
+ return {
2916
+ runtime: extra.runtime,
2917
+ ...extra.connectionParams
2918
+ };
2919
+ },
2920
+ subscribe: async (args) => graphql.subscribe(args),
2921
+ onConnect: makeOnConnectHandler(options),
2922
+ onDisconnect: makeOnDisconnectHandler(options),
2923
+ onSubscribe: makeOnSubscribeHandler(options, schema, complexityConfig, fieldComplexities),
2924
+ onComplete: makeOnCompleteHandler(options),
2925
+ onError: makeOnErrorHandler(options)
2926
+ };
2927
+ return makeServer(serverOptions);
2928
+ });
2929
+ }
2930
+ return wsServerPromise;
2867
2931
  };
2868
- const wsServer = graphqlWs.makeServer(serverOptions);
2869
2932
  return (socket) => effect.Effect.gen(function* () {
2933
+ const wsServer = yield* effect.Effect.tryPromise({
2934
+ try: () => getOrCreateServer(),
2935
+ catch: (error) => error
2936
+ });
2870
2937
  const runtime = yield* effect.Effect.provide(effect.Effect.runtime(), layer);
2871
2938
  const extra = {
2872
2939
  socket,