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