@wise-old-man/utils 3.3.10 → 3.3.12

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,5 +1,11 @@
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
+
3
9
  var config = {
4
10
  defaultUserAgent: `WiseOldMan JS Client v${process.env.npm_package_version}`,
5
11
  baseAPIUrl: 'https://api.wiseoldman.net/v2'
@@ -614,6 +620,7 @@ const Boss = {
614
620
  VETION: 'vetion',
615
621
  VORKATH: 'vorkath',
616
622
  WINTERTODT: 'wintertodt',
623
+ YAMA: 'yama',
617
624
  ZALCANO: 'zalcano',
618
625
  ZULRAH: 'zulrah'
619
626
  };
@@ -643,6 +650,8 @@ const PlayerType = {
643
650
  };
644
651
  const PlayerAnnotationType = {
645
652
  OPT_OUT: 'opt_out',
653
+ OPT_OUT_GROUPS: 'opt_out_groups',
654
+ OPT_OUT_COMPETITIONS: 'opt_out_competitions',
646
655
  BLOCKED: 'blocked',
647
656
  FAKE_F2P: 'fake_f2p'
648
657
  };
@@ -1966,6 +1975,7 @@ const BossProps = mapValues({
1966
1975
  [Boss.VETION]: { name: "Vet'ion" },
1967
1976
  [Boss.VORKATH]: { name: 'Vorkath' },
1968
1977
  [Boss.WINTERTODT]: { name: 'Wintertodt' },
1978
+ [Boss.YAMA]: { name: 'Yama' },
1969
1979
  [Boss.ZALCANO]: { name: 'Zalcano' },
1970
1980
  [Boss.ZULRAH]: { name: 'Zulrah' }
1971
1981
  }, props => (Object.assign(Object.assign({}, props), { type: exports.MetricType.BOSS, measure: exports.MetricMeasure.KILLS, isMembers: 'isMembers' in props ? props.isMembers : true, minimumValue: 'minimumValue' in props ? props.minimumValue : 5 })));
@@ -2057,6 +2067,226 @@ function getParentEfficiencyMetric(metric) {
2057
2067
  return null;
2058
2068
  }
2059
2069
 
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
+
2060
2290
  const CUSTOM_PERIOD_REGEX = /(\d+y)?(\d+m)?(\d+w)?(\d+d)?(\d+h)?/;
2061
2291
  const PeriodProps = {
2062
2292
  [Period.FIVE_MIN]: { name: '5 Min', milliseconds: 300000 },
@@ -2084,6 +2314,7 @@ function parsePeriodExpression(periodExpression) {
2084
2314
  durationMs: PeriodProps[fixed].milliseconds
2085
2315
  };
2086
2316
  }
2317
+ prometheusService.trackCustomPeriodExpression(fixed);
2087
2318
  const result = fixed.match(CUSTOM_PERIOD_REGEX);
2088
2319
  if (!result || result.length === 0 || result[0] !== fixed)
2089
2320
  return null;
package/dist/es/index.js CHANGED
@@ -1,3 +1,9 @@
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
+
1
7
  var config = {
2
8
  defaultUserAgent: `WiseOldMan JS Client v${process.env.npm_package_version}`,
3
9
  baseAPIUrl: 'https://api.wiseoldman.net/v2'
@@ -612,6 +618,7 @@ const Boss = {
612
618
  VETION: 'vetion',
613
619
  VORKATH: 'vorkath',
614
620
  WINTERTODT: 'wintertodt',
621
+ YAMA: 'yama',
615
622
  ZALCANO: 'zalcano',
616
623
  ZULRAH: 'zulrah'
617
624
  };
@@ -641,6 +648,8 @@ const PlayerType = {
641
648
  };
642
649
  const PlayerAnnotationType = {
643
650
  OPT_OUT: 'opt_out',
651
+ OPT_OUT_GROUPS: 'opt_out_groups',
652
+ OPT_OUT_COMPETITIONS: 'opt_out_competitions',
644
653
  BLOCKED: 'blocked',
645
654
  FAKE_F2P: 'fake_f2p'
646
655
  };
@@ -1964,6 +1973,7 @@ const BossProps = mapValues({
1964
1973
  [Boss.VETION]: { name: "Vet'ion" },
1965
1974
  [Boss.VORKATH]: { name: 'Vorkath' },
1966
1975
  [Boss.WINTERTODT]: { name: 'Wintertodt' },
1976
+ [Boss.YAMA]: { name: 'Yama' },
1967
1977
  [Boss.ZALCANO]: { name: 'Zalcano' },
1968
1978
  [Boss.ZULRAH]: { name: 'Zulrah' }
1969
1979
  }, props => (Object.assign(Object.assign({}, props), { type: MetricType.BOSS, measure: MetricMeasure.KILLS, isMembers: 'isMembers' in props ? props.isMembers : true, minimumValue: 'minimumValue' in props ? props.minimumValue : 5 })));
@@ -2055,6 +2065,226 @@ function getParentEfficiencyMetric(metric) {
2055
2065
  return null;
2056
2066
  }
2057
2067
 
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
+
2058
2288
  const CUSTOM_PERIOD_REGEX = /(\d+y)?(\d+m)?(\d+w)?(\d+d)?(\d+h)?/;
2059
2289
  const PeriodProps = {
2060
2290
  [Period.FIVE_MIN]: { name: '5 Min', milliseconds: 300000 },
@@ -2082,6 +2312,7 @@ function parsePeriodExpression(periodExpression) {
2082
2312
  durationMs: PeriodProps[fixed].milliseconds
2083
2313
  };
2084
2314
  }
2315
+ prometheusService.trackCustomPeriodExpression(fixed);
2085
2316
  const result = fixed.match(CUSTOM_PERIOD_REGEX);
2086
2317
  if (!result || result.length === 0 || result[0] !== fixed)
2087
2318
  return null;
package/dist/es/index.mjs CHANGED
@@ -1,3 +1,9 @@
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
+
1
7
  var config = {
2
8
  defaultUserAgent: `WiseOldMan JS Client v${process.env.npm_package_version}`,
3
9
  baseAPIUrl: 'https://api.wiseoldman.net/v2'
@@ -612,6 +618,7 @@ const Boss = {
612
618
  VETION: 'vetion',
613
619
  VORKATH: 'vorkath',
614
620
  WINTERTODT: 'wintertodt',
621
+ YAMA: 'yama',
615
622
  ZALCANO: 'zalcano',
616
623
  ZULRAH: 'zulrah'
617
624
  };
@@ -641,6 +648,8 @@ const PlayerType = {
641
648
  };
642
649
  const PlayerAnnotationType = {
643
650
  OPT_OUT: 'opt_out',
651
+ OPT_OUT_GROUPS: 'opt_out_groups',
652
+ OPT_OUT_COMPETITIONS: 'opt_out_competitions',
644
653
  BLOCKED: 'blocked',
645
654
  FAKE_F2P: 'fake_f2p'
646
655
  };
@@ -1964,6 +1973,7 @@ const BossProps = mapValues({
1964
1973
  [Boss.VETION]: { name: "Vet'ion" },
1965
1974
  [Boss.VORKATH]: { name: 'Vorkath' },
1966
1975
  [Boss.WINTERTODT]: { name: 'Wintertodt' },
1976
+ [Boss.YAMA]: { name: 'Yama' },
1967
1977
  [Boss.ZALCANO]: { name: 'Zalcano' },
1968
1978
  [Boss.ZULRAH]: { name: 'Zulrah' }
1969
1979
  }, props => (Object.assign(Object.assign({}, props), { type: MetricType.BOSS, measure: MetricMeasure.KILLS, isMembers: 'isMembers' in props ? props.isMembers : true, minimumValue: 'minimumValue' in props ? props.minimumValue : 5 })));
@@ -2055,6 +2065,226 @@ function getParentEfficiencyMetric(metric) {
2055
2065
  return null;
2056
2066
  }
2057
2067
 
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
+
2058
2288
  const CUSTOM_PERIOD_REGEX = /(\d+y)?(\d+m)?(\d+w)?(\d+d)?(\d+h)?/;
2059
2289
  const PeriodProps = {
2060
2290
  [Period.FIVE_MIN]: { name: '5 Min', milliseconds: 300000 },
@@ -2082,6 +2312,7 @@ function parsePeriodExpression(periodExpression) {
2082
2312
  durationMs: PeriodProps[fixed].milliseconds
2083
2313
  };
2084
2314
  }
2315
+ prometheusService.trackCustomPeriodExpression(fixed);
2085
2316
  const result = fixed.match(CUSTOM_PERIOD_REGEX);
2086
2317
  if (!result || result.length === 0 || result[0] !== fixed)
2087
2318
  return null;
package/dist/index.d.ts CHANGED
@@ -497,6 +497,7 @@ declare const Boss: {
497
497
  readonly VETION: "vetion";
498
498
  readonly VORKATH: "vorkath";
499
499
  readonly WINTERTODT: "wintertodt";
500
+ readonly YAMA: "yama";
500
501
  readonly ZALCANO: "zalcano";
501
502
  readonly ZULRAH: "zulrah";
502
503
  };
@@ -572,6 +573,7 @@ declare const Metric: {
572
573
  readonly VETION: "vetion";
573
574
  readonly VORKATH: "vorkath";
574
575
  readonly WINTERTODT: "wintertodt";
576
+ readonly YAMA: "yama";
575
577
  readonly ZALCANO: "zalcano";
576
578
  readonly ZULRAH: "zulrah";
577
579
  readonly LEAGUE_POINTS: "league_points";
@@ -640,6 +642,8 @@ declare const PlayerType: {
640
642
  type PlayerType = (typeof PlayerType)[keyof typeof PlayerType];
641
643
  declare const PlayerAnnotationType: {
642
644
  readonly OPT_OUT: "opt_out";
645
+ readonly OPT_OUT_GROUPS: "opt_out_groups";
646
+ readonly OPT_OUT_COMPETITIONS: "opt_out_competitions";
643
647
  readonly BLOCKED: "blocked";
644
648
  readonly FAKE_F2P: "fake_f2p";
645
649
  };
@@ -1402,9 +1406,6 @@ interface GroupStatistics {
1402
1406
  averageStats: FormattedSnapshot;
1403
1407
  metricLeaders: MetricLeaders;
1404
1408
  }
1405
- type MemberRoleChangeEvent = Omit<MemberActivity, 'createdAt'>;
1406
- type MemberJoinedEvent = Omit<MemberActivity, 'createdAt' | 'previousRole'>;
1407
- type MemberLeftEvent = Omit<MemberActivity, 'createdAt' | 'previousRole'>;
1408
1409
  type MemberActivityWithPlayer = MemberActivity & {
1409
1410
  player: Player;
1410
1411
  };
@@ -1733,6 +1734,7 @@ declare const MetricProps: {
1733
1734
  readonly vetion: BossProperties;
1734
1735
  readonly vorkath: BossProperties;
1735
1736
  readonly wintertodt: BossProperties;
1737
+ readonly yama: BossProperties;
1736
1738
  readonly zalcano: BossProperties;
1737
1739
  readonly zulrah: BossProperties;
1738
1740
  readonly overall: SkillProperties;
@@ -2281,4 +2283,4 @@ declare class WOMClient extends BaseAPIClient {
2281
2283
  constructor(options?: WOMClientOptions);
2282
2284
  }
2283
2285
 
2284
- export { ACTIVITIES, type Achievement, type AchievementDefinition, type AchievementProgress, type AchievementTemplate, Activity, type ActivityDelta, ActivityType, type ActivityValue, type AssertPlayerTypeResponse, BOSSES, type Bonus, Boss, type BossDelta, type BossMetaConfig, type BossValue, CAPPED_MAX_TOTAL_XP, COMBAT_SKILLS, COMPETITION_STATUSES, COMPETITION_TYPES, COMPUTED_METRICS, COUNTRY_CODES, type ChangeMemberRolePayload, CompetitionCSVTableType, type CompetitionDetails, type CompetitionDetailsCSVParams, type CompetitionListItem, CompetitionStatus, CompetitionStatusProps, CompetitionType, CompetitionTypeProps, type CompetitionWithParticipations, type CompetitionsSearchFilter, ComputedMetric, type ComputedMetricDelta, type ComputedMetricValue, Country, type CountryDetails, CountryProps, type CreateCompetitionPayload, type CreateCompetitionResponse, type CreateGroupPayload, type CreateGroupResponse, type DeltaGroupLeaderboardEntry, type DeltaLeaderboardEntry, type DeltaLeaderboardFilter, type DenyContext, type EditCompetitionPayload, type EditGroupPayload, EfficiencyAlgorithmType, type EfficiencyAlgorithmTypeUnion, type EfficiencyLeaderboardsFilter, type ExtendedAchievement, type ExtendedAchievementWithPlayer, F2P_BOSSES, type FlaggedPlayerReviewContext, type FormattedSnapshot, GROUP_ROLES, type GenericCountMessageResponse, type GenericMessageResponse, type GetGroupGainsFilter, type GetPlayerGainsResponse, type Group, type GroupDetails, type GroupHiscoresActivityItem, type GroupHiscoresBossItem, type GroupHiscoresComputedMetricItem, type GroupHiscoresEntry, type GroupHiscoresSkillItem, type GroupListItem, type GroupMemberFragment, type GroupRecordsFilter, GroupRole, GroupRoleProps, type GroupStatistics, MAX_LEVEL, MAX_SKILL_EXP, MAX_VIRTUAL_LEVEL, MEMBER_SKILLS, METRICS, type MapOf, type MeasuredDeltaProgress, type MemberActivityWithPlayer, type MemberInput, type MemberJoinedEvent, type MemberLeftEvent, type MemberRoleChangeEvent, type MembershipWithGroup, type MembershipWithPlayer, Metric, type MetricLeaders, MetricMeasure, MetricProps, MetricType, type MetricValueKey, type NameChange, type NameChangeDetails, NameChangeStatus, type NameChangeWithPlayer, type NameChangesSearchFilter, PERIODS, PLAYER_BUILDS, PLAYER_STATUSES, PLAYER_TYPES, PRIVELEGED_GROUP_ROLES, type ParticipationWithCompetition, type ParticipationWithCompetitionAndStandings, type ParticipationWithPlayer, type ParticipationWithPlayerAndProgress, Period, PeriodProps, type Player, PlayerAnnotationType, type PlayerArchiveWithPlayer, PlayerBuild, PlayerBuildProps, type PlayerCompetitionStandingsFilter, type PlayerCompetitionsFilter, type PlayerDeltasMap, type PlayerDetails, type PlayerRecordsFilter, PlayerStatus, PlayerStatusProps, PlayerType, PlayerTypeProps, REAL_METRICS, REAL_SKILLS, type Record, type RecordLeaderboardEntry, type RecordLeaderboardFilter, SKILLS, SKILL_EXP_AT_99, Skill, type SkillDelta, type SkillMetaConfig, type SkillMetaMethod, type SkillValue, type SkipContext, type Snapshot, type SnapshotFragment, type Team, type TimeRangeFilter, type Top5ProgressResult, WOMClient, findCountry, findCountryByCode, findCountryByName, findGroupRole, findMetric, findPeriod, findPlayerBuild, findPlayerType, formatNumber, getCombatLevel, getExpForLevel, getLevel, getMetricMeasure, getMetricName, getMetricRankKey, getMetricValueKey, getMinimumValue, getParentEfficiencyMetric, isActivity, isBoss, isCompetitionStatus, isCompetitionType, isComputedMetric, isCountry, isGroupRole, isMetric, isPeriod, isPlayerBuild, isPlayerStatus, isPlayerType, isSkill, padNumber, parsePeriodExpression, round };
2286
+ export { ACTIVITIES, type Achievement, type AchievementDefinition, type AchievementProgress, type AchievementTemplate, Activity, type ActivityDelta, ActivityType, type ActivityValue, type AssertPlayerTypeResponse, BOSSES, type Bonus, Boss, type BossDelta, type BossMetaConfig, type BossValue, CAPPED_MAX_TOTAL_XP, COMBAT_SKILLS, COMPETITION_STATUSES, COMPETITION_TYPES, COMPUTED_METRICS, COUNTRY_CODES, type ChangeMemberRolePayload, CompetitionCSVTableType, type CompetitionDetails, type CompetitionDetailsCSVParams, type CompetitionListItem, CompetitionStatus, CompetitionStatusProps, CompetitionType, CompetitionTypeProps, type CompetitionWithParticipations, type CompetitionsSearchFilter, ComputedMetric, type ComputedMetricDelta, type ComputedMetricValue, Country, type CountryDetails, CountryProps, type CreateCompetitionPayload, type CreateCompetitionResponse, type CreateGroupPayload, type CreateGroupResponse, type DeltaGroupLeaderboardEntry, type DeltaLeaderboardEntry, type DeltaLeaderboardFilter, type DenyContext, type EditCompetitionPayload, type EditGroupPayload, EfficiencyAlgorithmType, type EfficiencyAlgorithmTypeUnion, type EfficiencyLeaderboardsFilter, type ExtendedAchievement, type ExtendedAchievementWithPlayer, F2P_BOSSES, type FlaggedPlayerReviewContext, type FormattedSnapshot, GROUP_ROLES, type GenericCountMessageResponse, type GenericMessageResponse, type GetGroupGainsFilter, type GetPlayerGainsResponse, type Group, type GroupDetails, type GroupHiscoresActivityItem, type GroupHiscoresBossItem, type GroupHiscoresComputedMetricItem, type GroupHiscoresEntry, type GroupHiscoresSkillItem, type GroupListItem, type GroupMemberFragment, type GroupRecordsFilter, GroupRole, GroupRoleProps, type GroupStatistics, MAX_LEVEL, MAX_SKILL_EXP, MAX_VIRTUAL_LEVEL, MEMBER_SKILLS, METRICS, type MapOf, type MeasuredDeltaProgress, type MemberActivityWithPlayer, type MemberInput, type MembershipWithGroup, type MembershipWithPlayer, Metric, type MetricLeaders, MetricMeasure, MetricProps, MetricType, type MetricValueKey, type NameChange, type NameChangeDetails, NameChangeStatus, type NameChangeWithPlayer, type NameChangesSearchFilter, PERIODS, PLAYER_BUILDS, PLAYER_STATUSES, PLAYER_TYPES, PRIVELEGED_GROUP_ROLES, type ParticipationWithCompetition, type ParticipationWithCompetitionAndStandings, type ParticipationWithPlayer, type ParticipationWithPlayerAndProgress, Period, PeriodProps, type Player, PlayerAnnotationType, type PlayerArchiveWithPlayer, PlayerBuild, PlayerBuildProps, type PlayerCompetitionStandingsFilter, type PlayerCompetitionsFilter, type PlayerDeltasMap, type PlayerDetails, type PlayerRecordsFilter, PlayerStatus, PlayerStatusProps, PlayerType, PlayerTypeProps, REAL_METRICS, REAL_SKILLS, type Record, type RecordLeaderboardEntry, type RecordLeaderboardFilter, SKILLS, SKILL_EXP_AT_99, Skill, type SkillDelta, type SkillMetaConfig, type SkillMetaMethod, type SkillValue, type SkipContext, type Snapshot, type SnapshotFragment, type Team, type TimeRangeFilter, type Top5ProgressResult, WOMClient, findCountry, findCountryByCode, findCountryByName, findGroupRole, findMetric, findPeriod, findPlayerBuild, findPlayerType, formatNumber, getCombatLevel, getExpForLevel, getLevel, getMetricMeasure, getMetricName, getMetricRankKey, getMetricValueKey, getMinimumValue, getParentEfficiencyMetric, isActivity, isBoss, isCompetitionStatus, isCompetitionType, isComputedMetric, isCountry, isGroupRole, isMetric, isPeriod, isPlayerBuild, isPlayerStatus, isPlayerType, isSkill, padNumber, parsePeriodExpression, round };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wise-old-man/utils",
3
- "version": "3.3.10",
3
+ "version": "3.3.12",
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",