@mercnet/core
Version:
Core utilities and types for MercNet ecosystem
729 lines (724 loc) • 21.6 kB
JavaScript
import { GraphQLClient } from 'graphql-request';
import gql from 'graphql-tag';
import { z } from 'zod';
export { z } from 'zod';
// @mercnet/core - Built with tsup
// src/utils/index.ts
var capitalize = (str) => {
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
};
var camelCase = (str) => {
return str.replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => {
return index === 0 ? word.toLowerCase() : word.toUpperCase();
}).replace(/\s+/g, "");
};
var pick = (obj, keys) => {
const result = {};
keys.forEach((key) => {
if (key in obj) {
result[key] = obj[key];
}
});
return result;
};
var isObject = (item) => {
return item !== null && typeof item === "object" && !Array.isArray(item);
};
var isString = (value) => {
return typeof value === "string";
};
var isNumber = (value) => {
return typeof value === "number" && !isNaN(value);
};
var isBoolean = (value) => {
return typeof value === "boolean";
};
var isFunction = (value) => {
return typeof value === "function";
};
var isArray = (value) => {
return Array.isArray(value);
};
var unique = (array) => {
return Array.from(new Set(array));
};
var sleep = (ms) => {
return new Promise((resolve) => globalThis.setTimeout(resolve, ms));
};
var retry = async (fn, options) => {
let lastError;
for (let i = 0; i <= options.retries; i++) {
try {
return await fn();
} catch (error) {
lastError = error;
if (i < options.retries) {
const delay = options.delay || 1e3;
const backoff = options.backoff || 1;
await sleep(delay * Math.pow(backoff, i));
}
}
}
throw lastError;
};
var formatDate = (date, format = "YYYY-MM-DD") => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
const hours = String(date.getHours()).padStart(2, "0");
const minutes = String(date.getMinutes()).padStart(2, "0");
const seconds = String(date.getSeconds()).padStart(2, "0");
return format.replace("YYYY", String(year)).replace("MM", month).replace("DD", day).replace("HH", hours).replace("mm", minutes).replace("ss", seconds);
};
var isValidDate = (date) => {
return date instanceof Date && !isNaN(date.getTime());
};
var getEnv = (key, defaultValue) => {
if (typeof process !== "undefined" && process.env) {
return process.env[key] || defaultValue || "";
}
return defaultValue || "";
};
var isDevelopment = () => {
return getEnv("NODE_ENV") === "development";
};
var isProduction = () => {
return getEnv("NODE_ENV") === "production";
};
var MercNetError = class extends Error {
constructor(message, code, details) {
super(message);
this.code = code;
this.details = details;
this.name = "MercNetError";
}
};
var createError = (message, code, details) => {
return new MercNetError(message, code, details);
};
var GetAllMarketsDocument = gql`
query GetAllMarkets {
marketCollection {
edges {
node {
id
name
short_name
ticker
price
type
created_at
last_update_at
trend_status
sentiment
sentiment_update_at
market_activity_score_1h
market_activity_score_1d
data
status
}
}
}
}
`;
var GetMarketByIdDocument = gql`
query GetMarketById($id: Int!) {
marketCollection(filter: { id: { eq: $id } }) {
edges {
node {
id
name
short_name
ticker
price
type
created_at
last_update_at
trend_status
sentiment
sentiment_update_at
market_activity_score_1h
market_activity_score_1d
data
status
}
}
}
}
`;
var GetMarketByTickerDocument = gql`
query GetMarketByTicker($ticker: String!) {
marketCollection(filter: { ticker: { eq: $ticker } }) {
edges {
node {
id
name
short_name
ticker
price
type
created_at
last_update_at
trend_status
sentiment
sentiment_update_at
market_activity_score_1h
market_activity_score_1d
data
status
}
}
}
}
`;
var GetMarketsByStatusDocument = gql`
query GetMarketsByStatus($status: String!) {
marketCollection(filter: { status: { eq: $status } }) {
edges {
node {
id
name
short_name
ticker
price
type
created_at
last_update_at
trend_status
sentiment
sentiment_update_at
market_activity_score_1h
market_activity_score_1d
data
status
}
}
}
}
`;
var defaultWrapper = (action, _operationName, _operationType, _variables) => action();
function getSdk(client, withWrapper = defaultWrapper) {
return {
GetAllMarkets(variables, requestHeaders, signal) {
return withWrapper(
(wrappedRequestHeaders) => client.request({
document: GetAllMarketsDocument,
variables,
requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders },
signal
}),
"GetAllMarkets",
"query",
variables
);
},
GetMarketById(variables, requestHeaders, signal) {
return withWrapper(
(wrappedRequestHeaders) => client.request({
document: GetMarketByIdDocument,
variables,
requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders },
signal
}),
"GetMarketById",
"query",
variables
);
},
GetMarketByTicker(variables, requestHeaders, signal) {
return withWrapper(
(wrappedRequestHeaders) => client.request({
document: GetMarketByTickerDocument,
variables,
requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders },
signal
}),
"GetMarketByTicker",
"query",
variables
);
},
GetMarketsByStatus(variables, requestHeaders, signal) {
return withWrapper(
(wrappedRequestHeaders) => client.request({
document: GetMarketsByStatusDocument,
variables,
requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders },
signal
}),
"GetMarketsByStatus",
"query",
variables
);
}
};
}
// src/graphql/index.ts
var DEFAULT_CONFIG = {
retries: 3,
debug: false
};
var MercNetGraphQLClient = class {
client;
config;
sdk;
constructor(config) {
this.config = { ...DEFAULT_CONFIG, ...config };
this.client = new GraphQLClient(this.config.endpoint, {
headers: this.config.headers
});
this.sdk = getSdk(this.client);
}
// Update headers (useful for authentication)
setHeaders(headers) {
this.client.setHeaders(headers);
}
// Add a single header
setHeader(key, value) {
this.client.setHeader(key, value);
}
// Execute a GraphQL query
async query(query, variables, requestHeaders) {
try {
if (this.config.debug) {
console.log("[GraphQL Query]", { query, variables });
}
const response = await this.client.request(query, variables, requestHeaders);
if (this.config.debug) {
console.log("[GraphQL Response]", response);
}
return response;
} catch (error) {
if (this.config.debug) {
console.error("[GraphQL Error]", error);
}
throw this.handleGraphQLError(error);
}
}
// Execute a GraphQL mutation
async mutate(mutation, variables, requestHeaders) {
return this.query(mutation, variables, requestHeaders);
}
// Execute raw request with full response
async rawRequest(query, variables, requestHeaders) {
try {
const response = await this.client.rawRequest(query, variables, requestHeaders);
return {
data: response.data,
errors: response.errors
};
} catch (error) {
throw this.handleGraphQLError(error);
}
}
// Typed methods for market operations using generated SDK
async getAllMarkets() {
try {
if (this.config.debug) {
console.log("[GraphQL] Fetching all markets");
}
const response = await this.sdk.GetAllMarkets();
const markets = this.transformMarketsResponse(response);
if (this.config.debug) {
console.log("[GraphQL] Retrieved markets:", markets.length);
}
return markets;
} catch (error) {
throw this.handleGraphQLError(error);
}
}
async getMarketById(id) {
try {
if (this.config.debug) {
console.log("[GraphQL] Fetching market by ID:", id);
}
const response = await this.sdk.GetMarketById({ id });
const markets = this.transformMarketsResponse(response);
const market = markets.length > 0 ? markets[0] : null;
if (this.config.debug) {
console.log("[GraphQL] Retrieved market:", market ? market.id : "not found");
}
return market;
} catch (error) {
throw this.handleGraphQLError(error);
}
}
async getMarketByTicker(ticker) {
try {
if (this.config.debug) {
console.log("[GraphQL] Fetching market by ticker:", ticker);
}
const response = await this.sdk.GetMarketByTicker({ ticker });
const markets = this.transformMarketsResponse(response);
const market = markets.length > 0 ? markets[0] : null;
if (this.config.debug) {
console.log("[GraphQL] Retrieved market by ticker:", market ? market.ticker : "not found");
}
return market;
} catch (error) {
throw this.handleGraphQLError(error);
}
}
async getMarketsByStatus(status) {
try {
if (this.config.debug) {
console.log("[GraphQL] Fetching markets by status:", status);
}
const response = await this.sdk.GetMarketsByStatus({ status });
const markets = this.transformMarketsResponse(response);
if (this.config.debug) {
console.log("[GraphQL] Retrieved markets by status:", markets.length);
}
return markets;
} catch (error) {
throw this.handleGraphQLError(error);
}
}
// Transform GraphQL response to Market array
transformMarketsResponse(response) {
if (!response.marketCollection?.edges) {
return [];
}
return response.marketCollection.edges.filter((edge) => edge?.node).map((edge) => {
const node = edge.node;
return {
id: node.id,
name: node.name,
short_name: node.short_name,
ticker: node.ticker,
price: node.price,
type: node.type,
created_at: node.created_at,
last_update_at: node.last_update_at,
trend_status: node.trend_status,
sentiment: node.sentiment,
sentiment_update_at: node.sentiment_update_at,
market_activity_score_1h: node.market_activity_score_1h,
market_activity_score_1d: node.market_activity_score_1d,
data: node.data,
status: node.status
};
});
}
// Handle GraphQL errors
handleGraphQLError(error) {
if (error.response?.errors) {
const graphqlErrors = error.response.errors;
const message = graphqlErrors.map((e) => e.message).join(", ");
return new MercNetError(message, "GRAPHQL_ERROR", {
errors: graphqlErrors,
query: error.request?.query,
variables: error.request?.variables
});
}
if (error.message) {
return new MercNetError(error.message, "GRAPHQL_NETWORK_ERROR", {
originalError: error
});
}
return new MercNetError("Unknown GraphQL error", "GRAPHQL_UNKNOWN_ERROR", {
originalError: error
});
}
};
var createGraphQLClient = (config) => {
return new MercNetGraphQLClient(config);
};
var COMMON_FRAGMENTS = {
// Market fragment
MARKET_FRAGMENT: `
fragment MarketFragment on market {
id
name
short_name
ticker
price
type
created_at
last_update_at
trend_status
sentiment
sentiment_update_at
market_activity_score_1h
market_activity_score_1d
data
status
}
`,
// Pagination info fragment
PAGINATION_INFO: `
fragment PaginationInfo on PageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
`,
// Error fragment
ERROR_INFO: `
fragment ErrorInfo on Error {
message
code
details
}
`
};
var QueryBuilder = class {
fields = [];
fragments = [];
variables = [];
// Add a field to the query
field(name, subFields) {
if (subFields) {
this.fields.push(`${name} { ${subFields.join(" ")} }`);
} else {
this.fields.push(name);
}
return this;
}
// Add a fragment to the query
fragment(name, fragment) {
this.fragments.push(fragment);
this.fields.push(`...${name}`);
return this;
}
// Add a variable to the query
variable(name, type) {
this.variables.push(`$${name}: ${type}`);
return this;
}
// Build the final query
build(operationType = "query", operationName) {
const variablesPart = this.variables.length > 0 ? `(${this.variables.join(", ")})` : "";
const operationPart = operationName ? `${operationType} ${operationName}${variablesPart}` : `${operationType}${variablesPart}`;
const query = `
${operationPart} {
${this.fields.join("\n")}
}
${this.fragments.join("\n")}
`;
return query.trim();
}
};
var createQueryBuilder = () => {
return new QueryBuilder();
};
var gql2 = (strings, ...values) => {
return strings.reduce((result, string, index) => {
return result + string + (values[index] || "");
}, "");
};
var getSchemaInfo = async (client) => {
const introspectionQuery = gql2`
query IntrospectionQuery {
__schema {
types {
name
kind
}
queryType {
fields {
name
}
}
mutationType {
fields {
name
}
}
subscriptionType {
fields {
name
}
}
}
}
`;
const response = await client.query(introspectionQuery);
const schema = response.__schema;
return {
types: schema.types.map((type) => type.name),
queries: schema.queryType?.fields?.map((field) => field.name) || [],
mutations: schema.mutationType?.fields?.map((field) => field.name) || [],
subscriptions: schema.subscriptionType?.fields?.map((field) => field.name) || []
};
};
var baseEntitySchema = z.object({
id: z.string().min(1, "ID is required"),
createdAt: z.date(),
updatedAt: z.date()
});
var paginationParamsSchema = z.object({
page: z.number().int().positive().optional().default(1),
limit: z.number().int().positive().max(100).optional().default(20),
offset: z.number().int().nonnegative().optional()
});
var apiResponseSchema = (dataSchema) => z.object({
data: dataSchema.optional(),
error: z.object({
code: z.string(),
message: z.string(),
details: z.record(z.unknown()).optional()
}).optional(),
success: z.boolean(),
message: z.string().optional()
});
var validators = {
email: z.string().email("Invalid email format"),
password: z.string().min(8, "Password must be at least 8 characters"),
url: z.string().url("Invalid URL format"),
phone: z.string().regex(/^\+?[1-9]\d{1,14}$/, "Invalid phone number format"),
uuid: z.string().uuid("Invalid UUID format"),
slug: z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "Invalid slug format"),
hex: z.string().regex(/^#?([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/, "Invalid hex color"),
// Date validators
dateString: z.string().datetime("Invalid date format"),
dateRange: z.object({
start: z.date(),
end: z.date()
}).refine((data) => data.end > data.start, {
message: "End date must be after start date",
path: ["end"]
}),
// Number validators
positiveNumber: z.number().positive("Must be a positive number"),
nonNegativeNumber: z.number().nonnegative("Must be a non-negative number"),
percentage: z.number().min(0).max(100, "Must be between 0 and 100"),
currency: z.number().multipleOf(0.01, "Invalid currency format"),
// String validators
nonEmptyString: z.string().min(1, "Cannot be empty"),
alphanumeric: z.string().regex(/^[a-zA-Z0-9]+$/, "Must contain only letters and numbers"),
noWhitespace: z.string().regex(/^\S+$/, "Cannot contain whitespace")
};
var environmentSchema = z.enum(["development", "production", "test", "staging"]);
var baseConfigSchema = z.object({
environment: environmentSchema,
apiUrl: validators.url,
graphqlUrl: validators.url.optional(),
debug: z.boolean().optional().default(false)
});
var ValidationError = class extends MercNetError {
constructor(message, issues) {
super(message, "VALIDATION_ERROR", { issues });
this.issues = issues;
this.name = "ValidationError";
}
};
var validate = (schema, data, options) => {
const result = schema.safeParse(data);
if (result.success) {
return { success: true, data: result.data };
}
const error = new ValidationError(
options?.errorMessage || "Validation failed",
result.error.issues
);
if (options?.throwOnError) {
throw error;
}
return { success: false, error };
};
var validateAndThrow = (schema, data, errorMessage) => {
const result = validate(schema, data, { throwOnError: true, errorMessage });
return result.data;
};
var createArraySchema = (itemSchema, options) => {
let schema = z.array(itemSchema);
if (options?.minLength !== void 0) {
schema = schema.min(options.minLength, `Must have at least ${options.minLength} items`);
}
if (options?.maxLength !== void 0) {
schema = schema.max(options.maxLength, `Must have at most ${options.maxLength} items`);
}
if (options?.unique) {
schema = schema.refine(
(items) => {
const set = new Set(items.map((item) => JSON.stringify(item)));
return set.size === items.length;
},
{ message: "Items must be unique" }
);
}
return schema;
};
var createOptionalWithDefault = (schema, defaultValue) => {
return schema.optional().default(defaultValue);
};
var transforms = {
stringToNumber: z.string().transform((val, ctx) => {
const parsed = parseFloat(val);
if (isNaN(parsed)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Not a number"
});
return z.NEVER;
}
return parsed;
}),
stringToBoolean: z.string().transform((val) => {
return val.toLowerCase() === "true" || val === "1";
}),
stringToDate: z.string().transform((val, ctx) => {
const date = new Date(val);
if (isNaN(date.getTime())) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Invalid date"
});
return z.NEVER;
}
return date;
}),
trimString: z.string().transform((val) => val.trim()),
toLowerCase: z.string().transform((val) => val.toLowerCase()),
toUpperCase: z.string().transform((val) => val.toUpperCase())
};
var createConditionalSchema = (baseSchema, conditions) => {
return baseSchema.superRefine((data, ctx) => {
for (const condition of conditions) {
if (condition.when(data)) {
const result = condition.then.safeParse(data);
if (!result.success) {
result.error.issues.forEach((issue) => {
ctx.addIssue({
...issue,
message: condition.message || issue.message
});
});
}
}
}
});
};
var customValidators = {
strongPassword: z.string().refine(
(password) => {
const hasUpperCase = /[A-Z]/.test(password);
const hasLowerCase = /[a-z]/.test(password);
const hasNumbers = /\d/.test(password);
const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password);
return hasUpperCase && hasLowerCase && hasNumbers && hasSpecialChar;
},
{
message: "Password must contain uppercase, lowercase, number and special character"
}
),
uniqueArray: (itemSchema, keyExtractor) => z.array(itemSchema).refine(
(items) => {
if (!keyExtractor) {
return new Set(items).size === items.length;
}
const keys = items.map(keyExtractor);
return new Set(keys).size === keys.length;
},
{ message: "Array items must be unique" }
),
futureDate: z.date().refine((date) => date > /* @__PURE__ */ new Date(), { message: "Date must be in the future" }),
pastDate: z.date().refine((date) => date < /* @__PURE__ */ new Date(), { message: "Date must be in the past" })
};
// src/index.ts
var VERSION = "0.1.0";
export { apiResponseSchema as ApiResponseSchema, baseConfigSchema as BaseConfigSchema, baseEntitySchema as BaseEntitySchema, COMMON_FRAGMENTS, environmentSchema as EnvironmentSchema, MercNetError, MercNetGraphQLClient, paginationParamsSchema as PaginationParamsSchema, QueryBuilder, VERSION, ValidationError, camelCase, capitalize, createArraySchema, createConditionalSchema, createError, createGraphQLClient, createOptionalWithDefault, createQueryBuilder, customValidators, formatDate, getEnv, getSchemaInfo, gql2 as gql, isArray, isBoolean, isDevelopment, isFunction, isNumber, isObject, isProduction, isString, isValidDate, pick, retry, sleep, transforms, unique, validate, validateAndThrow, validators };
//# sourceMappingURL=index.js.map
//# sourceMappingURL=index.js.map