Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

rivet-graphql

Package Overview
Dependencies
Maintainers
4
Versions
19
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

rivet-graphql - npm Package Compare versions

Comparing version 0.5.0 to 0.6.0-canary-20230308183240

index.ts

6

CHANGELOG.md
# rivet-graphql
## 0.6.0-canary-20230308183240
### Minor Changes
- [#34](https://github.com/hashicorp/rivet-graphql/pull/34) [`060c5c5`](https://github.com/hashicorp/rivet-graphql/commit/060c5c541be010722bf7cd8a81af464c5b9b9c20) Thanks [@dstaley](https://github.com/dstaley)! - Add support for TypedDocumentNode
## 0.5.0

@@ -4,0 +10,0 @@

40

index.d.ts

@@ -1,26 +0,28 @@

declare function _exports(url: string, options: import("graphql-request/dist/types.dom").RequestInit & {
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { GraphQLClient, type Variables } from 'graphql-request';
import type { DocumentNode } from 'graphql';
import type { TypedDocumentNode } from '@graphql-typed-document-node/core';
interface FragmentSpec {
fragment?: string | DocumentNode;
dependencies?: {
fragmentSpec?: FragmentSpec;
}[];
requiredVariables?: Record<string, unknown>;
}
declare const _default: (url: string, options: ConstructorParameters<typeof GraphQLClient>[1] & {
timeout?: number;
retryCount?: number;
}): {
<T = any>({ query, dependencies, variables }: {
query: string | DocumentNode;
}) => {
<T, V extends Variables = Variables>({ query, dependencies, variables, }: {
query: string | DocumentNode | TypedDocumentNode<T, V>;
dependencies?: {
fragmentSpec?: FragmentSpec;
}[];
variables?: Record<string, any>;
}): Promise<T>;
variables?: Record<string, unknown>;
}): Promise<V>;
client: GraphQLClient;
};
export = _exports;
export type GQLRequestInit = import("graphql-request/dist/types.dom").RequestInit;
export type DocumentNode = import("graphql/language/ast").DocumentNode;
export type exports = import("graphql-request").GraphQLClient;
export type FragmentSpec = {
fragment?: string | DocumentNode;
dependencies?: {
fragmentSpec?: FragmentSpec;
}[];
requiredVariables?: Record<string, any>;
};
import { GraphQLClient } from "graphql-request";
//# sourceMappingURL=index.d.ts.map
export = _default;

@@ -0,1 +1,2 @@

"use strict";
/**

@@ -5,81 +6,20 @@ * Copyright (c) HashiCorp, Inc.

*/
//@ts-check
let { GraphQLClient } = require('graphql-request')
const { parse, parseType } = require('graphql/language/parser')
const { print } = require('graphql/language/printer')
/** @typedef { import("graphql-request/dist/types.dom").RequestInit} GQLRequestInit */
/** @typedef { import("graphql/language/ast").DocumentNode} DocumentNode */
/** @typedef { import("graphql-request").GraphQLClient } */
const graphql_request_1 = require("graphql-request");
const parser_1 = require("graphql/language/parser");
const printer_1 = require("graphql/language/printer");
/**
* @typedef {Object} FragmentSpec
* @property {string | DocumentNode} [fragment]
* @property {{ fragmentSpec?: FragmentSpec }[]} [dependencies]
* @property {Record<string, any>} [requiredVariables]
*/
/**
*
* @param {string} url
* @param {GQLRequestInit & { timeout?: number, retryCount?: number }} options
*/
module.exports = function Rivet(url, options) {
if (!options.timeout) options.timeout = 30000
const retryCount = options.retryCount || 0
delete options.retryCount
const client = new GraphQLClient(url, options)
if (retryCount) {
client.request = requestWithRetry.bind(
null,
retryCount,
client.request.bind(client)
)
}
/**
*
* @template [T=any]
* @param {Object} params
* @param {string | DocumentNode} params.query
* @param {{ fragmentSpec?: FragmentSpec }[]} [params.dependencies]
* @param {Record<string, any>} [params.variables]
* @returns {Promise<T>}
*/
function fetch({ query, dependencies = [], variables }) {
if (!query) throw fetchMissingQueryError()
const _dependencies = processDependencies(dependencies)
const _query = processVariables(dependencies, variables, query)
return client.request(
`${_query}\n${[..._dependencies].join('\n')}`,
variables
)
}
fetch.client = client
return fetch
}
/**
*
* @param {{ fragmentSpec?: FragmentSpec }[]} dependencies
*/
function extractFragmentSpecs(dependencies) {
// throw an error if dependencies isn't an array
if (!Array.isArray(dependencies)) throw dependenciesTypeError(dependencies)
// filter out any dependencies that don't have a fragment spec
return dependencies
.filter((d) => d.fragmentSpec)
.map((d) => {
return Object.assign({}, d.fragmentSpec, { __original: d })
})
// throw an error if dependencies isn't an array
if (!Array.isArray(dependencies))
throw dependenciesTypeError(dependencies);
// filter out any dependencies that don't have a fragment spec
return dependencies
.filter((d) => d.fragmentSpec)
.map((d) => {
return Object.assign({}, d.fragmentSpec, { __original: d });
});
}
// Go through component dependencies and extract all of the fragments that we need

@@ -93,23 +33,18 @@ // to make the query. This is a recursive function to account for deep nested deps.

function processDependencies(_dependencies) {
const dependencies = extractFragmentSpecs(_dependencies)
return dependencies.reduce((acc, component) => {
// Add the main fragment if one is provided
if (component.fragment) {
acc.push(
typeof component.fragment === 'string'
? component.fragment
: print(component.fragment)
)
}
// Recursively iterate through dependencies and collect all fragments
if (component.dependencies) {
acc.push(...processDependencies(component.dependencies))
}
// Dedupe the array before returning
return [...new Set(acc)]
}, [])
const dependencies = extractFragmentSpecs(_dependencies);
return dependencies.reduce((acc, component) => {
// Add the main fragment if one is provided
if (component.fragment) {
acc.push(typeof component.fragment === 'string'
? component.fragment
: (0, printer_1.print)(component.fragment));
}
// Recursively iterate through dependencies and collect all fragments
if (component.dependencies) {
acc.push(...processDependencies(component.dependencies));
}
// Dedupe the array before returning
return [...new Set(acc)];
}, []);
}
// Go through components and variables and ensure that the user has provided values

@@ -126,41 +61,35 @@ // for all variables that components need. Then dynamically inject variables that

function processVariables(dependencies, variables, query) {
// First, we loop through dependencies to extract the variables they define
// Along the way we throw clear errors if there are any variable mismatched
const vars = _findVariables(dependencies, variables)
// If there are no variables, we can return
if (!Object.keys(vars).length) {
return typeof query === 'string' ? query : print(query)
}
// Otherwise, inject those variables into the query's params.
// First we parse the query into an AST
const ast = typeof query === 'string' ? parse(query) : query
// See function definition below for details
if (ast.definitions.length > 1) throw multipleQueriesError()
// Then we loop through the variables and create AST nodes for them
Object.entries(vars).map(([_name, _type]) => {
const variable = {
kind: 'Variable',
name: { kind: 'Name', value: _name },
// First, we loop through dependencies to extract the variables they define
// Along the way we throw clear errors if there are any variable mismatched
const vars = _findVariables(dependencies, variables);
// If there are no variables, we can return
if (!Object.keys(vars).length) {
return typeof query === 'string' ? query : (0, printer_1.print)(query);
}
const type = parseType(_type)
// Add the AST nodes to the variable definitions at the top of the query.
// Worth noting it only does this for the first query defined in the file,
// but we throw if there is more than one anyway.
//@ts-ignore
ast.definitions[0].variableDefinitions.push({
kind: 'VariableDefinition',
variable,
type,
})
})
// Finally we stringify the modified AST back into a graphql string
return print(ast)
// Otherwise, inject those variables into the query's params.
// First we parse the query into an AST
const ast = typeof query === 'string' ? (0, parser_1.parse)(query) : query;
// See function definition below for details
if (ast.definitions.length > 1)
throw multipleQueriesError();
// Then we loop through the variables and create AST nodes for them
Object.entries(vars).map(([_name, _type]) => {
const variable = {
kind: 'Variable',
name: { kind: 'Name', value: _name },
};
const type = (0, parser_1.parseType)(_type);
// Add the AST nodes to the variable definitions at the top of the query.
// Worth noting it only does this for the first query defined in the file,
// but we throw if there is more than one anyway.
//@ts-ignore
ast.definitions[0].variableDefinitions.push({
kind: 'VariableDefinition',
variable,
type,
});
});
// Finally we stringify the modified AST back into a graphql string
return (0, printer_1.print)(ast);
}
// Internal function, recursively extracts "variables" arguments from a set of components

@@ -175,78 +104,62 @@ // and its deep nested dependencies.

function _findVariables(_dependencies, variables) {
const dependencies = extractFragmentSpecs(_dependencies)
return dependencies.reduce((acc, component) => {
if (component.requiredVariables) {
// If no variables are passed to fetch but dependencies define variables, error
if (!variables) throw variableMismatchError(component)
Object.entries(component.requiredVariables).map(([k, v]) => {
// If variables are present but the one we need is missing, error
if (!variables[k]) throw variableMismatchError(component, k)
// Otherwise, add the variable to our list
acc[k] = v
})
}
// If the component has dependencies, we recurse to get an object containing
// any dependency variables, then add to the object. We naturally dedupe since
// this is an object, so we just add all.
if (component.dependencies) {
Object.entries(_findVariables(component.dependencies, variables)).map(
([k, v]) => {
acc[k] = v
const dependencies = extractFragmentSpecs(_dependencies);
return dependencies.reduce((acc, component) => {
if (component.requiredVariables) {
// If no variables are passed to fetch but dependencies define variables, error
if (!variables)
throw variableMismatchError(component, null);
Object.entries(component.requiredVariables).map(([k, v]) => {
// If variables are present but the one we need is missing, error
if (!variables[k])
throw variableMismatchError(component, k);
// Otherwise, add the variable to our list
acc[k] = v;
});
}
)
}
return acc
}, {})
// If the component has dependencies, we recurse to get an object containing
// any dependency variables, then add to the object. We naturally dedupe since
// this is an object, so we just add all.
if (component.dependencies) {
Object.entries(_findVariables(component.dependencies, variables)).map(([k, v]) => {
acc[k] = v;
});
}
return acc;
}, {});
}
// Super clear error messages when component dependencies ask for variables that
// are not provided in the fetch query.
function variableMismatchError(component, specificVar) {
//@ts-ignore
const fragmentName = parse(component.fragment).definitions[0].name.value
const fragmentVars = Object.keys(component.requiredVariables).map(
(v) => `"${v}"`
)
return new Error(
`The fragment "${fragmentName}" requires ${
specificVar
//@ts-ignore
const fragmentName = (0, parser_1.parse)(component.fragment).definitions[0].name.value;
const fragmentVars = Object.keys(component.requiredVariables).map((v) => `"${v}"`);
return new Error(`The fragment "${fragmentName}" requires ${specificVar
? `the variable "${specificVar}"`
: `variables ${fragmentVars.join(', ')}`
}, but it is not provided. Make sure you are passing "variables" as an argument to "fetch", and that it defines ${
specificVar ? `"${specificVar}"` : fragmentVars.join(', ')
}.`
)
: `variables ${fragmentVars.join(', ')}`}, but it is not provided. Make sure you are passing "variables" as an argument to "fetch", and that it defines ${specificVar ? `"${specificVar}"` : fragmentVars.join(', ')}.`);
}
// request with retries if the query fails
async function requestWithRetry(retryCount, originalRequest, ...args) {
const uuid = _createUUID()
const maxRetries = retryCount
for (let retry = 1; retry <= maxRetries; retry++) {
try {
return await originalRequest(...args)
} catch (err) {
console.log(`[${uuid}] Failed retry #${retry}, retrying...`)
const isLastAttempt = retry === maxRetries
if (isLastAttempt) {
console.error(`[${uuid}] Failed all retries, throwing!`)
throw err
}
const uuid = _createUUID();
const maxRetries = retryCount;
for (let retry = 1; retry <= maxRetries; retry++) {
try {
return await originalRequest(...args);
}
catch (err) {
console.log(`[${uuid}] Failed retry #${retry}, retrying...`);
const isLastAttempt = retry === maxRetries;
if (isLastAttempt) {
console.error(`[${uuid}] Failed all retries, throwing!`);
throw err;
}
}
}
}
}
// used to identify a retried request
function _createUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = (Math.random() * 16) | 0,
v = c == 'x' ? r : (r & 0x3) | 0x8
return v.toString(16)
})
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = (Math.random() * 16) | 0, v = c == 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
// We error if there were multiple queries, since graphql errors both

@@ -257,17 +170,28 @@ // if a variable is present but not used, or not present and used. In theory

function multipleQueriesError() {
return new Error(
'You have defined multiple queries in one request and are also using variables. At the moment, we do not support the use of variables with multiple queries. Please either consolidate to one query per request, or make a PR to to add this functionalty.'
)
return new Error('You have defined multiple queries in one request and are also using variables. At the moment, we do not support the use of variables with multiple queries. Please either consolidate to one query per request, or make a PR to to add this functionalty.');
}
function fetchMissingQueryError() {
return new Error('The "query" parameter is required')
return new Error('The "query" parameter is required');
}
function dependenciesTypeError(dependencies) {
return new Error(
`The "dependencies" argument must be an array, the following dependency argument is not valid: ${JSON.stringify(
dependencies
)}`
)
return new Error(`The "dependencies" argument must be an array, the following dependency argument is not valid: ${JSON.stringify(dependencies)}`);
}
module.exports = function Rivet(url, options) {
if (!options.timeout)
options.timeout = 30000;
const retryCount = options.retryCount || 0;
delete options.retryCount;
const client = new graphql_request_1.GraphQLClient(url, options);
if (retryCount) {
client.request = requestWithRetry.bind(null, retryCount, client.request.bind(client));
}
function fetch({ query, dependencies = [], variables, }) {
if (!query)
throw fetchMissingQueryError();
const _dependencies = processDependencies(dependencies);
const _query = processVariables(dependencies, variables, query);
return client.request(`${_query}\n${[..._dependencies].join('\n')}`, variables);
}
fetch.client = client;
return fetch;
};
{
"name": "rivet-graphql",
"description": "a relay-like graphql data loading system for nextjs",
"version": "0.5.0",
"version": "0.6.0-canary-20230308183240",
"author": "Jeff Escalante",

@@ -10,4 +10,5 @@ "bugs": {

"dependencies": {
"graphql": "^15.3.0",
"graphql-request": "^3.0.0"
"@graphql-typed-document-node/core": "^3.1.2",
"graphql": "^16.6.0",
"graphql-request": "^5.2.0"
},

@@ -19,3 +20,3 @@ "devDependencies": {

"rewire": "^5.0.0",
"typescript": "^4.6.2"
"typescript": "^4.9.5"
},

@@ -39,5 +40,5 @@ "homepage": "https://github.com/hashicorp/rivet-graphql#readme",

"release:canary": "npm run generate:types && changeset publish --tag canary",
"test": "jest",
"test": "npm run generate:types && jest",
"generate:types": "tsc -p ."
}
}
{
"compilerOptions": {
"target": "es2020",
"lib": ["es2020"],
"module": "commonjs",
"allowJs": true,
"declaration": true,
"emitDeclarationOnly": true,
"declarationMap": true,
"downlevelIteration": true,
"skipLibCheck": true
},
"include": ["index.js"]
"include": ["index.ts"]
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc