npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@wayfair/node-froid

v3.2.2

Published

Federated GQL Relay Object Identification implementation

Downloads

28

Readme

node-froid: NodeJS - Federated Relay Object Identification

Release Lint codecov Contributor Covenant license: MIT Maintainer

Table of Contents

About The Project

The problem

There isn't good support for the Relay's Object Identification spec in the Federated GraphQL ecosystem. This makes it difficult to support common patterns used to refetch objects from your graph to power things like cache TTLs and cache-miss hydration.

The solution

@wayfair/node-froid provides two key pieces of functionality:

  • id processing: a solution that can be used to run inline as part of a custom Data Source or as a completely separate subgraph (recommended) dedicated to service your object identification implementation .
  • schema generation: a schema generation script that reflects on all subgraphs in your federated graph and generates a valid relay object identification schema.
    • Can run in Federation v1 or v2 mode
    • Supports contracts!

Getting Started

To get a local copy up and running follow these simple steps.

Installation

This module is distributed via npm which is bundled with node and should be installed as one of your project's direct dependencies:

npm install @wayfair/node-froid

or

for installation via yarn

yarn add @wayfair/node-froid

This library has peerDependencies listings for graphql and graphql-relay.

Library API

handleFroidRequest

| Parameter Name | Required | Description | Type | Default | | ------------------- | -------- | --------------------------------------------------------- | ------------------------- | -------------------------- | | request | Yes | The request object passed to the froid subgraph | see specific properties | | | request.query | Yes | The query string for the request | string | | | request.variables | Yes | The variables for the request | Record<string, unknown> | | | options | | Configuration options available to handleFroidRequest | see specific properties | {} | | options.encode | | A callback for encoding the object identify key values | (string) => string | (keyString) => keyString | | options.decode | | A callback for decoding an object identifier's key values | (string) => string | (keyString) => keyString | | options.cache | | Cache to use to avoid re-parsing query documents | FroidCache | |

Returns Promise<object[]>: A promise representing the list of entity objects containing a relay-spec compliant id value.

generateFroidSchema

| Parameter Name | Required | Description | Type | Default | | -------------------------- | -------- | --------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ---------------------- | | subgraphSchemaMap | Yes | A mapping of subgraph names --> subgraph SDLs used to generate the froid schema | Map<string, string> | | | froidSubgraphName | Yes | The name of the relay subgraph service | string | | | options | | Optional configuration for schema generation | see specific properties | {} | | options.contractTags | | A list of supported contract tags | string[] | [] | | options.federatedVersion | | The version of federation to generate schema for | FederationVersion | FederationVersion.V2 | | options.typeExceptions | | Types to exclude from id field generation | string[] | [] | | options.nodeQualifier | | A custom function to qualify whether or not an entity should be included in node-relay schema | (node: DefinitionNode, objectTypes: Record<string, ObjectTypeNode>) => boolean | |

Returns DocumentNode[]: The froid schema

Usage

id Processing

Custom GraphQL Gateway Datasource

import {GraphQLDataSourceProcessOptions} from '@apollo/gateway';
import {GraphQLResponse} from 'apollo-server-types';
import {handleFroidRequest} from '@wayfair/node-froid';
import {Context} from './path/to/your/ContextType';

class FroidDataSource {
  process({
    request,
  }: Pick<
    GraphQLDataSourceProcessOptions<Context>,
    'request'
  >): Promise<GraphQLResponse> {
    return await handleFroidRequest(request);
  }
}

Custom GraphQL Gateway Datasource w/Encryption

// Datasource Implementation
import {GraphQLDataSourceProcessOptions} from '@apollo/gateway';
import {GraphQLResponse} from 'apollo-server-types';
import {
  DecodeCallback,
  EncodeCallback,
  handleFroidRequest,
} from '@wayfair/node-froid';
// You only really need this if you are using context
import {Context} from './path/to/your/ContextType';
// Used to determine which encoder to use
import {FeatureToggleManager} from './path/to/your/FeatureToggleManager';

// Interface we need to match properly encode key values
interface Encoder {
  encode: EncodeCallback;
  decode: DecodeCallback;
}

class FroidLDataSource {
  private encoder1: Encoder;
  private encoder2: Encoder;

  // Take two encoders to support live key rotation
  constructor(encoder1: Encoder, encoder2, Encoder) {
    this.encoder1 = encoder1;
    this.encoder2 = encoder2;
  }

  process({
    request,
  }: Pick<
    GraphQLDataSourceProcessOptions<Context>,
    'request'
  >): Promise<GraphQLResponse> {
    const encoder = FeatureToggleManager.useEncoder1()
      ? this.encoder1
      : this.encoder2;

    return await handleFroidRequest(request, {...encoder});
  }
}

// Sample Encoder
import crypto from 'crypto';
import {DecodeCallback, EncodeCallback} from '@wayfair/node-froid';

const ENCRYPTION_ALGORITHM = 'aes-256-cbc';

// Interface we need to match properly encode key values
interface Encoder {
  encode: EncodeCallback;
  decode: DecodeCallback;
}

type CreateEncoderArguments = {
  base64InitializationVector: string;
  base64EncryptionKey: string;
};

export class CustomEncoder implements Encoder {
  private iv: Buffer;
  private key: Buffer;

  constructor({
    base64InitializationVector,
    base64EncryptionKey,
  }: CreateEncoderArguments) {
    this.iv = Buffer.from(base64InitializationVector, 'base64');
    this.key = Buffer.from(base64EncryptionKey, 'base64');
  }

  public encode(value: string): string {
    const cipher = crypto.createCipheriv(
      ENCRYPTION_ALGORITHM,
      this.key,
      this.iv
    );
    const encryptedValue = cipher.update(value);
    const encryptedBuffer = Buffer.concat([encryptedValue, cipher.final()]);

    return encryptedBuffer.toString('base64');
  }

  public decode(value: string): object {
    const decipher = crypto.createDecipheriv(
      ENCRYPTION_ALGORITHM,
      this.key,
      this.iv
    );
    const decryptedValue = decipher.update(Buffer.from(value, 'base64'));
    const decryptedBuffer = Buffer.concat([decryptedValue, decipher.final()]);

    return decryptedBuffer.toString();
  }
}

Custom GraphQL Gateway Datasource w/Cache

import {GraphQLDataSourceProcessOptions} from '@apollo/gateway';
import {GraphQLResponse} from 'apollo-server-types';
import {handleRelayRequest} from '@wayfair/node-froid';
import {Context} from './path/to/your/ContextType';
import LRU from 'lru-cache';

const cache = new LRU({max: 500});

class RelayNodeGraphQLDataSource {
  process({
    request,
  }: Pick<
    GraphQLDataSourceProcessOptions<Context>,
    'request'
  >): Promise<GraphQLResponse> {
    return await handleRelayRequest(request, {
      cache,
    });
  }
}

Subgraph w/Express Server

import express from 'express';
import bodyParser from 'body-parser';
import {handleFroidRequest} from '@wayfair/node-froid';

const port = process.env.PORT || 5000;

const app = express();
app.use(bodyParser.urlencoded({extended: false}));
app.use(bodyParser.json());

// No need to run a full GraphQL server.
// Avoid the additional overhead and manage the route directly instead!
app.post('/graphql', async (req, res) => {
  const result = await handleFroidRequest(req.body);
  res.send(result);
});

app.listen(port, () => {
  console.log(`Froid subgraph listening on port ${port}`);
});

Schema Generation

Basic Script

import fs from 'fs';
import {print} from 'graphql';
import {generateFroidSchema} from '@wayfair/node-froid';
// You have to provide this. Apollo's public API should provide the ability to extract out subgraph SDL
import {getFederatedSchemas} from './getFederatedSchemas';

const froidSubgraphName = 'froid-service';
const variant = 'current';

// Map<string, string> where the key is the subgraph name, and the value is the SDL schema
const subgraphSchemaMap = getFederatedSchemas(variant);

const schemaAst = generateFroidSchema(subgraphSchemaMap, froidSubgraphName);

// persist results to a file to use with rover publish
fs.writeFileSync('schema.graphql', print(schemaAst));

Roadmap

See the open issues for a list of proposed features (and known issues).

Contributing

Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated. For detailed contributing guidelines, please see CONTRIBUTING.md

License

Distributed under the MIT License. See LICENSE for more information.

Contact

Project Link: https://github.com/wayfair-incubator/node-froid

Acknowledgements

This template was adapted from https://github.com/othneildrew/Best-README-Template.