@oxy.so/core 1.0.1 → 1.1.2
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.
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/OxyServices.errors.js +28 -1
- package/dist/cjs/i18n/locales/en-US.json +14 -1
- package/dist/cjs/i18n/locales/es-ES.json +14 -1
- package/dist/cjs/i18n/locales/locales/en-US.json +14 -1
- package/dist/cjs/i18n/locales/locales/es-ES.json +14 -1
- package/dist/cjs/mixins/OxyServices.agency.js +113 -0
- package/dist/cjs/mixins/OxyServices.assets.js +90 -0
- package/dist/cjs/mixins/OxyServices.connectedApps.js +20 -3
- package/dist/cjs/mixins/OxyServices.utility.js +1 -21
- package/dist/cjs/mixins/index.js +3 -4
- package/dist/cjs/server/index.js +6 -1
- package/dist/cjs/server/traffic.js +107 -0
- package/dist/cjs/server/trafficNodeHttp.js +86 -0
- package/dist/cjs/utils/cache.js +28 -19
- package/dist/cjs/utils/textNormalization.js +1 -1
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/OxyServices.errors.js +26 -0
- package/dist/esm/i18n/locales/en-US.json +14 -1
- package/dist/esm/i18n/locales/es-ES.json +14 -1
- package/dist/esm/i18n/locales/locales/en-US.json +14 -1
- package/dist/esm/i18n/locales/locales/es-ES.json +14 -1
- package/dist/esm/mixins/OxyServices.agency.js +110 -0
- package/dist/esm/mixins/OxyServices.assets.js +91 -1
- package/dist/esm/mixins/OxyServices.connectedApps.js +20 -3
- package/dist/esm/mixins/OxyServices.utility.js +1 -21
- package/dist/esm/mixins/index.js +3 -4
- package/dist/esm/server/index.js +2 -0
- package/dist/esm/server/traffic.js +104 -0
- package/dist/esm/server/trafficNodeHttp.js +79 -0
- package/dist/esm/utils/cache.js +28 -19
- package/dist/esm/utils/textNormalization.js +1 -1
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/OxyServices.errors.d.ts +24 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/types/mixins/OxyServices.agency.d.ts +135 -0
- package/dist/types/mixins/OxyServices.assets.d.ts +43 -1
- package/dist/types/mixins/OxyServices.connectedApps.d.ts +15 -0
- package/dist/types/mixins/OxyServices.utility.d.ts +0 -9
- package/dist/types/mixins/index.d.ts +2 -2
- package/dist/types/models/interfaces.d.ts +26 -0
- package/dist/types/server/index.d.ts +3 -0
- package/dist/types/server/traffic.d.ts +27 -0
- package/dist/types/server/trafficNodeHttp.d.ts +6 -0
- package/dist/types/utils/cache.d.ts +1 -4
- package/dist/types/utils/textNormalization.d.ts +1 -1
- package/package.json +4 -4
- package/src/OxyServices.errors.ts +36 -0
- package/src/i18n/locales/en-US.json +14 -1
- package/src/i18n/locales/es-ES.json +14 -1
- package/src/index.ts +15 -0
- package/src/mixins/OxyServices.agency.ts +277 -0
- package/src/mixins/OxyServices.assets.ts +105 -2
- package/src/mixins/OxyServices.connectedApps.ts +42 -4
- package/src/mixins/OxyServices.utility.ts +1 -26
- package/src/mixins/__tests__/OxyServices.serviceLinkedDownloadUrl.test.ts +139 -0
- package/src/mixins/__tests__/agency.test.ts +120 -0
- package/src/mixins/__tests__/connectedApps.test.ts +23 -0
- package/src/mixins/index.ts +4 -5
- package/src/models/interfaces.ts +27 -0
- package/src/server/__tests__/traffic.test.ts +49 -0
- package/src/server/__tests__/trafficNodeHttp.test.ts +38 -0
- package/src/server/index.ts +5 -0
- package/src/server/traffic.ts +116 -0
- package/src/server/trafficNodeHttp.ts +69 -0
- package/src/utils/__tests__/cacheLifecycle.test.ts +65 -0
- package/src/utils/cache.ts +22 -20
- package/src/utils/textNormalization.ts +1 -1
- package/dist/cjs/mixins/OxyServices.links.js +0 -72
- package/dist/esm/mixins/OxyServices.links.js +0 -69
- package/dist/types/mixins/OxyServices.links.d.ts +0 -104
- package/src/mixins/OxyServices.links.ts +0 -107
- package/src/mixins/__tests__/OxyServices.links.test.ts +0 -154
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
import https from 'node:https';
|
|
3
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
4
|
+
import { syncBuiltinESMExports } from 'node:module';
|
|
5
|
+
import { normalizeInfrastructureRegion, trafficType } from '@oxy.so/telemetry/collector';
|
|
6
|
+
const suppressed = new AsyncLocalStorage();
|
|
7
|
+
export function withoutNodeHttpObservation(callback) { return suppressed.run(true, callback); }
|
|
8
|
+
/** Node and Bun share the HTTP facade; Bun does not emit Node's HTTP diagnostic channels. */
|
|
9
|
+
export function observeNodeHttp(collector, service, region, resolveEndpoint) {
|
|
10
|
+
const seen = new WeakSet();
|
|
11
|
+
const restoreRequests = new Set();
|
|
12
|
+
const observe = (request) => {
|
|
13
|
+
if (seen.has(request) || suppressed.getStore())
|
|
14
|
+
return request;
|
|
15
|
+
seen.add(request);
|
|
16
|
+
const host = request.getHeader('host') ?? request.host;
|
|
17
|
+
if (typeof host !== 'string')
|
|
18
|
+
return request;
|
|
19
|
+
const url = new URL(request.path || '/', `${request.protocol || 'http:'}//${host}`);
|
|
20
|
+
if (/^\/(?:internal\/activity|auth\/service-token|platform-infrastructure)(?:\/|$)/.test(url.pathname))
|
|
21
|
+
return request;
|
|
22
|
+
const peer = resolveEndpoint(url);
|
|
23
|
+
if (peer && !request.headersSent)
|
|
24
|
+
request.setHeader('X-Oxy-Source-Region', region);
|
|
25
|
+
const flow = { service, region, activityType: trafficType(url.pathname), scope: peer ? 'internal' : 'external', direction: 'outbound', sourceRegion: region, sourceService: service, targetService: peer?.service };
|
|
26
|
+
const emit = request.emit;
|
|
27
|
+
let recorded = false;
|
|
28
|
+
const restore = () => { if (request.emit === observedEmit)
|
|
29
|
+
request.emit = emit; restoreRequests.delete(restore); };
|
|
30
|
+
// Intercept notifications without adding an 'error' listener: observation
|
|
31
|
+
// must not turn an unhandled application error into a swallowed error.
|
|
32
|
+
const observedEmit = function (event, ...args) {
|
|
33
|
+
try {
|
|
34
|
+
if (!recorded && event === 'response') {
|
|
35
|
+
recorded = true;
|
|
36
|
+
const response = args[0];
|
|
37
|
+
const targetRegion = peer ? normalizeInfrastructureRegion(response.headers['x-oxy-region']) : undefined;
|
|
38
|
+
collector.record({ ...flow, targetRegion });
|
|
39
|
+
collector.record({ ...flow, direction: 'inbound', sourceRegion: targetRegion, targetRegion: region, sourceService: peer?.service, targetService: service });
|
|
40
|
+
}
|
|
41
|
+
else if (!recorded && (event === 'error' || event === 'close')) {
|
|
42
|
+
recorded = true;
|
|
43
|
+
collector.record(flow);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch { /* A metrics failure cannot change a request's behavior. */ }
|
|
47
|
+
if (event === 'close')
|
|
48
|
+
restore();
|
|
49
|
+
return emit.call(this, event, ...args);
|
|
50
|
+
};
|
|
51
|
+
request.emit = observedEmit;
|
|
52
|
+
restoreRequests.add(restore);
|
|
53
|
+
return request;
|
|
54
|
+
};
|
|
55
|
+
const safelyObserve = (request) => {
|
|
56
|
+
try {
|
|
57
|
+
return observe(request);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return request;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const patches = [http, https].map(module => {
|
|
64
|
+
const request = module.request;
|
|
65
|
+
const get = module.get;
|
|
66
|
+
const wrappedRequest = function (...args) { return safelyObserve(request.apply(module, args)); };
|
|
67
|
+
const wrappedGet = function (...args) { return safelyObserve(get.apply(module, args)); };
|
|
68
|
+
module.request = wrappedRequest;
|
|
69
|
+
module.get = wrappedGet;
|
|
70
|
+
return () => {
|
|
71
|
+
if (module.request === wrappedRequest)
|
|
72
|
+
module.request = request;
|
|
73
|
+
if (module.get === wrappedGet)
|
|
74
|
+
module.get = get;
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
syncBuiltinESMExports();
|
|
78
|
+
return () => { patches.forEach(restore => restore()); restoreRequests.forEach(restore => restore()); syncBuiltinESMExports(); };
|
|
79
|
+
}
|
package/dist/esm/utils/cache.js
CHANGED
|
@@ -78,6 +78,8 @@ export class TTLCache {
|
|
|
78
78
|
const effectiveTTL = ttl === undefined ? this.defaultTTL : ttl;
|
|
79
79
|
const expiresAt = now + effectiveTTL;
|
|
80
80
|
this.cache.set(key, { data, timestamp: now, expiresAt });
|
|
81
|
+
if (!cleanupInterval && activeCaches.has(this))
|
|
82
|
+
updateCleanupInterval();
|
|
81
83
|
}
|
|
82
84
|
/**
|
|
83
85
|
* Delete a specific cache entry
|
|
@@ -85,7 +87,10 @@ export class TTLCache {
|
|
|
85
87
|
* @returns true if entry was deleted, false if not found
|
|
86
88
|
*/
|
|
87
89
|
delete(key) {
|
|
88
|
-
|
|
90
|
+
const deleted = this.cache.delete(key);
|
|
91
|
+
if (activeCaches.has(this))
|
|
92
|
+
updateCleanupInterval();
|
|
93
|
+
return deleted;
|
|
89
94
|
}
|
|
90
95
|
/**
|
|
91
96
|
* Clear all cache entries
|
|
@@ -94,6 +99,8 @@ export class TTLCache {
|
|
|
94
99
|
this.cache.clear();
|
|
95
100
|
this.hits = 0;
|
|
96
101
|
this.misses = 0;
|
|
102
|
+
if (activeCaches.has(this))
|
|
103
|
+
updateCleanupInterval();
|
|
97
104
|
}
|
|
98
105
|
/**
|
|
99
106
|
* Check if a key exists and is not expired
|
|
@@ -191,28 +198,30 @@ const activeCaches = new Set();
|
|
|
191
198
|
*/
|
|
192
199
|
export function registerCacheForCleanup(cache) {
|
|
193
200
|
activeCaches.add(cache);
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
cleanupInterval
|
|
201
|
+
updateCleanupInterval();
|
|
202
|
+
}
|
|
203
|
+
/** Empty registered caches need no timer; importing a client must stay inert. */
|
|
204
|
+
function updateCleanupInterval() {
|
|
205
|
+
const hasEntries = [...activeCaches].some(cache => cache.size() > 0);
|
|
206
|
+
if (!hasEntries) {
|
|
207
|
+
if (cleanupInterval)
|
|
208
|
+
clearInterval(cleanupInterval);
|
|
209
|
+
cleanupInterval = null;
|
|
210
|
+
return;
|
|
203
211
|
}
|
|
212
|
+
if (cleanupInterval)
|
|
213
|
+
return;
|
|
214
|
+
cleanupInterval = setInterval(() => {
|
|
215
|
+
for (const cache of activeCaches)
|
|
216
|
+
cache.cleanup();
|
|
217
|
+
updateCleanupInterval();
|
|
218
|
+
}, 60000);
|
|
219
|
+
cleanupInterval.unref?.();
|
|
204
220
|
}
|
|
205
|
-
/**
|
|
206
|
-
* Unregister a cache from automatic cleanup
|
|
207
|
-
* @param cache Cache instance to unregister
|
|
208
|
-
*/
|
|
221
|
+
/** Unregister a cache without changing its entries or TTL behavior. */
|
|
209
222
|
export function unregisterCacheFromCleanup(cache) {
|
|
210
223
|
activeCaches.delete(cache);
|
|
211
|
-
|
|
212
|
-
if (activeCaches.size === 0 && cleanupInterval) {
|
|
213
|
-
clearInterval(cleanupInterval);
|
|
214
|
-
cleanupInterval = null;
|
|
215
|
-
}
|
|
224
|
+
updateCleanupInterval();
|
|
216
225
|
}
|
|
217
226
|
/**
|
|
218
227
|
* Stop all cleanup intervals (useful for testing)
|
|
@@ -83,7 +83,7 @@ const LEADING_HORIZONTAL_WHITESPACE = /\n +/g;
|
|
|
83
83
|
/** Three or more line breaks: more than one blank line between paragraphs. */
|
|
84
84
|
const EXCESS_BLANK_LINES = /\n{3,}/g;
|
|
85
85
|
/**
|
|
86
|
-
* Normalize a SINGLE-LINE text value
|
|
86
|
+
* Normalize a SINGLE-LINE text value such as a page title or `siteName`,
|
|
87
87
|
* display names, image alt text, handles, profile field labels.
|
|
88
88
|
*
|
|
89
89
|
* A line break in such a value is never meaningful — it is an artifact of the
|