@wise-old-man/utils 3.3.12 → 3.3.14

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.
@@ -1,11 +1,5 @@
1
1
  'use strict';
2
2
 
3
- var axios = require('axios');
4
- var prometheus = require('prom-client');
5
- var zod = require('zod');
6
- require('dotenv/config');
7
- var fetchable = require('@attio/fetchable');
8
-
9
3
  var config = {
10
4
  defaultUserAgent: `WiseOldMan JS Client v${process.env.npm_package_version}`,
11
5
  baseAPIUrl: 'https://api.wiseoldman.net/v2'
@@ -75,7 +69,7 @@ function handleError(status, path, data) {
75
69
  throw new BadRequestError(path, data.message, data.data);
76
70
  }
77
71
  if (status === 403) {
78
- throw new ForbiddenError(path, data.message);
72
+ throw new ForbiddenError(path, data.message, data.data);
79
73
  }
80
74
  if (status === 404) {
81
75
  throw new NotFoundError(path, data.message);
@@ -97,11 +91,12 @@ class BadRequestError extends Error {
97
91
  }
98
92
  }
99
93
  class ForbiddenError extends Error {
100
- constructor(resource, message) {
94
+ constructor(resource, message, data) {
101
95
  super(message);
102
96
  this.name = 'ForbiddenError';
103
97
  this.resource = resource;
104
98
  this.statusCode = 403;
99
+ this.data = data;
105
100
  }
106
101
  }
107
102
  class NotFoundError extends Error {
@@ -2067,226 +2062,6 @@ function getParentEfficiencyMetric(metric) {
2067
2062
  return null;
2068
2063
  }
2069
2064
 
2070
- /**
2071
- * This file has been created as a way to force any usage
2072
- * of process.env to go through a dotenv.config first.
2073
- */
2074
- /**
2075
- * This ensures that an env var is required in prod but optional in dev/test.
2076
- */
2077
- function prodOnly(varSchema) {
2078
- if (process.env.NODE_ENV === 'production') {
2079
- return varSchema;
2080
- }
2081
- return zod.z.optional(varSchema);
2082
- }
2083
- const envVariablesSchema = zod.z.object({
2084
- // Prisma Database URL
2085
- CORE_DATABASE_URL: zod.z.string().trim().min(1),
2086
- // Redis Configs
2087
- REDIS_HOST: zod.z.string().trim().min(1),
2088
- REDIS_PORT: zod.z.coerce.number().positive().int(),
2089
- // Node Environment
2090
- NODE_ENV: zod.z.enum(['development', 'production', 'test']),
2091
- // Port for the API to run on
2092
- API_PORT: zod.z.optional(zod.z.coerce.number().positive().int()),
2093
- // Admin Password (For mod+ operations)
2094
- ADMIN_PASSWORD: prodOnly(zod.z.string().trim().min(1)),
2095
- // Sentry (for error tracking)
2096
- API_SENTRY_DSN: prodOnly(zod.z.string().trim().min(1)),
2097
- // Patreon Token (to access their API)
2098
- PATREON_BEARER_TOKEN: prodOnly(zod.z.string().trim().min(1)),
2099
- // Discord Bot API URL (to send events to)
2100
- DISCORD_BOT_API_URL: prodOnly(zod.z.string().trim().min(1).url()),
2101
- // Our Prometheus metrics aggregator service URL
2102
- PROMETHEUS_METRICS_SERVICE_URL: prodOnly(zod.z.string().trim().min(1).url()),
2103
- // Discord Monitoring Webhooks
2104
- DISCORD_PATREON_WEBHOOK_URL: prodOnly(zod.z.string().trim().min(1).url()),
2105
- DISCORD_MONITORING_WEBHOOK_URL: prodOnly(zod.z.string().trim().min(1).url()),
2106
- // Proxy Configs
2107
- PROXY_LIST: prodOnly(zod.z.string().trim().min(1)),
2108
- PROXY_USER: prodOnly(zod.z.string().trim().min(1)),
2109
- PROXY_PASSWORD: prodOnly(zod.z.string().trim().min(1)),
2110
- PROXY_PORT: prodOnly(zod.z.coerce.number().positive().int()),
2111
- CPU_COUNT: prodOnly(zod.z.coerce.number().positive().int()),
2112
- // Openai API Key
2113
- OPENAI_API_KEY: prodOnly(zod.z.string().trim().min(1).startsWith('sk-'))
2114
- });
2115
- // This will load env vars from a .env file, type check them,and throw an error
2116
- // (interrupting the process) if they're required and missing, or of an invalid type.
2117
- try {
2118
- envVariablesSchema.parse(process.env);
2119
- }
2120
- catch (error) {
2121
- const errorPayload = JSON.stringify(error, null, 2);
2122
- throw new Error(`Invalid environment variables. Please check env.ts for more info.\n${errorPayload}`);
2123
- }
2124
- function getThreadIndex() {
2125
- if (process.env.pm_id === undefined) {
2126
- return null;
2127
- }
2128
- return parseInt(process.env.pm_id, 10);
2129
- }
2130
-
2131
- class PrometheusService {
2132
- constructor() {
2133
- this.pushInterval = null;
2134
- this.registry = new prometheus.Registry();
2135
- this.registry.setDefaultLabels({ app: 'wise-old-man', threadIndex: getThreadIndex() });
2136
- prometheus.collectDefaultMetrics({ register: this.registry });
2137
- this.effectHistogram = new prometheus.Histogram({
2138
- name: 'effect_duration_seconds',
2139
- help: 'Duration of effects in microseconds',
2140
- labelNames: ['effectName', 'status'],
2141
- buckets: [0.1, 0.3, 0.5, 0.7, 1, 3, 5, 7, 10, 30]
2142
- });
2143
- this.httpHistogram = new prometheus.Histogram({
2144
- name: 'http_request_duration_seconds',
2145
- help: 'Duration of HTTP requests in microseconds',
2146
- labelNames: ['method', 'route', 'status', 'userAgent'],
2147
- buckets: [0.1, 0.3, 0.5, 0.7, 1, 3, 5, 7, 10, 30]
2148
- });
2149
- this.jobHistogram = new prometheus.Histogram({
2150
- name: 'job_duration_seconds',
2151
- help: 'Duration of jobs in microseconds',
2152
- labelNames: ['jobName', 'status'],
2153
- buckets: [0.1, 0.5, 1, 5, 10, 30, 60]
2154
- });
2155
- this.jobQueueGauge = new prometheus.Gauge({
2156
- name: 'job_queue_size',
2157
- help: 'Number of jobs in different states for each queue',
2158
- labelNames: ['queueName', 'state']
2159
- });
2160
- this.eventCounter = new prometheus.Counter({
2161
- name: 'event_counter',
2162
- help: 'Count of events emitted',
2163
- labelNames: ['eventType']
2164
- });
2165
- this.customPeriodCounter = new prometheus.Counter({
2166
- name: 'custom_period_counter',
2167
- help: 'Count of custom period expressions used',
2168
- labelNames: ['customPeriod']
2169
- });
2170
- this.updatePlayerJobSourceCounter = new prometheus.Counter({
2171
- name: 'update_player_job_source_counter',
2172
- help: 'Count of update player jobs dispatched',
2173
- labelNames: ['source']
2174
- });
2175
- this.runeMetricsHistogram = new prometheus.Histogram({
2176
- name: 'runemetrics_duration_seconds',
2177
- help: 'Duration of RuneMetrics requests in microseconds',
2178
- labelNames: ['status'],
2179
- buckets: [0.1, 0.3, 0.5, 1, 5, 10, 30]
2180
- });
2181
- this.hiscoresHistogram = new prometheus.Histogram({
2182
- name: 'hiscores_duration_seconds',
2183
- help: 'Duration of hiscores requests in microseconds',
2184
- labelNames: ['status'],
2185
- buckets: [0.1, 0.3, 0.5, 1, 5, 10, 30]
2186
- });
2187
- this.registry.registerMetric(this.jobHistogram);
2188
- this.registry.registerMetric(this.jobQueueGauge);
2189
- this.registry.registerMetric(this.httpHistogram);
2190
- this.registry.registerMetric(this.effectHistogram);
2191
- this.registry.registerMetric(this.eventCounter);
2192
- this.registry.registerMetric(this.customPeriodCounter);
2193
- this.registry.registerMetric(this.updatePlayerJobSourceCounter);
2194
- this.registry.registerMetric(this.runeMetricsHistogram);
2195
- this.registry.registerMetric(this.hiscoresHistogram);
2196
- }
2197
- init() {
2198
- this.pushInterval = setInterval(() => {
2199
- this.pushMetrics();
2200
- }, 60000);
2201
- }
2202
- shutdown() {
2203
- if (this.pushInterval !== null) {
2204
- clearInterval(this.pushInterval);
2205
- }
2206
- }
2207
- pushMetrics() {
2208
- return __awaiter(this, void 0, void 0, function* () {
2209
- if (process.env.NODE_ENV === 'test') {
2210
- return fetchable.errored({ code: 'NOT_ALLOWED_IN_TEST_ENV' });
2211
- }
2212
- if (!process.env.PROMETHEUS_METRICS_SERVICE_URL) {
2213
- return fetchable.errored({ code: 'MISSING_METRICS_URL' });
2214
- }
2215
- const metricsResult = yield fetchable.fromPromise(this.registry.getMetricsAsJSON());
2216
- if (fetchable.isErrored(metricsResult)) {
2217
- return fetchable.errored({
2218
- code: 'FAILED_TO_GET_PROMETHEUS_METRICS',
2219
- subError: metricsResult.error
2220
- });
2221
- }
2222
- const requestResult = yield fetchable.fromPromise(axios.post(process.env.PROMETHEUS_METRICS_SERVICE_URL, {
2223
- source: 'api',
2224
- data: metricsResult.value,
2225
- threadIndex: getThreadIndex()
2226
- }));
2227
- if (fetchable.isErrored(requestResult)) {
2228
- return fetchable.errored({
2229
- code: 'FAILED_TO_PUSH_PROMETHEUS_METRICS',
2230
- subError: requestResult.error
2231
- });
2232
- }
2233
- return fetchable.complete(true);
2234
- });
2235
- }
2236
- trackHttpRequest() {
2237
- return this.httpHistogram.startTimer();
2238
- }
2239
- trackRuneMetricsRequest() {
2240
- return this.runeMetricsHistogram.startTimer();
2241
- }
2242
- trackHiscoresRequest() {
2243
- return this.hiscoresHistogram.startTimer();
2244
- }
2245
- trackEffect(effectName, fn) {
2246
- return __awaiter(this, void 0, void 0, function* () {
2247
- const endTimer = this.effectHistogram.startTimer();
2248
- try {
2249
- yield fn();
2250
- endTimer({ effectName, status: 1 });
2251
- }
2252
- catch (error) {
2253
- endTimer({ effectName, status: 0 });
2254
- throw error;
2255
- }
2256
- });
2257
- }
2258
- trackJob(jobName, handler) {
2259
- return __awaiter(this, void 0, void 0, function* () {
2260
- const endTimer = this.jobHistogram.startTimer();
2261
- try {
2262
- yield handler();
2263
- endTimer({ jobName, status: 1 });
2264
- }
2265
- catch (error) {
2266
- endTimer({ jobName, status: 0 });
2267
- throw error;
2268
- }
2269
- });
2270
- }
2271
- trackEventEmitted(eventType) {
2272
- this.eventCounter.inc({ eventType });
2273
- }
2274
- trackCustomPeriodExpression(customPeriod) {
2275
- this.customPeriodCounter.inc({ customPeriod });
2276
- }
2277
- trackUpdatePlayerJobSource(source) {
2278
- this.updatePlayerJobSourceCounter.inc({ source });
2279
- }
2280
- updateQueueMetrics(queueName, counts) {
2281
- return __awaiter(this, void 0, void 0, function* () {
2282
- for (const [state, count] of Object.entries(counts)) {
2283
- this.jobQueueGauge.set({ queueName, state }, count);
2284
- }
2285
- });
2286
- }
2287
- }
2288
- var prometheusService = new PrometheusService();
2289
-
2290
2065
  const CUSTOM_PERIOD_REGEX = /(\d+y)?(\d+m)?(\d+w)?(\d+d)?(\d+h)?/;
2291
2066
  const PeriodProps = {
2292
2067
  [Period.FIVE_MIN]: { name: '5 Min', milliseconds: 300000 },
@@ -2314,7 +2089,6 @@ function parsePeriodExpression(periodExpression) {
2314
2089
  durationMs: PeriodProps[fixed].milliseconds
2315
2090
  };
2316
2091
  }
2317
- prometheusService.trackCustomPeriodExpression(fixed);
2318
2092
  const result = fixed.match(CUSTOM_PERIOD_REGEX);
2319
2093
  if (!result || result.length === 0 || result[0] !== fixed)
2320
2094
  return null;
package/dist/es/index.js CHANGED
@@ -1,9 +1,3 @@
1
- import axios from 'axios';
2
- import prometheus from 'prom-client';
3
- import { z } from 'zod';
4
- import 'dotenv/config';
5
- import { errored, fromPromise, isErrored, complete } from '@attio/fetchable';
6
-
7
1
  var config = {
8
2
  defaultUserAgent: `WiseOldMan JS Client v${process.env.npm_package_version}`,
9
3
  baseAPIUrl: 'https://api.wiseoldman.net/v2'
@@ -73,7 +67,7 @@ function handleError(status, path, data) {
73
67
  throw new BadRequestError(path, data.message, data.data);
74
68
  }
75
69
  if (status === 403) {
76
- throw new ForbiddenError(path, data.message);
70
+ throw new ForbiddenError(path, data.message, data.data);
77
71
  }
78
72
  if (status === 404) {
79
73
  throw new NotFoundError(path, data.message);
@@ -95,11 +89,12 @@ class BadRequestError extends Error {
95
89
  }
96
90
  }
97
91
  class ForbiddenError extends Error {
98
- constructor(resource, message) {
92
+ constructor(resource, message, data) {
99
93
  super(message);
100
94
  this.name = 'ForbiddenError';
101
95
  this.resource = resource;
102
96
  this.statusCode = 403;
97
+ this.data = data;
103
98
  }
104
99
  }
105
100
  class NotFoundError extends Error {
@@ -2065,226 +2060,6 @@ function getParentEfficiencyMetric(metric) {
2065
2060
  return null;
2066
2061
  }
2067
2062
 
2068
- /**
2069
- * This file has been created as a way to force any usage
2070
- * of process.env to go through a dotenv.config first.
2071
- */
2072
- /**
2073
- * This ensures that an env var is required in prod but optional in dev/test.
2074
- */
2075
- function prodOnly(varSchema) {
2076
- if (process.env.NODE_ENV === 'production') {
2077
- return varSchema;
2078
- }
2079
- return z.optional(varSchema);
2080
- }
2081
- const envVariablesSchema = z.object({
2082
- // Prisma Database URL
2083
- CORE_DATABASE_URL: z.string().trim().min(1),
2084
- // Redis Configs
2085
- REDIS_HOST: z.string().trim().min(1),
2086
- REDIS_PORT: z.coerce.number().positive().int(),
2087
- // Node Environment
2088
- NODE_ENV: z.enum(['development', 'production', 'test']),
2089
- // Port for the API to run on
2090
- API_PORT: z.optional(z.coerce.number().positive().int()),
2091
- // Admin Password (For mod+ operations)
2092
- ADMIN_PASSWORD: prodOnly(z.string().trim().min(1)),
2093
- // Sentry (for error tracking)
2094
- API_SENTRY_DSN: prodOnly(z.string().trim().min(1)),
2095
- // Patreon Token (to access their API)
2096
- PATREON_BEARER_TOKEN: prodOnly(z.string().trim().min(1)),
2097
- // Discord Bot API URL (to send events to)
2098
- DISCORD_BOT_API_URL: prodOnly(z.string().trim().min(1).url()),
2099
- // Our Prometheus metrics aggregator service URL
2100
- PROMETHEUS_METRICS_SERVICE_URL: prodOnly(z.string().trim().min(1).url()),
2101
- // Discord Monitoring Webhooks
2102
- DISCORD_PATREON_WEBHOOK_URL: prodOnly(z.string().trim().min(1).url()),
2103
- DISCORD_MONITORING_WEBHOOK_URL: prodOnly(z.string().trim().min(1).url()),
2104
- // Proxy Configs
2105
- PROXY_LIST: prodOnly(z.string().trim().min(1)),
2106
- PROXY_USER: prodOnly(z.string().trim().min(1)),
2107
- PROXY_PASSWORD: prodOnly(z.string().trim().min(1)),
2108
- PROXY_PORT: prodOnly(z.coerce.number().positive().int()),
2109
- CPU_COUNT: prodOnly(z.coerce.number().positive().int()),
2110
- // Openai API Key
2111
- OPENAI_API_KEY: prodOnly(z.string().trim().min(1).startsWith('sk-'))
2112
- });
2113
- // This will load env vars from a .env file, type check them,and throw an error
2114
- // (interrupting the process) if they're required and missing, or of an invalid type.
2115
- try {
2116
- envVariablesSchema.parse(process.env);
2117
- }
2118
- catch (error) {
2119
- const errorPayload = JSON.stringify(error, null, 2);
2120
- throw new Error(`Invalid environment variables. Please check env.ts for more info.\n${errorPayload}`);
2121
- }
2122
- function getThreadIndex() {
2123
- if (process.env.pm_id === undefined) {
2124
- return null;
2125
- }
2126
- return parseInt(process.env.pm_id, 10);
2127
- }
2128
-
2129
- class PrometheusService {
2130
- constructor() {
2131
- this.pushInterval = null;
2132
- this.registry = new prometheus.Registry();
2133
- this.registry.setDefaultLabels({ app: 'wise-old-man', threadIndex: getThreadIndex() });
2134
- prometheus.collectDefaultMetrics({ register: this.registry });
2135
- this.effectHistogram = new prometheus.Histogram({
2136
- name: 'effect_duration_seconds',
2137
- help: 'Duration of effects in microseconds',
2138
- labelNames: ['effectName', 'status'],
2139
- buckets: [0.1, 0.3, 0.5, 0.7, 1, 3, 5, 7, 10, 30]
2140
- });
2141
- this.httpHistogram = new prometheus.Histogram({
2142
- name: 'http_request_duration_seconds',
2143
- help: 'Duration of HTTP requests in microseconds',
2144
- labelNames: ['method', 'route', 'status', 'userAgent'],
2145
- buckets: [0.1, 0.3, 0.5, 0.7, 1, 3, 5, 7, 10, 30]
2146
- });
2147
- this.jobHistogram = new prometheus.Histogram({
2148
- name: 'job_duration_seconds',
2149
- help: 'Duration of jobs in microseconds',
2150
- labelNames: ['jobName', 'status'],
2151
- buckets: [0.1, 0.5, 1, 5, 10, 30, 60]
2152
- });
2153
- this.jobQueueGauge = new prometheus.Gauge({
2154
- name: 'job_queue_size',
2155
- help: 'Number of jobs in different states for each queue',
2156
- labelNames: ['queueName', 'state']
2157
- });
2158
- this.eventCounter = new prometheus.Counter({
2159
- name: 'event_counter',
2160
- help: 'Count of events emitted',
2161
- labelNames: ['eventType']
2162
- });
2163
- this.customPeriodCounter = new prometheus.Counter({
2164
- name: 'custom_period_counter',
2165
- help: 'Count of custom period expressions used',
2166
- labelNames: ['customPeriod']
2167
- });
2168
- this.updatePlayerJobSourceCounter = new prometheus.Counter({
2169
- name: 'update_player_job_source_counter',
2170
- help: 'Count of update player jobs dispatched',
2171
- labelNames: ['source']
2172
- });
2173
- this.runeMetricsHistogram = new prometheus.Histogram({
2174
- name: 'runemetrics_duration_seconds',
2175
- help: 'Duration of RuneMetrics requests in microseconds',
2176
- labelNames: ['status'],
2177
- buckets: [0.1, 0.3, 0.5, 1, 5, 10, 30]
2178
- });
2179
- this.hiscoresHistogram = new prometheus.Histogram({
2180
- name: 'hiscores_duration_seconds',
2181
- help: 'Duration of hiscores requests in microseconds',
2182
- labelNames: ['status'],
2183
- buckets: [0.1, 0.3, 0.5, 1, 5, 10, 30]
2184
- });
2185
- this.registry.registerMetric(this.jobHistogram);
2186
- this.registry.registerMetric(this.jobQueueGauge);
2187
- this.registry.registerMetric(this.httpHistogram);
2188
- this.registry.registerMetric(this.effectHistogram);
2189
- this.registry.registerMetric(this.eventCounter);
2190
- this.registry.registerMetric(this.customPeriodCounter);
2191
- this.registry.registerMetric(this.updatePlayerJobSourceCounter);
2192
- this.registry.registerMetric(this.runeMetricsHistogram);
2193
- this.registry.registerMetric(this.hiscoresHistogram);
2194
- }
2195
- init() {
2196
- this.pushInterval = setInterval(() => {
2197
- this.pushMetrics();
2198
- }, 60000);
2199
- }
2200
- shutdown() {
2201
- if (this.pushInterval !== null) {
2202
- clearInterval(this.pushInterval);
2203
- }
2204
- }
2205
- pushMetrics() {
2206
- return __awaiter(this, void 0, void 0, function* () {
2207
- if (process.env.NODE_ENV === 'test') {
2208
- return errored({ code: 'NOT_ALLOWED_IN_TEST_ENV' });
2209
- }
2210
- if (!process.env.PROMETHEUS_METRICS_SERVICE_URL) {
2211
- return errored({ code: 'MISSING_METRICS_URL' });
2212
- }
2213
- const metricsResult = yield fromPromise(this.registry.getMetricsAsJSON());
2214
- if (isErrored(metricsResult)) {
2215
- return errored({
2216
- code: 'FAILED_TO_GET_PROMETHEUS_METRICS',
2217
- subError: metricsResult.error
2218
- });
2219
- }
2220
- const requestResult = yield fromPromise(axios.post(process.env.PROMETHEUS_METRICS_SERVICE_URL, {
2221
- source: 'api',
2222
- data: metricsResult.value,
2223
- threadIndex: getThreadIndex()
2224
- }));
2225
- if (isErrored(requestResult)) {
2226
- return errored({
2227
- code: 'FAILED_TO_PUSH_PROMETHEUS_METRICS',
2228
- subError: requestResult.error
2229
- });
2230
- }
2231
- return complete(true);
2232
- });
2233
- }
2234
- trackHttpRequest() {
2235
- return this.httpHistogram.startTimer();
2236
- }
2237
- trackRuneMetricsRequest() {
2238
- return this.runeMetricsHistogram.startTimer();
2239
- }
2240
- trackHiscoresRequest() {
2241
- return this.hiscoresHistogram.startTimer();
2242
- }
2243
- trackEffect(effectName, fn) {
2244
- return __awaiter(this, void 0, void 0, function* () {
2245
- const endTimer = this.effectHistogram.startTimer();
2246
- try {
2247
- yield fn();
2248
- endTimer({ effectName, status: 1 });
2249
- }
2250
- catch (error) {
2251
- endTimer({ effectName, status: 0 });
2252
- throw error;
2253
- }
2254
- });
2255
- }
2256
- trackJob(jobName, handler) {
2257
- return __awaiter(this, void 0, void 0, function* () {
2258
- const endTimer = this.jobHistogram.startTimer();
2259
- try {
2260
- yield handler();
2261
- endTimer({ jobName, status: 1 });
2262
- }
2263
- catch (error) {
2264
- endTimer({ jobName, status: 0 });
2265
- throw error;
2266
- }
2267
- });
2268
- }
2269
- trackEventEmitted(eventType) {
2270
- this.eventCounter.inc({ eventType });
2271
- }
2272
- trackCustomPeriodExpression(customPeriod) {
2273
- this.customPeriodCounter.inc({ customPeriod });
2274
- }
2275
- trackUpdatePlayerJobSource(source) {
2276
- this.updatePlayerJobSourceCounter.inc({ source });
2277
- }
2278
- updateQueueMetrics(queueName, counts) {
2279
- return __awaiter(this, void 0, void 0, function* () {
2280
- for (const [state, count] of Object.entries(counts)) {
2281
- this.jobQueueGauge.set({ queueName, state }, count);
2282
- }
2283
- });
2284
- }
2285
- }
2286
- var prometheusService = new PrometheusService();
2287
-
2288
2063
  const CUSTOM_PERIOD_REGEX = /(\d+y)?(\d+m)?(\d+w)?(\d+d)?(\d+h)?/;
2289
2064
  const PeriodProps = {
2290
2065
  [Period.FIVE_MIN]: { name: '5 Min', milliseconds: 300000 },
@@ -2312,7 +2087,6 @@ function parsePeriodExpression(periodExpression) {
2312
2087
  durationMs: PeriodProps[fixed].milliseconds
2313
2088
  };
2314
2089
  }
2315
- prometheusService.trackCustomPeriodExpression(fixed);
2316
2090
  const result = fixed.match(CUSTOM_PERIOD_REGEX);
2317
2091
  if (!result || result.length === 0 || result[0] !== fixed)
2318
2092
  return null;
package/dist/es/index.mjs CHANGED
@@ -1,9 +1,3 @@
1
- import axios from 'axios';
2
- import prometheus from 'prom-client';
3
- import { z } from 'zod';
4
- import 'dotenv/config';
5
- import { errored, fromPromise, isErrored, complete } from '@attio/fetchable';
6
-
7
1
  var config = {
8
2
  defaultUserAgent: `WiseOldMan JS Client v${process.env.npm_package_version}`,
9
3
  baseAPIUrl: 'https://api.wiseoldman.net/v2'
@@ -73,7 +67,7 @@ function handleError(status, path, data) {
73
67
  throw new BadRequestError(path, data.message, data.data);
74
68
  }
75
69
  if (status === 403) {
76
- throw new ForbiddenError(path, data.message);
70
+ throw new ForbiddenError(path, data.message, data.data);
77
71
  }
78
72
  if (status === 404) {
79
73
  throw new NotFoundError(path, data.message);
@@ -95,11 +89,12 @@ class BadRequestError extends Error {
95
89
  }
96
90
  }
97
91
  class ForbiddenError extends Error {
98
- constructor(resource, message) {
92
+ constructor(resource, message, data) {
99
93
  super(message);
100
94
  this.name = 'ForbiddenError';
101
95
  this.resource = resource;
102
96
  this.statusCode = 403;
97
+ this.data = data;
103
98
  }
104
99
  }
105
100
  class NotFoundError extends Error {
@@ -2065,226 +2060,6 @@ function getParentEfficiencyMetric(metric) {
2065
2060
  return null;
2066
2061
  }
2067
2062
 
2068
- /**
2069
- * This file has been created as a way to force any usage
2070
- * of process.env to go through a dotenv.config first.
2071
- */
2072
- /**
2073
- * This ensures that an env var is required in prod but optional in dev/test.
2074
- */
2075
- function prodOnly(varSchema) {
2076
- if (process.env.NODE_ENV === 'production') {
2077
- return varSchema;
2078
- }
2079
- return z.optional(varSchema);
2080
- }
2081
- const envVariablesSchema = z.object({
2082
- // Prisma Database URL
2083
- CORE_DATABASE_URL: z.string().trim().min(1),
2084
- // Redis Configs
2085
- REDIS_HOST: z.string().trim().min(1),
2086
- REDIS_PORT: z.coerce.number().positive().int(),
2087
- // Node Environment
2088
- NODE_ENV: z.enum(['development', 'production', 'test']),
2089
- // Port for the API to run on
2090
- API_PORT: z.optional(z.coerce.number().positive().int()),
2091
- // Admin Password (For mod+ operations)
2092
- ADMIN_PASSWORD: prodOnly(z.string().trim().min(1)),
2093
- // Sentry (for error tracking)
2094
- API_SENTRY_DSN: prodOnly(z.string().trim().min(1)),
2095
- // Patreon Token (to access their API)
2096
- PATREON_BEARER_TOKEN: prodOnly(z.string().trim().min(1)),
2097
- // Discord Bot API URL (to send events to)
2098
- DISCORD_BOT_API_URL: prodOnly(z.string().trim().min(1).url()),
2099
- // Our Prometheus metrics aggregator service URL
2100
- PROMETHEUS_METRICS_SERVICE_URL: prodOnly(z.string().trim().min(1).url()),
2101
- // Discord Monitoring Webhooks
2102
- DISCORD_PATREON_WEBHOOK_URL: prodOnly(z.string().trim().min(1).url()),
2103
- DISCORD_MONITORING_WEBHOOK_URL: prodOnly(z.string().trim().min(1).url()),
2104
- // Proxy Configs
2105
- PROXY_LIST: prodOnly(z.string().trim().min(1)),
2106
- PROXY_USER: prodOnly(z.string().trim().min(1)),
2107
- PROXY_PASSWORD: prodOnly(z.string().trim().min(1)),
2108
- PROXY_PORT: prodOnly(z.coerce.number().positive().int()),
2109
- CPU_COUNT: prodOnly(z.coerce.number().positive().int()),
2110
- // Openai API Key
2111
- OPENAI_API_KEY: prodOnly(z.string().trim().min(1).startsWith('sk-'))
2112
- });
2113
- // This will load env vars from a .env file, type check them,and throw an error
2114
- // (interrupting the process) if they're required and missing, or of an invalid type.
2115
- try {
2116
- envVariablesSchema.parse(process.env);
2117
- }
2118
- catch (error) {
2119
- const errorPayload = JSON.stringify(error, null, 2);
2120
- throw new Error(`Invalid environment variables. Please check env.ts for more info.\n${errorPayload}`);
2121
- }
2122
- function getThreadIndex() {
2123
- if (process.env.pm_id === undefined) {
2124
- return null;
2125
- }
2126
- return parseInt(process.env.pm_id, 10);
2127
- }
2128
-
2129
- class PrometheusService {
2130
- constructor() {
2131
- this.pushInterval = null;
2132
- this.registry = new prometheus.Registry();
2133
- this.registry.setDefaultLabels({ app: 'wise-old-man', threadIndex: getThreadIndex() });
2134
- prometheus.collectDefaultMetrics({ register: this.registry });
2135
- this.effectHistogram = new prometheus.Histogram({
2136
- name: 'effect_duration_seconds',
2137
- help: 'Duration of effects in microseconds',
2138
- labelNames: ['effectName', 'status'],
2139
- buckets: [0.1, 0.3, 0.5, 0.7, 1, 3, 5, 7, 10, 30]
2140
- });
2141
- this.httpHistogram = new prometheus.Histogram({
2142
- name: 'http_request_duration_seconds',
2143
- help: 'Duration of HTTP requests in microseconds',
2144
- labelNames: ['method', 'route', 'status', 'userAgent'],
2145
- buckets: [0.1, 0.3, 0.5, 0.7, 1, 3, 5, 7, 10, 30]
2146
- });
2147
- this.jobHistogram = new prometheus.Histogram({
2148
- name: 'job_duration_seconds',
2149
- help: 'Duration of jobs in microseconds',
2150
- labelNames: ['jobName', 'status'],
2151
- buckets: [0.1, 0.5, 1, 5, 10, 30, 60]
2152
- });
2153
- this.jobQueueGauge = new prometheus.Gauge({
2154
- name: 'job_queue_size',
2155
- help: 'Number of jobs in different states for each queue',
2156
- labelNames: ['queueName', 'state']
2157
- });
2158
- this.eventCounter = new prometheus.Counter({
2159
- name: 'event_counter',
2160
- help: 'Count of events emitted',
2161
- labelNames: ['eventType']
2162
- });
2163
- this.customPeriodCounter = new prometheus.Counter({
2164
- name: 'custom_period_counter',
2165
- help: 'Count of custom period expressions used',
2166
- labelNames: ['customPeriod']
2167
- });
2168
- this.updatePlayerJobSourceCounter = new prometheus.Counter({
2169
- name: 'update_player_job_source_counter',
2170
- help: 'Count of update player jobs dispatched',
2171
- labelNames: ['source']
2172
- });
2173
- this.runeMetricsHistogram = new prometheus.Histogram({
2174
- name: 'runemetrics_duration_seconds',
2175
- help: 'Duration of RuneMetrics requests in microseconds',
2176
- labelNames: ['status'],
2177
- buckets: [0.1, 0.3, 0.5, 1, 5, 10, 30]
2178
- });
2179
- this.hiscoresHistogram = new prometheus.Histogram({
2180
- name: 'hiscores_duration_seconds',
2181
- help: 'Duration of hiscores requests in microseconds',
2182
- labelNames: ['status'],
2183
- buckets: [0.1, 0.3, 0.5, 1, 5, 10, 30]
2184
- });
2185
- this.registry.registerMetric(this.jobHistogram);
2186
- this.registry.registerMetric(this.jobQueueGauge);
2187
- this.registry.registerMetric(this.httpHistogram);
2188
- this.registry.registerMetric(this.effectHistogram);
2189
- this.registry.registerMetric(this.eventCounter);
2190
- this.registry.registerMetric(this.customPeriodCounter);
2191
- this.registry.registerMetric(this.updatePlayerJobSourceCounter);
2192
- this.registry.registerMetric(this.runeMetricsHistogram);
2193
- this.registry.registerMetric(this.hiscoresHistogram);
2194
- }
2195
- init() {
2196
- this.pushInterval = setInterval(() => {
2197
- this.pushMetrics();
2198
- }, 60000);
2199
- }
2200
- shutdown() {
2201
- if (this.pushInterval !== null) {
2202
- clearInterval(this.pushInterval);
2203
- }
2204
- }
2205
- pushMetrics() {
2206
- return __awaiter(this, void 0, void 0, function* () {
2207
- if (process.env.NODE_ENV === 'test') {
2208
- return errored({ code: 'NOT_ALLOWED_IN_TEST_ENV' });
2209
- }
2210
- if (!process.env.PROMETHEUS_METRICS_SERVICE_URL) {
2211
- return errored({ code: 'MISSING_METRICS_URL' });
2212
- }
2213
- const metricsResult = yield fromPromise(this.registry.getMetricsAsJSON());
2214
- if (isErrored(metricsResult)) {
2215
- return errored({
2216
- code: 'FAILED_TO_GET_PROMETHEUS_METRICS',
2217
- subError: metricsResult.error
2218
- });
2219
- }
2220
- const requestResult = yield fromPromise(axios.post(process.env.PROMETHEUS_METRICS_SERVICE_URL, {
2221
- source: 'api',
2222
- data: metricsResult.value,
2223
- threadIndex: getThreadIndex()
2224
- }));
2225
- if (isErrored(requestResult)) {
2226
- return errored({
2227
- code: 'FAILED_TO_PUSH_PROMETHEUS_METRICS',
2228
- subError: requestResult.error
2229
- });
2230
- }
2231
- return complete(true);
2232
- });
2233
- }
2234
- trackHttpRequest() {
2235
- return this.httpHistogram.startTimer();
2236
- }
2237
- trackRuneMetricsRequest() {
2238
- return this.runeMetricsHistogram.startTimer();
2239
- }
2240
- trackHiscoresRequest() {
2241
- return this.hiscoresHistogram.startTimer();
2242
- }
2243
- trackEffect(effectName, fn) {
2244
- return __awaiter(this, void 0, void 0, function* () {
2245
- const endTimer = this.effectHistogram.startTimer();
2246
- try {
2247
- yield fn();
2248
- endTimer({ effectName, status: 1 });
2249
- }
2250
- catch (error) {
2251
- endTimer({ effectName, status: 0 });
2252
- throw error;
2253
- }
2254
- });
2255
- }
2256
- trackJob(jobName, handler) {
2257
- return __awaiter(this, void 0, void 0, function* () {
2258
- const endTimer = this.jobHistogram.startTimer();
2259
- try {
2260
- yield handler();
2261
- endTimer({ jobName, status: 1 });
2262
- }
2263
- catch (error) {
2264
- endTimer({ jobName, status: 0 });
2265
- throw error;
2266
- }
2267
- });
2268
- }
2269
- trackEventEmitted(eventType) {
2270
- this.eventCounter.inc({ eventType });
2271
- }
2272
- trackCustomPeriodExpression(customPeriod) {
2273
- this.customPeriodCounter.inc({ customPeriod });
2274
- }
2275
- trackUpdatePlayerJobSource(source) {
2276
- this.updatePlayerJobSourceCounter.inc({ source });
2277
- }
2278
- updateQueueMetrics(queueName, counts) {
2279
- return __awaiter(this, void 0, void 0, function* () {
2280
- for (const [state, count] of Object.entries(counts)) {
2281
- this.jobQueueGauge.set({ queueName, state }, count);
2282
- }
2283
- });
2284
- }
2285
- }
2286
- var prometheusService = new PrometheusService();
2287
-
2288
2063
  const CUSTOM_PERIOD_REGEX = /(\d+y)?(\d+m)?(\d+w)?(\d+d)?(\d+h)?/;
2289
2064
  const PeriodProps = {
2290
2065
  [Period.FIVE_MIN]: { name: '5 Min', milliseconds: 300000 },
@@ -2312,7 +2087,6 @@ function parsePeriodExpression(periodExpression) {
2312
2087
  durationMs: PeriodProps[fixed].milliseconds
2313
2088
  };
2314
2089
  }
2315
- prometheusService.trackCustomPeriodExpression(fixed);
2316
2090
  const result = fixed.match(CUSTOM_PERIOD_REGEX);
2317
2091
  if (!result || result.length === 0 || result[0] !== fixed)
2318
2092
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wise-old-man/utils",
3
- "version": "3.3.12",
3
+ "version": "3.3.14",
4
4
  "description": "A JavaScript/TypeScript client that interfaces and consumes the Wise Old Man API, an API that tracks and measures players' progress in Old School Runescape.",
5
5
  "keywords": [
6
6
  "wiseoldman",