console/scripts/seed-usage.ts

175 lines
5 KiB
TypeScript

/**
* Example:
* `TARGET=<target_id> FEDERATION=1 STAGE=local TOKEN=<access_token> pnpm seed:usage`
*/
import { parse as parsePath } from 'path';
import { buildSchema, DocumentNode, GraphQLSchema, parse, print } from 'graphql';
import { createHive, HiveClient } from '@graphql-hive/core';
import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader';
import { loadDocuments, loadSchema, loadTypedefs } from '@graphql-tools/load';
import {
composeServices,
ServiceDefinition,
transformSupergraphToPublicSchema,
} from '@theguild/federation-composition';
const token = process.env.TOKEN || process.env.HIVE_TOKEN;
if (!token) {
throw new Error('Missing "TOKEN"');
}
const target = process.env.TARGET;
const isFederation = process.env.FEDERATION === '1';
const stage = process.env.STAGE || 'local';
const batches = parseInt(process.env.BATCHES || '10', 10) || 10;
const operationsPerBatch = parseInt(process.env.OPERATIONS || '10', 10) || 10;
const interval = parseInt(process.env.INTERVAL || '1000') || 1000;
let usageReportingEndpoint: string;
let environment: string;
switch (stage.toLowerCase()) {
case 'staging': {
usageReportingEndpoint = 'https://app.hiveready.dev/usage';
environment = 'staging';
break;
}
case 'dev': {
usageReportingEndpoint = 'https://app.buzzcheck.dev/usage';
environment = 'dev';
break;
}
default: {
usageReportingEndpoint = 'http://localhost:4001';
environment = 'local';
}
}
console.log(`
Environment: ${environment}
Usage reporting endpoint: ${usageReportingEndpoint}
`);
const createInstance = () => {
return createHive({
token,
agent: {
name: 'Hive Seed Script',
maxSize: 10,
},
debug: true,
enabled: true,
usage: {
target: target || undefined,
clientInfo: () => ({
// @todo create multiple clients with different versions and/or names
name: 'Fake Hive Client',
version: '1.1.1',
}),
endpoint: usageReportingEndpoint,
max: 10,
sampleRate: 1,
},
});
};
/**
* Not very precise, but provides enough randomization to weight the queries reasonably well.
*/
const chooseQuery = (queries: DocumentNode[]) => {
for (let i = 0; i < queries.length; i++) {
const randNumber = Math.random() * 100;
if (randNumber <= 25) {
return queries[i];
}
}
return queries[queries.length - 1];
};
function start(args: { instance: HiveClient; schema: GraphQLSchema; queries: DocumentNode[] }) {
let sentBatches = 0;
let intervalId: NodeJS.Timeout | null = setInterval(async () => {
if (sentBatches >= batches) {
console.log('Done.');
if (!!intervalId) {
clearInterval(intervalId);
await args.instance.dispose();
}
intervalId = null;
return;
}
sentBatches++;
for (let i = 0; i < operationsPerBatch; i++) {
const randNumber = Math.random() * 100;
const done = args.instance.collectUsage();
await done(
{
document: chooseQuery(args.queries),
schema: args.schema,
variableValues: {},
contextValue: {},
},
randNumber <= 95
? {
errors: undefined,
}
: {
errors: [{ message: 'oops' }],
},
);
}
}, interval);
}
const instance = createInstance();
await instance.info();
if (isFederation === false) {
const schema = await loadSchema('scripts/seed-schemas/mono.graphql', {
loaders: [new GraphQLFileLoader()],
});
const documents = await loadDocuments('scripts/seed-usage/mono/*.graphql', {
loaders: [new GraphQLFileLoader()],
});
const queries = documents.map(({ document }) => {
if (!document) {
throw new Error('Unexpected error. Could not find document.');
}
return document;
});
start({ instance, schema, queries });
} else {
const schemaDocs = await loadTypedefs('scripts/seed-schemas/federated/*.graphql', {
loaders: [new GraphQLFileLoader()],
});
const services = schemaDocs.map((d): ServiceDefinition => {
if (!d.rawSDL) {
throw new Error(`Missing SDL at "${d.location}"`);
}
if (!d.location) {
throw new Error(`Unexpected error. Missing "location".`);
}
const service = parsePath(d.location).name.replaceAll('.', '-');
return {
typeDefs: parse(d.rawSDL),
name: service,
url: `https://${service ? `${service}.` : ''}localhost/graphql`,
};
});
const { supergraphSdl, errors } = composeServices(services);
if (errors) {
throw new Error(`Could not compose:\n - ${errors.map(e => e.message).join('\n - ')}`);
}
const apiSchema = print(transformSupergraphToPublicSchema(parse(supergraphSdl)));
const documents = await loadDocuments('scripts/seed-usage/federated/*.graphql', {
loaders: [new GraphQLFileLoader()],
});
const queries = documents.map(({ document }) => {
if (!document) {
throw new Error('Unexpected error. Could not find document.');
}
return document;
});
start({ instance, schema: buildSchema(apiSchema), queries });
}