@ecosy/core 0.4.0 → 0.6.0

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 (132) hide show
  1. package/CHANGELOG.md +173 -0
  2. package/dist/cache/disk.d.ts +44 -0
  3. package/dist/cache/disk.js +1 -0
  4. package/dist/cache/disk.mjs +1 -0
  5. package/dist/cache/index.d.ts +17 -0
  6. package/dist/cache/index.js +1 -0
  7. package/dist/cache/index.mjs +1 -0
  8. package/dist/cache/memory.d.ts +19 -0
  9. package/dist/cache/memory.js +1 -0
  10. package/dist/cache/memory.mjs +1 -0
  11. package/dist/cache/redis.d.ts +65 -0
  12. package/dist/cache/redis.js +1 -0
  13. package/dist/cache/redis.mjs +1 -0
  14. package/dist/cache/types.d.ts +51 -0
  15. package/dist/cache/types.js +1 -0
  16. package/dist/cache/types.mjs +1 -0
  17. package/dist/ecosy-core.umd.js +1 -1
  18. package/dist/ecosy-core.umd.js.map +1 -1
  19. package/dist/index.d.ts +0 -9
  20. package/dist/index.js +1 -1
  21. package/dist/index.mjs +1 -1
  22. package/dist/serialize/index.d.ts +62 -0
  23. package/dist/serialize/index.js +1 -0
  24. package/dist/serialize/index.mjs +1 -0
  25. package/dist/serialize/interpolate.d.ts +10 -0
  26. package/dist/serialize/interpolate.js +1 -0
  27. package/dist/serialize/interpolate.mjs +1 -0
  28. package/dist/serialize/json.d.ts +3 -0
  29. package/dist/serialize/json.js +1 -0
  30. package/dist/serialize/json.mjs +1 -0
  31. package/dist/serialize/primitive.d.ts +3 -0
  32. package/dist/serialize/primitive.js +1 -0
  33. package/dist/serialize/primitive.mjs +1 -0
  34. package/dist/serialize/query-string.d.ts +3 -0
  35. package/dist/serialize/query-string.js +1 -0
  36. package/dist/serialize/query-string.mjs +1 -0
  37. package/dist/serialize/types.d.ts +42 -0
  38. package/dist/serialize/url.d.ts +3 -0
  39. package/dist/serialize/url.js +1 -0
  40. package/dist/serialize/url.mjs +1 -0
  41. package/dist/syhemo/collectors.d.ts +25 -0
  42. package/dist/syhemo/collectors.js +1 -0
  43. package/dist/syhemo/collectors.mjs +1 -0
  44. package/dist/syhemo/exposition.d.ts +15 -0
  45. package/dist/syhemo/exposition.js +1 -0
  46. package/dist/syhemo/exposition.mjs +1 -0
  47. package/dist/syhemo/index.d.ts +50 -0
  48. package/dist/syhemo/index.js +1 -0
  49. package/dist/syhemo/index.mjs +1 -0
  50. package/dist/{logger.d.ts → syhemo/logger.d.ts} +23 -11
  51. package/dist/syhemo/logger.js +1 -0
  52. package/dist/{logger.mjs → syhemo/logger.mjs} +1 -1
  53. package/dist/syhemo/types.d.ts +114 -0
  54. package/dist/utilities/index.d.ts +2 -0
  55. package/dist/utilities/index.js +1 -1
  56. package/dist/utilities/index.mjs +1 -1
  57. package/dist/{searchify.d.ts → utilities/searchify.d.ts} +1 -1
  58. package/dist/utilities/searchify.js +1 -0
  59. package/dist/utilities/searchify.mjs +1 -0
  60. package/package.json +35 -37
  61. package/agents/skills/ecosy-core-fetcher/SKILL.md +0 -93
  62. package/dist/env.d.ts +0 -26
  63. package/dist/env.js +0 -1
  64. package/dist/env.mjs +0 -1
  65. package/dist/http/array.d.ts +0 -2
  66. package/dist/http/array.js +0 -1
  67. package/dist/http/array.mjs +0 -1
  68. package/dist/http/client.d.ts +0 -38
  69. package/dist/http/client.js +0 -1
  70. package/dist/http/client.mjs +0 -1
  71. package/dist/http/endpoint.d.ts +0 -8
  72. package/dist/http/endpoint.js +0 -1
  73. package/dist/http/endpoint.mjs +0 -1
  74. package/dist/http/fetcher.d.ts +0 -84
  75. package/dist/http/fetcher.js +0 -1
  76. package/dist/http/fetcher.mjs +0 -1
  77. package/dist/http/http-core.d.ts +0 -5
  78. package/dist/http/http-core.js +0 -1
  79. package/dist/http/http-core.mjs +0 -1
  80. package/dist/http/http-static.d.ts +0 -20
  81. package/dist/http/http-static.js +0 -1
  82. package/dist/http/http-static.mjs +0 -1
  83. package/dist/http/http.d.ts +0 -56
  84. package/dist/http/http.js +0 -1
  85. package/dist/http/http.mjs +0 -1
  86. package/dist/http/index.d.ts +0 -14
  87. package/dist/http/index.js +0 -1
  88. package/dist/http/index.mjs +0 -1
  89. package/dist/http/init.d.ts +0 -83
  90. package/dist/http/init.js +0 -1
  91. package/dist/http/init.mjs +0 -1
  92. package/dist/http/method.d.ts +0 -15
  93. package/dist/http/method.js +0 -1
  94. package/dist/http/method.mjs +0 -1
  95. package/dist/http/plugins/cache.d.ts +0 -13
  96. package/dist/http/plugins/cache.js +0 -1
  97. package/dist/http/plugins/cache.mjs +0 -1
  98. package/dist/http/plugins/dedupe.d.ts +0 -7
  99. package/dist/http/plugins/dedupe.js +0 -1
  100. package/dist/http/plugins/dedupe.mjs +0 -1
  101. package/dist/http/plugins/index.d.ts +0 -3
  102. package/dist/http/plugins/index.js +0 -1
  103. package/dist/http/plugins/index.mjs +0 -1
  104. package/dist/http/plugins/logger.d.ts +0 -24
  105. package/dist/http/plugins/logger.js +0 -1
  106. package/dist/http/plugins/logger.mjs +0 -1
  107. package/dist/http/request.d.ts +0 -26
  108. package/dist/http/response.d.ts +0 -9
  109. package/dist/http/utils.d.ts +0 -21
  110. package/dist/http/utils.js +0 -1
  111. package/dist/http/utils.mjs +0 -1
  112. package/dist/http/xml.d.ts +0 -24
  113. package/dist/http/xml.js +0 -1
  114. package/dist/http/xml.mjs +0 -1
  115. package/dist/logger.js +0 -1
  116. package/dist/node_modules/tslib/tslib.es6.js +0 -1
  117. package/dist/node_modules/tslib/tslib.es6.mjs +0 -1
  118. package/dist/searchify.js +0 -1
  119. package/dist/searchify.mjs +0 -1
  120. package/dist/serialize.d.ts +0 -80
  121. package/dist/serialize.js +0 -1
  122. package/dist/serialize.mjs +0 -1
  123. package/dist/syhemo.d.ts +0 -102
  124. package/dist/syhemo.js +0 -1
  125. package/dist/syhemo.mjs +0 -1
  126. /package/dist/{http/request.js → serialize/types.js} +0 -0
  127. /package/dist/{http/request.mjs → serialize/types.mjs} +0 -0
  128. /package/dist/{http/response.js → syhemo/types.js} +0 -0
  129. /package/dist/{http/response.mjs → syhemo/types.mjs} +0 -0
  130. /package/dist/{slugify.d.ts → utilities/slugify.d.ts} +0 -0
  131. /package/dist/{slugify.js → utilities/slugify.js} +0 -0
  132. /package/dist/{slugify.mjs → utilities/slugify.mjs} +0 -0
package/dist/index.d.ts CHANGED
@@ -1,16 +1,7 @@
1
- export * from "./env";
2
- export * from "./http";
3
- export * from "./logger";
4
- export * from "./searchify";
5
1
  export * from "./serialize";
6
- export * from "./slugify";
7
2
  export * from "./subscriber";
8
3
  export * from "./utilities";
9
- export type * from "./http";
10
- export type * from "./logger";
11
- export type * from "./searchify";
12
4
  export type * from "./serialize";
13
- export type * from "./slugify";
14
5
  export type * from "./subscriber";
15
6
  export type * from "./utilities";
16
7
  export type * from "./types";
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var e=require("./env.js"),t=require("./http/array.js"),r=require("./http/client.js"),i=require("./http/endpoint.js"),s=require("./http/fetcher.js"),p=require("./http/http-static.js"),o=require("./http/http.js"),u=require("./http/init.js"),l=require("./http/method.js"),a=require("./http/plugins/logger.js"),x=require("./http/plugins/dedupe.js"),c=require("./http/plugins/cache.js"),n=require("./http/utils.js"),j=require("./http/xml.js"),g=require("./http/http-core.js"),q=require("./logger.js");require("./searchify.js");var h=require("./serialize.js"),b=require("./slugify.js"),f=require("./subscriber.js"),d=require("./utilities/clone.js"),m=require("./utilities/defer.js"),E=require("./utilities/filelist.js"),y=require("./utilities/flatten.js"),F=require("./utilities/freeze.js"),H=require("./utilities/get.js"),M=require("./utilities/is-equal.js"),O=require("./utilities/is-function.js"),S=require("./utilities/object.js"),A=require("./utilities/merge.js"),L=require("./utilities/formdata.js"),R=require("./utilities/sanitize-mime.js"),T=require("./utilities/string.js");exports.getEnv=e.getEnv,exports.asArray=t.asArray,exports.createClient=r.createClient,exports.Endpoint=i.Endpoint,exports.Fetcher=s.Fetcher,exports.HttpStatic=p.HttpStatic,exports.Http=o.Http,exports.getHttpInitSlice=u.getHttpInitSlice,exports.Methods=l.Methods,exports.Uploads=l.Uploads,exports.loggerPlugin=a.loggerPlugin,exports.dedupePlugin=x.dedupePlugin,exports.cachePlugin=c.cachePlugin,exports.HttpUtils=n.HttpUtils,exports.HttpXML=j.HttpXML,exports.HttpCore=g.HttpCore,exports.Logger=q.Logger,exports.Serialize=h.Serialize,exports.DEFAULT_TRANSFORMER=b.DEFAULT_TRANSFORMER,exports.slugify=b.slugify,exports.Subscriber=f.Subscriber,exports.clone=d.clone,exports.defer=m.defer,exports.deferAsync=m.deferAsync,exports.isFileList=E.isFileList,exports.escapeRegexKey=y.escapeRegexKey,exports.flatten=y.flatten,exports.flattenToArray=y.flattenToArray,exports.freeze=F.freeze,exports.get=H.get,exports.isEqual=M.isEqual,exports.isFunction=O.isFunction,exports.hasOwnProperty=S.hasOwnProperty,exports.isComplexObject=S.isComplexObject,exports.isLiteralObject=S.isLiteralObject,exports.isObject=S.isObject,exports.isObjectable=S.isObjectable,exports.merge=A.merge,exports.isFormData=L.isFormData,exports.objectToFormData=L.objectToFormData,exports.MIME_REGEX=R.MIME_REGEX,exports.sanitizeMime=R.sanitizeMime,exports.pascalToKebab=T.pascalToKebab,exports.toString=T.toString,exports.ucfirst=T.ucfirst;
1
+ "use strict";var e=require("./serialize/index.js"),i=require("./subscriber.js"),r=require("./utilities/clone.js"),s=require("./utilities/defer.js"),t=require("./utilities/filelist.js"),o=require("./utilities/flatten.js"),u=require("./utilities/freeze.js"),l=require("./utilities/get.js"),a=require("./utilities/is-equal.js"),p=require("./utilities/is-function.js"),x=require("./utilities/object.js"),j=require("./utilities/merge.js"),n=require("./utilities/formdata.js"),c=require("./utilities/sanitize-mime.js"),q=require("./utilities/searchify.js"),f=require("./utilities/slugify.js"),b=require("./utilities/string.js"),g=require("./serialize/json.js"),m=require("./serialize/primitive.js"),y=require("./serialize/url.js"),z=require("./serialize/interpolate.js"),E=require("./serialize/query-string.js");exports.Serialize=e.Serialize,exports.Subscriber=i.Subscriber,exports.clone=r.clone,exports.defer=s.defer,exports.deferAsync=s.deferAsync,exports.isFileList=t.isFileList,exports.escapeRegexKey=o.escapeRegexKey,exports.flatten=o.flatten,exports.flattenToArray=o.flattenToArray,exports.freeze=u.freeze,exports.get=l.get,exports.isEqual=a.isEqual,exports.isFunction=p.isFunction,exports.hasOwnProperty=x.hasOwnProperty,exports.isComplexObject=x.isComplexObject,exports.isLiteralObject=x.isLiteralObject,exports.isObject=x.isObject,exports.isObjectable=x.isObjectable,exports.merge=j.merge,exports.isFormData=n.isFormData,exports.objectToFormData=n.objectToFormData,exports.MIME_REGEX=c.MIME_REGEX,exports.sanitizeMime=c.sanitizeMime,exports.searchify=q.searchify,exports.DEFAULT_TRANSFORMER=f.DEFAULT_TRANSFORMER,exports.slugify=f.slugify,exports.pascalToKebab=b.pascalToKebab,exports.toString=b.toString,exports.ucfirst=b.ucfirst,exports.Json=g.Json,exports.Primitive=m.Primitive,exports.Url=y.Url,exports.interpolate=z.interpolate,exports.queryString=E.queryString;
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- export{getEnv}from"./env.mjs";export{asArray}from"./http/array.mjs";export{createClient}from"./http/client.mjs";export{Endpoint}from"./http/endpoint.mjs";export{Fetcher}from"./http/fetcher.mjs";export{HttpStatic}from"./http/http-static.mjs";export{Http}from"./http/http.mjs";export{getHttpInitSlice}from"./http/init.mjs";export{Methods,Uploads}from"./http/method.mjs";export{loggerPlugin}from"./http/plugins/logger.mjs";export{dedupePlugin}from"./http/plugins/dedupe.mjs";export{cachePlugin}from"./http/plugins/cache.mjs";export{HttpUtils}from"./http/utils.mjs";export{HttpXML}from"./http/xml.mjs";export{HttpCore}from"./http/http-core.mjs";export{Logger}from"./logger.mjs";import"./searchify.mjs";export{Serialize}from"./serialize.mjs";export{DEFAULT_TRANSFORMER,slugify}from"./slugify.mjs";export{Subscriber}from"./subscriber.mjs";export{clone}from"./utilities/clone.mjs";export{defer,deferAsync}from"./utilities/defer.mjs";export{isFileList}from"./utilities/filelist.mjs";export{escapeRegexKey,flatten,flattenToArray}from"./utilities/flatten.mjs";export{freeze}from"./utilities/freeze.mjs";export{get}from"./utilities/get.mjs";export{isEqual}from"./utilities/is-equal.mjs";export{isFunction}from"./utilities/is-function.mjs";export{hasOwnProperty,isComplexObject,isLiteralObject,isObject,isObjectable}from"./utilities/object.mjs";export{merge}from"./utilities/merge.mjs";export{isFormData,objectToFormData}from"./utilities/formdata.mjs";export{MIME_REGEX,sanitizeMime}from"./utilities/sanitize-mime.mjs";export{pascalToKebab,toString,ucfirst}from"./utilities/string.mjs";
1
+ export{Serialize}from"./serialize/index.mjs";export{Subscriber}from"./subscriber.mjs";export{clone}from"./utilities/clone.mjs";export{defer,deferAsync}from"./utilities/defer.mjs";export{isFileList}from"./utilities/filelist.mjs";export{escapeRegexKey,flatten,flattenToArray}from"./utilities/flatten.mjs";export{freeze}from"./utilities/freeze.mjs";export{get}from"./utilities/get.mjs";export{isEqual}from"./utilities/is-equal.mjs";export{isFunction}from"./utilities/is-function.mjs";export{hasOwnProperty,isComplexObject,isLiteralObject,isObject,isObjectable}from"./utilities/object.mjs";export{merge}from"./utilities/merge.mjs";export{isFormData,objectToFormData}from"./utilities/formdata.mjs";export{MIME_REGEX,sanitizeMime}from"./utilities/sanitize-mime.mjs";export{searchify}from"./utilities/searchify.mjs";export{DEFAULT_TRANSFORMER,slugify}from"./utilities/slugify.mjs";export{pascalToKebab,toString,ucfirst}from"./utilities/string.mjs";export{Json}from"./serialize/json.mjs";export{Primitive}from"./serialize/primitive.mjs";export{Url}from"./serialize/url.mjs";export{interpolate}from"./serialize/interpolate.mjs";export{queryString}from"./serialize/query-string.mjs";
@@ -0,0 +1,62 @@
1
+ import { interpolate } from "./interpolate";
2
+ export * from "./types";
3
+ export { interpolate } from "./interpolate";
4
+ export { Json } from "./json";
5
+ export { Primitive } from "./primitive";
6
+ export { queryString } from "./query-string";
7
+ export { Url } from "./url";
8
+ /**
9
+ * Centralized serialization engine and type-guard toolkit.
10
+ * Addresses common pitfalls of `JSON.stringify`/`JSON.parse` — BigInt safety,
11
+ * Date preservation, undefined stripping, and safe URL encoding/decoding.
12
+ *
13
+ * This is the convenience surface: it gathers every part under one name.
14
+ * Referencing it keeps all of them, because a class is a single binding and a
15
+ * bundler cannot drop half of one. Code that cares about what it ships should
16
+ * import the part it uses instead — each lives in its own module, so nothing
17
+ * else comes with it:
18
+ *
19
+ * ```ts
20
+ * import { Primitive } from "@ecosy/core/serialize/primitive";
21
+ * import { queryString } from "@ecosy/core/serialize/query-string";
22
+ * ```
23
+ *
24
+ * The two are the same objects, so mixing them is free.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * Serialize.JSON.stringify({ date: new Date(), big: 123n });
29
+ * Serialize.JSON.parse<User>(jsonText);
30
+ * Serialize.URL.encode("hello world");
31
+ * Serialize.queryString.stringify({ page: 1, tags: ["a", "b"] });
32
+ * ```
33
+ */
34
+ export declare class Serialize {
35
+ static readonly interpolate: typeof interpolate;
36
+ /** Type guards and deep normalization utilities. */
37
+ static readonly Primitive: {
38
+ readonly isString: (value: unknown) => value is string;
39
+ readonly isNumber: (value: unknown) => value is number;
40
+ readonly isBoolean: (value: unknown) => value is boolean;
41
+ readonly isPrimitive: (value: unknown) => value is import("./types").PrimitiveValue;
42
+ readonly isDate: (value: unknown) => value is Date;
43
+ readonly isPlainObject: (value: unknown) => value is import("..").LiteralObject;
44
+ readonly normalize: <T, R = unknown>(data: T) => R;
45
+ };
46
+ /** Safe JSON stringify/parse that never throws. */
47
+ static readonly JSON: {
48
+ readonly stringify: (value: unknown, space?: number) => string;
49
+ readonly parse: <T = unknown>(text: string, reviver?: ((this: any, key: string, value: any) => any) | undefined) => T | null;
50
+ };
51
+ /** URL encoding/decoding with malformed-character recovery. */
52
+ static readonly URL: {
53
+ readonly encode: (value: string, component?: boolean | ((value: string) => string)) => string;
54
+ readonly decode: (value: string, component?: boolean | ((value: string) => string)) => string;
55
+ readonly build: (uri: string, params?: Record<string, import("./types").PrimitiveValue> | null) => string;
56
+ };
57
+ /** Query string parse/stringify with configurable array formats. */
58
+ static readonly queryString: {
59
+ readonly parse: (query: string) => Record<string, string>;
60
+ readonly stringify: (params: Record<string, unknown>, options?: import("./types").SerializeQueryOptions) => string;
61
+ };
62
+ }
@@ -0,0 +1 @@
1
+ "use strict";var r=require("./interpolate.js"),e=require("./json.js"),i=require("./primitive.js"),t=require("./query-string.js"),s=require("./url.js");class o{}o.interpolate=r.interpolate,o.Primitive=i.Primitive,o.JSON=e.Json,o.URL=s.Url,o.queryString=t.queryString,exports.interpolate=r.interpolate,exports.Json=e.Json,exports.Primitive=i.Primitive,exports.queryString=t.queryString,exports.Url=s.Url,exports.Serialize=o;
@@ -0,0 +1 @@
1
+ import{interpolate as r}from"./interpolate.mjs";import{Json as m}from"./json.mjs";import{Primitive as i}from"./primitive.mjs";import{queryString as o}from"./query-string.mjs";import{Url as t}from"./url.mjs";class e{}e.interpolate=r,e.Primitive=i,e.JSON=m,e.URL=t,e.queryString=o;export{m as Json,i as Primitive,e as Serialize,t as Url,r as interpolate,o as queryString};
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Interpolates `{key}` placeholders in a string using deep path resolution via {@link get}.
3
+ * Objects are silently replaced with empty strings to avoid `[object Object]`.
4
+ *
5
+ * @param pattern - Template string with `{key}` or `{path.to.key}` placeholders.
6
+ * @param params - Data object or array to resolve values from.
7
+ * @returns The interpolated string.
8
+ */
9
+ export declare function interpolate(pattern: string, params?: Record<string, unknown> | Array<unknown>): string;
10
+ /** Type guards and deep normalization utilities. */
@@ -0,0 +1 @@
1
+ "use strict";var t=require("../utilities/get.js");exports.interpolate=function(e,r={}){return e&&"string"==typeof e&&e.trim().length&&e.includes("{")&&e.includes("}")?e.replace(/\{([a-zA-Z0-9_.-]+)\}/g,(e,i)=>{const n=t.get(r,i);return null==n||"object"==typeof n?"":String(n)}):e};
@@ -0,0 +1 @@
1
+ import{get as t}from"../utilities/get.mjs";function e(e,n={}){return e&&"string"==typeof e&&e.trim().length&&e.includes("{")&&e.includes("}")?e.replace(/\{([a-zA-Z0-9_.-]+)\}/g,(e,r)=>{const i=t(n,r);return null==i||"object"==typeof i?"":String(i)}):e}export{e as interpolate};
@@ -0,0 +1,3 @@
1
+ import type { SerializeJSON } from "./types";
2
+ /** Safe JSON stringify/parse that never throws. */
3
+ export declare const Json: SerializeJSON;
@@ -0,0 +1 @@
1
+ "use strict";var r=require("../utilities/freeze.js"),e=require("./primitive.js");const t=r.freeze({stringify:(r,t)=>{var i;try{const n=e.Primitive.normalize(r);return null!==(i=JSON.stringify(n,null,t))&&void 0!==i?i:""}catch(r){return""}},parse:(r,e)=>{if(!r)return null;try{return JSON.parse(r,e)}catch(r){return null}}});exports.Json=t;
@@ -0,0 +1 @@
1
+ import{freeze as r}from"../utilities/freeze.mjs";import{Primitive as t}from"./primitive.mjs";const i=r({stringify:(r,i)=>{var e;try{const n=t.normalize(r);return null!==(e=JSON.stringify(n,null,i))&&void 0!==e?e:""}catch(r){return""}},parse:(r,t)=>{if(!r)return null;try{return JSON.parse(r,t)}catch(r){return null}}});export{i as Json};
@@ -0,0 +1,3 @@
1
+ import type { SerializePrimitive } from "./types";
2
+ /** Type guards and deep normalization utilities. */
3
+ export declare const Primitive: SerializePrimitive;
@@ -0,0 +1 @@
1
+ "use strict";var i=require("../utilities/freeze.js"),e=require("../utilities/object.js"),t=require("../utilities/is-function.js");const r=i.freeze({isString:i=>"string"==typeof i,isNumber:i=>"number"==typeof i&&Number.isFinite(i),isBoolean:i=>"boolean"==typeof i,isPrimitive:i=>!e.isObjectable(i),isDate:i=>i instanceof Date&&!Number.isNaN(i.getTime()),isPlainObject:e.isLiteralObject,normalize(i){const s=r;if(s.isPrimitive(i))return"bigint"==typeof i?i.toString():i;if(s.isDate(i))return i.toISOString();if(Array.isArray(i))return i.map(i=>s.normalize(i));if(e.isLiteralObject(i)){const t={};for(const r in i)if(e.hasOwnProperty(i,r)){const e=i[r];void 0!==e&&(t[r]=s.normalize(e))}return t}return i&&e.hasOwnProperty(i,"toJSON")&&t.isFunction(i.toJSON)?i.toJSON():{}}});exports.Primitive=r;
@@ -0,0 +1 @@
1
+ import{freeze as i}from"../utilities/freeze.mjs";import{isLiteralObject as t,hasOwnProperty as e,isObjectable as r}from"../utilities/object.mjs";import{isFunction as o}from"../utilities/is-function.mjs";const n=i({isString:i=>"string"==typeof i,isNumber:i=>"number"==typeof i&&Number.isFinite(i),isBoolean:i=>"boolean"==typeof i,isPrimitive:i=>!r(i),isDate:i=>i instanceof Date&&!Number.isNaN(i.getTime()),isPlainObject:t,normalize(i){const r=n;if(r.isPrimitive(i))return"bigint"==typeof i?i.toString():i;if(r.isDate(i))return i.toISOString();if(Array.isArray(i))return i.map(i=>r.normalize(i));if(t(i)){const t={};for(const o in i)if(e(i,o)){const e=i[o];void 0!==e&&(t[o]=r.normalize(e))}return t}return i&&e(i,"toJSON")&&o(i.toJSON)?i.toJSON():{}}});export{n as Primitive};
@@ -0,0 +1,3 @@
1
+ import type { SerializeQueryString } from "./types";
2
+ /** Query string parse/stringify with configurable array formats. */
3
+ export declare const queryString: SerializeQueryString;
@@ -0,0 +1 @@
1
+ "use strict";var r=require("../utilities/freeze.js"),e=require("./primitive.js"),t=require("./url.js");const i=r.freeze({parse(r){if(!r)return{};const e=r.startsWith("?")?r.slice(1):r,i={};return e.split("&").forEach(r=>{if(!r)return;const[e,s]=r.split("=");e&&(i[t.Url.decode(e)]=s?t.Url.decode(s):"")}),i},stringify(r,i={}){if(null===r||"object"!=typeof r)return"";const{arrayFormat:s="none",arrayFormatSeparator:o=",",skipNull:n=!1,skipEmptyString:l=!1,encode:a=!0,strict:u=!0,sort:c=!1}=i,p=r=>r.length>0&&/^[a-zA-Z0-9_\-.[\]]+$/.test(r),f=r=>a?t.Url.encode(r,a):r,$=[],y=(r,t)=>{if(Array.isArray(t)){if("comma"===s||"separator"===s){const e=t.filter(r=>null!=r&&""!==r);return void(e.length>0&&$.push(`${f(r)}=${f(e.map(String).join(o))}`))}t.forEach((e,t)=>{let i=r;"bracket"===s?i=`${r}[]`:"index"===s&&(i=`${r}[${t}]`),y(i,e)})}else if(e.Primitive.isPlainObject(t))for(const e in t)Object.prototype.hasOwnProperty.call(t,e)&&y(`${r}[${e}]`,t[e]);else null!=t?""!==t?"boolean"!=typeof t?e.Primitive.isDate(t)?$.push(`${f(r)}=${f(t.toISOString())}`):$.push(`${f(r)}=${f(String(t))}`):$.push(`${f(r)}=${t?"true":"false"}`):l||$.push(`${f(r)}=`):n||$.push(`${f(r)}=`)};let h=Object.keys(r);c&&(h="function"==typeof c?h.sort(c):h.sort());for(const e of h)u&&!p(e)||y(e,r[e]);return $.join("&")}});exports.queryString=i;
@@ -0,0 +1 @@
1
+ import{freeze as t}from"../utilities/freeze.mjs";import{Primitive as r}from"./primitive.mjs";import{Url as e}from"./url.mjs";const o=t({parse(t){if(!t)return{};const r=t.startsWith("?")?t.slice(1):t,o={};return r.split("&").forEach(t=>{if(!t)return;const[r,s]=t.split("=");r&&(o[e.decode(r)]=s?e.decode(s):"")}),o},stringify(t,o={}){if(null===t||"object"!=typeof t)return"";const{arrayFormat:s="none",arrayFormatSeparator:i=",",skipNull:n=!1,skipEmptyString:a=!1,encode:p=!0,strict:l=!0,sort:c=!1}=o,f=t=>t.length>0&&/^[a-zA-Z0-9_\-.[\]]+$/.test(t),u=t=>p?e.encode(t,p):t,m=[],$=(t,e)=>{if(Array.isArray(e)){if("comma"===s||"separator"===s){const r=e.filter(t=>null!=t&&""!==t);return void(r.length>0&&m.push(`${u(t)}=${u(r.map(String).join(i))}`))}e.forEach((r,e)=>{let o=t;"bracket"===s?o=`${t}[]`:"index"===s&&(o=`${t}[${e}]`),$(o,r)})}else if(r.isPlainObject(e))for(const r in e)Object.prototype.hasOwnProperty.call(e,r)&&$(`${t}[${r}]`,e[r]);else null!=e?""!==e?"boolean"!=typeof e?r.isDate(e)?m.push(`${u(t)}=${u(e.toISOString())}`):m.push(`${u(t)}=${u(String(e))}`):m.push(`${u(t)}=${e?"true":"false"}`):a||m.push(`${u(t)}=`):n||m.push(`${u(t)}=`)};let h=Object.keys(t);c&&(h="function"==typeof c?h.sort(c):h.sort());for(const r of h)l&&!f(r)||$(r,t[r]);return m.join("&")}});export{o as queryString};
@@ -0,0 +1,42 @@
1
+ import type { Freezable, LiteralObject } from "../types/built-in";
2
+ /** Anything JSON can hold as a leaf, plus the JS primitives that need special care. */
3
+ export type PrimitiveValue = string | number | boolean | null | undefined | symbol | bigint;
4
+ /** Options for {@link queryString.stringify}. */
5
+ export interface SerializeQueryOptions {
6
+ /** Array serialization format. Defaults to `"none"`. */
7
+ arrayFormat?: "bracket" | "index" | "comma" | "separator" | "none";
8
+ /** Separator character when `arrayFormat` is `"separator"`. Defaults to `","`. */
9
+ arrayFormatSeparator?: string;
10
+ /** Skip keys whose value is `null` or `undefined`. */
11
+ skipNull?: boolean;
12
+ /** Skip keys whose value is an empty string. */
13
+ skipEmptyString?: boolean;
14
+ /** Whether to URL-encode keys and values, or a custom encoder function. Defaults to `true`. */
15
+ encode?: boolean | ((value: string) => string);
16
+ /** Reject keys with non-standard characters when `true`. Defaults to `true`. */
17
+ strict?: boolean;
18
+ /** Sort keys alphabetically, or provide a custom comparator. */
19
+ sort?: boolean | ((a: string, b: string) => number);
20
+ }
21
+ export type SerializePrimitive = Freezable<{
22
+ isString(value: unknown): value is string;
23
+ isNumber(value: unknown): value is number;
24
+ isBoolean(value: unknown): value is boolean;
25
+ isPrimitive(value: unknown): value is PrimitiveValue;
26
+ isDate(value: unknown): value is Date;
27
+ isPlainObject(value: unknown): value is LiteralObject;
28
+ normalize<T, R = unknown>(data: T): R;
29
+ }>;
30
+ export type SerializeJSON = Freezable<{
31
+ stringify: (value: unknown, space?: number) => string;
32
+ parse: <T = unknown>(text: string, reviver?: ((this: any, key: string, value: any) => any) | undefined) => T | null;
33
+ }>;
34
+ export type SerializeURL = Freezable<{
35
+ encode(value: string, component?: boolean | ((value: string) => string)): string;
36
+ decode(value: string, component?: boolean | ((value: string) => string)): string;
37
+ build(uri: string, params?: Record<string, PrimitiveValue> | null): string;
38
+ }>;
39
+ export type SerializeQueryString = Freezable<{
40
+ parse(query: string): Record<string, string>;
41
+ stringify(params: Record<string, unknown>, options?: SerializeQueryOptions): string;
42
+ }>;
@@ -0,0 +1,3 @@
1
+ import type { SerializeURL } from "./types";
2
+ /** URL encoding/decoding with malformed-character recovery. */
3
+ export declare const Url: SerializeURL;
@@ -0,0 +1 @@
1
+ "use strict";const e=require("../utilities/freeze.js").freeze({encode(e,r=!0){if(!e)return"";if("function"==typeof r)return r(e);try{return r?encodeURIComponent(e):encodeURI(e)}catch(n){const t=e.replace(/[\uD800-\uDFFF]/g,"");return r?encodeURIComponent(t):encodeURI(t)}},decode:(e,r=!0)=>{if(!e)return"";if("function"==typeof r)return r(e);const n=r?decodeURIComponent:decodeURI;return(r?e.replace(/\+/g,"%20"):e).replace(/(%[0-9A-F]{2})+/gi,e=>{try{return n(e)}catch(r){return console.warn(`[Serialize.URL] Cannot decode part: ${e}`),e}})},build:(r,n)=>r?n&&"object"==typeof n?r.replace(/:([a-zA-Z\d_]+)/g,(r,t)=>{const o=n[t];return null==o?r:e.encode(String(o),!0)}):r:""});exports.Url=e;
@@ -0,0 +1 @@
1
+ import{freeze as e}from"../utilities/freeze.mjs";const n=e({encode(e,n=!0){if(!e)return"";if("function"==typeof n)return n(e);try{return n?encodeURIComponent(e):encodeURI(e)}catch(r){const t=e.replace(/[\uD800-\uDFFF]/g,"");return n?encodeURIComponent(t):encodeURI(t)}},decode:(e,n=!0)=>{if(!e)return"";if("function"==typeof n)return n(e);const r=n?decodeURIComponent:decodeURI;return(n?e.replace(/\+/g,"%20"):e).replace(/(%[0-9A-F]{2})+/gi,e=>{try{return r(e)}catch(n){return console.warn(`[Serialize.URL] Cannot decode part: ${e}`),e}})},build:(e,r)=>e?r&&"object"==typeof r?e.replace(/:([a-zA-Z\d_]+)/g,(e,t)=>{const o=r[t];return null==o?e:n.encode(String(o),!0)}):e:""});export{n as Url};
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Where every number comes from.
3
+ *
4
+ * All of it is Node stdlib — perf_hooks, v8, os, process — plus two things the
5
+ * host feeds in: `recordHttpRequest` for traffic, and the `db` callback given
6
+ * to `Syhemo.start()`. Nothing here knows what framework is running above it.
7
+ *
8
+ * Two rules hold throughout, both from the Prometheus model:
9
+ *
10
+ * - Values are in base units and are not rounded.
11
+ * - Anything typed `counter` only ever grows. Collection runs on Syhemo's own
12
+ * interval, so a scrape never mutates state and two scrapers never steal
13
+ * each other's numbers.
14
+ */
15
+ import type { Metric, SyhemoLogSource } from "./types";
16
+ /**
17
+ * Records one finished request.
18
+ *
19
+ * @param latencyMs Duration in milliseconds — converted to seconds here, since
20
+ * callers time requests in ms and Prometheus wants seconds.
21
+ */
22
+ export declare function recordHttpRequest(latencyMs: number): void;
23
+ /** Registers the connectivity probe `Syhemo.start({ db })` was given. */
24
+ export declare function setDbChecker(fn: (() => boolean) | null): void;
25
+ export declare function collectMetrics(source: SyhemoLogSource): Metric[];
@@ -0,0 +1 @@
1
+ "use strict";var node_perf_hooks=require("node:perf_hooks"),v8=require("v8"),os=require("os");function _interopNamespace(e){if(e&&e.__esModule)return e;var t=Object.create(null);return e&&Object.keys(e).forEach(function(o){if("default"!==o){var a=Object.getOwnPropertyDescriptor(e,o);Object.defineProperty(t,o,a.get?a:{enumerable:!0,get:function(){return e[o]}})}}),t.default=e,Object.freeze(t)}var v8__namespace=_interopNamespace(v8),os__namespace=_interopNamespace(os);const NS_PER_SECOND=1e9,MS_PER_SECOND=1e3;function collectMemory(){const e=process.memoryUsage();return[{name:"process_resident_memory_bytes",type:"gauge",help:"Resident set size in bytes.",value:e.rss},{name:"nodejs_external_memory_bytes",type:"gauge",help:"Memory used by C++ objects bound to JavaScript, in bytes.",value:e.external},{name:"nodejs_array_buffers_bytes",type:"gauge",help:"Memory allocated for ArrayBuffers and SharedArrayBuffers, in bytes.",value:e.arrayBuffers}]}let prevCpuUsage=null;function collectCpu(){var e,t;const o=os__namespace.cpus();let a=0;if(prevCpuUsage&&prevCpuUsage.length===o.length){let e=0,t=0;for(let a=0;a<o.length;a++){const l=prevCpuUsage[a].times,s=o[a].times,n=l.user+l.nice+l.sys+l.idle+l.irq;e+=s.user+s.nice+s.sys+s.idle+s.irq-n,t+=s.idle-l.idle}a=e>0?1-t/e:0}return prevCpuUsage=o,[{name:"process_cpu_usage_ratio",type:"gauge",help:"Share of CPU time spent non-idle since the previous collection, 0 to 1.",value:a},{name:"nodejs_cpu_count",type:"gauge",help:"Number of logical CPUs.",value:o.length},{name:"nodejs_cpu_info",type:"gauge",help:"CPU model, always 1.",labels:{model:null!==(t=null===(e=o[0])||void 0===e?void 0:e.model)&&void 0!==t?t:"unknown"},value:1}]}function collectHeap(){const e=v8__namespace.getHeapStatistics();return[{name:"nodejs_heap_size_total_bytes",type:"gauge",help:"Total V8 heap size in bytes.",value:e.total_heap_size},{name:"nodejs_heap_size_used_bytes",type:"gauge",help:"Used V8 heap size in bytes.",value:e.used_heap_size},{name:"nodejs_heap_size_limit_bytes",type:"gauge",help:"Ceiling V8 will grow the heap to, in bytes.",value:e.heap_size_limit},{name:"nodejs_malloced_memory_bytes",type:"gauge",help:"Memory malloc'd by V8, in bytes.",value:e.malloced_memory},{name:"nodejs_native_contexts",type:"gauge",help:"Live top-level contexts. A number that only grows indicates a leak.",value:e.number_of_native_contexts},{name:"nodejs_detached_contexts",type:"gauge",help:"Contexts detached but not yet garbage collected.",value:e.number_of_detached_contexts}]}function collectHandles(){var e,t,o,a,l,s;const n=null!==(t=null===(e=process._getActiveHandles)||void 0===e?void 0:e.call(process))&&void 0!==t?t:[],r=null!==(a=null===(o=process._getActiveRequests)||void 0===o?void 0:o.call(process))&&void 0!==a?a:[];let c=0,u=0;for(const e of n){const t=null!==(s=null===(l=null==e?void 0:e.constructor)||void 0===l?void 0:l.name)&&void 0!==s?s:"";"Timeout"===t||"Timer"===t||"Immediate"===t?c++:"Socket"!==t&&"TCP"!==t&&"TLSSocket"!==t||u++}return[{name:"nodejs_active_handles",type:"gauge",help:"Active libuv handles by kind.",labels:{kind:"timer"},value:c},{name:"nodejs_active_handles",type:"gauge",help:"Active libuv handles by kind.",labels:{kind:"socket"},value:u},{name:"nodejs_active_handles",type:"gauge",help:"Active libuv handles by kind.",labels:{kind:"other"},value:n.length-c-u},{name:"nodejs_active_requests_total",type:"gauge",help:"Active libuv requests.",value:r.length}]}const TOP_MODULE_GROUPS=20;function collectModules(){var _a;try{const cache=eval("typeof require !== 'undefined' && require.cache")||{},keys=Object.keys(cache),groups=new Map;for(const e of keys){const t=e.match(/node_modules\/([^/]+)/),o=t?`node_modules/${t[1]}`:e.replace(process.cwd(),".");groups.set(o,(null!==(_a=groups.get(o))&&void 0!==_a?_a:0)+1)}const top=Array.from(groups.entries()).sort((e,t)=>t[1]-e[1]).slice(0,TOP_MODULE_GROUPS);return[{name:"nodejs_module_cache_entries",type:"gauge",help:"Entries in the CommonJS module cache.",value:keys.length},...top.map(([e,t])=>({name:"nodejs_module_cache_entries_by_group",type:"gauge",help:`Module cache entries per group, top ${TOP_MODULE_GROUPS}.`,labels:{group:e},value:t}))]}catch(e){return[{name:"nodejs_module_cache_entries",type:"gauge",help:"Entries in the CommonJS module cache.",value:0}]}}const histogram=node_perf_hooks.monitorEventLoopDelay({resolution:10});function collectEventLoop(){const e=0===histogram.count,t=t=>e||!Number.isFinite(t)?0:t/NS_PER_SECOND,o=[{name:"nodejs_eventloop_lag_mean_seconds",type:"gauge",help:"Mean event loop lag over the last interval.",value:t(histogram.mean)},{name:"nodejs_eventloop_lag_min_seconds",type:"gauge",help:"Minimum event loop lag over the last interval.",value:t(histogram.min)},{name:"nodejs_eventloop_lag_max_seconds",type:"gauge",help:"Maximum event loop lag over the last interval.",value:t(histogram.max)},{name:"nodejs_eventloop_lag_p99_seconds",type:"gauge",help:"99th percentile event loop lag over the last interval.",value:t(histogram.percentile(99))}];return histogram.reset(),o}histogram.enable();const LATENCY_BUCKETS=[.005,.01,.025,.05,.1,.25,.5,1,2.5,5,10];let httpTotal=0,httpDurationSum=0;const httpBuckets=new Array(LATENCY_BUCKETS.length+1).fill(0);function recordHttpRequest(e){const t=e/MS_PER_SECOND;httpTotal++,httpDurationSum+=t;for(let e=0;e<LATENCY_BUCKETS.length;e++)t<=LATENCY_BUCKETS[e]&&httpBuckets[e]++;httpBuckets[LATENCY_BUCKETS.length]++}function collectHttp(){const e=LATENCY_BUCKETS.map((e,t)=>({le:e,count:httpBuckets[t]}));return e.push({le:1/0,count:httpBuckets[LATENCY_BUCKETS.length]}),[{name:"http_requests_total",type:"counter",help:"Requests recorded since the process started.",value:httpTotal},{name:"http_request_duration_seconds",type:"histogram",help:"Request duration in seconds.",buckets:e,sum:httpDurationSum,count:httpTotal}]}let dbChecker=null;function setDbChecker(e){dbChecker=e}function collectDbPool(){return[{name:"db_pool_connected",type:"gauge",help:"1 when the database pool reports a connection, 0 otherwise.",value:dbChecker&&dbChecker()?1:0}]}function collectLogs(e){const t=e.counts(),o="Log entries written since the process started, by level.";return[{name:"nodejs_log_entries_total",type:"counter",help:o,labels:{level:"error"},value:t.errors},{name:"nodejs_log_entries_total",type:"counter",help:o,labels:{level:"warn"},value:t.warns},{name:"nodejs_log_entries_total",type:"counter",help:o,labels:{level:"other"},value:Math.max(0,t.total-t.errors-t.warns)}]}function collectSystem(){const[e,t,o]=os__namespace.loadavg(),a="System load average.";return[{name:"nodejs_version_info",type:"gauge",help:"Node.js version and platform, always 1.",labels:{version:process.version,platform:os__namespace.platform(),arch:os__namespace.arch()},value:1},{name:"system_memory_total_bytes",type:"gauge",help:"Total system memory in bytes.",value:os__namespace.totalmem()},{name:"system_memory_free_bytes",type:"gauge",help:"Free system memory in bytes.",value:os__namespace.freemem()},{name:"process_uptime_seconds",type:"gauge",help:"Seconds since the process started.",value:process.uptime()},{name:"system_load_average",type:"gauge",help:a,labels:{period:"1m"},value:e},{name:"system_load_average",type:"gauge",help:a,labels:{period:"5m"},value:t},{name:"system_load_average",type:"gauge",help:a,labels:{period:"15m"},value:o}]}function collectMetrics(e){return[...collectMemory(),...collectCpu(),...collectHeap(),...collectHandles(),...collectModules(),...collectEventLoop(),...collectHttp(),...collectDbPool(),...collectLogs(e),...collectSystem()]}exports.collectMetrics=collectMetrics,exports.recordHttpRequest=recordHttpRequest,exports.setDbChecker=setDbChecker;
@@ -0,0 +1 @@
1
+ import{monitorEventLoopDelay}from"node:perf_hooks";import*as v8 from"v8";import*as os from"os";const NS_PER_SECOND=1e9,MS_PER_SECOND=1e3;function collectMemory(){const e=process.memoryUsage();return[{name:"process_resident_memory_bytes",type:"gauge",help:"Resident set size in bytes.",value:e.rss},{name:"nodejs_external_memory_bytes",type:"gauge",help:"Memory used by C++ objects bound to JavaScript, in bytes.",value:e.external},{name:"nodejs_array_buffers_bytes",type:"gauge",help:"Memory allocated for ArrayBuffers and SharedArrayBuffers, in bytes.",value:e.arrayBuffers}]}let prevCpuUsage=null;function collectCpu(){var e,t;const o=os.cpus();let l=0;if(prevCpuUsage&&prevCpuUsage.length===o.length){let e=0,t=0;for(let l=0;l<o.length;l++){const s=prevCpuUsage[l].times,a=o[l].times,n=s.user+s.nice+s.sys+s.idle+s.irq;e+=a.user+a.nice+a.sys+a.idle+a.irq-n,t+=a.idle-s.idle}l=e>0?1-t/e:0}return prevCpuUsage=o,[{name:"process_cpu_usage_ratio",type:"gauge",help:"Share of CPU time spent non-idle since the previous collection, 0 to 1.",value:l},{name:"nodejs_cpu_count",type:"gauge",help:"Number of logical CPUs.",value:o.length},{name:"nodejs_cpu_info",type:"gauge",help:"CPU model, always 1.",labels:{model:null!==(t=null===(e=o[0])||void 0===e?void 0:e.model)&&void 0!==t?t:"unknown"},value:1}]}function collectHeap(){const e=v8.getHeapStatistics();return[{name:"nodejs_heap_size_total_bytes",type:"gauge",help:"Total V8 heap size in bytes.",value:e.total_heap_size},{name:"nodejs_heap_size_used_bytes",type:"gauge",help:"Used V8 heap size in bytes.",value:e.used_heap_size},{name:"nodejs_heap_size_limit_bytes",type:"gauge",help:"Ceiling V8 will grow the heap to, in bytes.",value:e.heap_size_limit},{name:"nodejs_malloced_memory_bytes",type:"gauge",help:"Memory malloc'd by V8, in bytes.",value:e.malloced_memory},{name:"nodejs_native_contexts",type:"gauge",help:"Live top-level contexts. A number that only grows indicates a leak.",value:e.number_of_native_contexts},{name:"nodejs_detached_contexts",type:"gauge",help:"Contexts detached but not yet garbage collected.",value:e.number_of_detached_contexts}]}function collectHandles(){var e,t,o,l,s,a;const n=null!==(t=null===(e=process._getActiveHandles)||void 0===e?void 0:e.call(process))&&void 0!==t?t:[],r=null!==(l=null===(o=process._getActiveRequests)||void 0===o?void 0:o.call(process))&&void 0!==l?l:[];let c=0,u=0;for(const e of n){const t=null!==(a=null===(s=null==e?void 0:e.constructor)||void 0===s?void 0:s.name)&&void 0!==a?a:"";"Timeout"===t||"Timer"===t||"Immediate"===t?c++:"Socket"!==t&&"TCP"!==t&&"TLSSocket"!==t||u++}return[{name:"nodejs_active_handles",type:"gauge",help:"Active libuv handles by kind.",labels:{kind:"timer"},value:c},{name:"nodejs_active_handles",type:"gauge",help:"Active libuv handles by kind.",labels:{kind:"socket"},value:u},{name:"nodejs_active_handles",type:"gauge",help:"Active libuv handles by kind.",labels:{kind:"other"},value:n.length-c-u},{name:"nodejs_active_requests_total",type:"gauge",help:"Active libuv requests.",value:r.length}]}const TOP_MODULE_GROUPS=20;function collectModules(){var _a;try{const cache=eval("typeof require !== 'undefined' && require.cache")||{},keys=Object.keys(cache),groups=new Map;for(const e of keys){const t=e.match(/node_modules\/([^/]+)/),o=t?`node_modules/${t[1]}`:e.replace(process.cwd(),".");groups.set(o,(null!==(_a=groups.get(o))&&void 0!==_a?_a:0)+1)}const top=Array.from(groups.entries()).sort((e,t)=>t[1]-e[1]).slice(0,TOP_MODULE_GROUPS);return[{name:"nodejs_module_cache_entries",type:"gauge",help:"Entries in the CommonJS module cache.",value:keys.length},...top.map(([e,t])=>({name:"nodejs_module_cache_entries_by_group",type:"gauge",help:`Module cache entries per group, top ${TOP_MODULE_GROUPS}.`,labels:{group:e},value:t}))]}catch(e){return[{name:"nodejs_module_cache_entries",type:"gauge",help:"Entries in the CommonJS module cache.",value:0}]}}const histogram=monitorEventLoopDelay({resolution:10});function collectEventLoop(){const e=0===histogram.count,t=t=>e||!Number.isFinite(t)?0:t/NS_PER_SECOND,o=[{name:"nodejs_eventloop_lag_mean_seconds",type:"gauge",help:"Mean event loop lag over the last interval.",value:t(histogram.mean)},{name:"nodejs_eventloop_lag_min_seconds",type:"gauge",help:"Minimum event loop lag over the last interval.",value:t(histogram.min)},{name:"nodejs_eventloop_lag_max_seconds",type:"gauge",help:"Maximum event loop lag over the last interval.",value:t(histogram.max)},{name:"nodejs_eventloop_lag_p99_seconds",type:"gauge",help:"99th percentile event loop lag over the last interval.",value:t(histogram.percentile(99))}];return histogram.reset(),o}histogram.enable();const LATENCY_BUCKETS=[.005,.01,.025,.05,.1,.25,.5,1,2.5,5,10];let httpTotal=0,httpDurationSum=0;const httpBuckets=new Array(LATENCY_BUCKETS.length+1).fill(0);function recordHttpRequest(e){const t=e/MS_PER_SECOND;httpTotal++,httpDurationSum+=t;for(let e=0;e<LATENCY_BUCKETS.length;e++)t<=LATENCY_BUCKETS[e]&&httpBuckets[e]++;httpBuckets[LATENCY_BUCKETS.length]++}function collectHttp(){const e=LATENCY_BUCKETS.map((e,t)=>({le:e,count:httpBuckets[t]}));return e.push({le:1/0,count:httpBuckets[LATENCY_BUCKETS.length]}),[{name:"http_requests_total",type:"counter",help:"Requests recorded since the process started.",value:httpTotal},{name:"http_request_duration_seconds",type:"histogram",help:"Request duration in seconds.",buckets:e,sum:httpDurationSum,count:httpTotal}]}let dbChecker=null;function setDbChecker(e){dbChecker=e}function collectDbPool(){return[{name:"db_pool_connected",type:"gauge",help:"1 when the database pool reports a connection, 0 otherwise.",value:dbChecker&&dbChecker()?1:0}]}function collectLogs(e){const t=e.counts(),o="Log entries written since the process started, by level.";return[{name:"nodejs_log_entries_total",type:"counter",help:o,labels:{level:"error"},value:t.errors},{name:"nodejs_log_entries_total",type:"counter",help:o,labels:{level:"warn"},value:t.warns},{name:"nodejs_log_entries_total",type:"counter",help:o,labels:{level:"other"},value:Math.max(0,t.total-t.errors-t.warns)}]}function collectSystem(){const[e,t,o]=os.loadavg(),l="System load average.";return[{name:"nodejs_version_info",type:"gauge",help:"Node.js version and platform, always 1.",labels:{version:process.version,platform:os.platform(),arch:os.arch()},value:1},{name:"system_memory_total_bytes",type:"gauge",help:"Total system memory in bytes.",value:os.totalmem()},{name:"system_memory_free_bytes",type:"gauge",help:"Free system memory in bytes.",value:os.freemem()},{name:"process_uptime_seconds",type:"gauge",help:"Seconds since the process started.",value:process.uptime()},{name:"system_load_average",type:"gauge",help:l,labels:{period:"1m"},value:e},{name:"system_load_average",type:"gauge",help:l,labels:{period:"5m"},value:t},{name:"system_load_average",type:"gauge",help:l,labels:{period:"15m"},value:o}]}function collectMetrics(e){return[...collectMemory(),...collectCpu(),...collectHeap(),...collectHandles(),...collectModules(),...collectEventLoop(),...collectHttp(),...collectDbPool(),...collectLogs(e),...collectSystem()]}export{collectMetrics,recordHttpRequest,setDbChecker};
@@ -0,0 +1,15 @@
1
+ import type { Metric } from "./types";
2
+ /**
3
+ * Renders metrics as the Prometheus text exposition format.
4
+ *
5
+ * Serve the result verbatim with `CONTENT_TYPE`; nothing else is required to
6
+ * be scrapeable.
7
+ */
8
+ /** What a `/metrics` response must declare. */
9
+ export declare const CONTENT_TYPE = "text/plain; version=0.0.4; charset=utf-8";
10
+ /**
11
+ * `# HELP` and `# TYPE` are declared once per metric name, even when several
12
+ * series share it under different labels — repeating them makes the scrape
13
+ * fail with a duplicate-metadata error.
14
+ */
15
+ export declare function render(metrics: Metric[]): string;
@@ -0,0 +1 @@
1
+ "use strict";function e(e,n){const t=Object.assign(Object.assign({},e),n),r=Object.keys(t);if(0===r.length)return"";return`{${r.map(e=>{return`${e}="${n=t[e],n.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\n/g,"\\n")}"`;var n}).join(",")}}`}function n(e){return e===1/0?"+Inf":e===-1/0?"-Inf":Number.isNaN(e)?"NaN":String(e)}function t(t){return[`${t.name}${e(t.labels)} ${n(t.value)}`]}function r(t){const r=t.buckets.map(r=>`${t.name}_bucket${e(t.labels,{le:n(r.le)})} ${n(r.count)}`);return r.push(`${t.name}_sum${e(t.labels)} ${n(t.sum)}`),r.push(`${t.name}_count${e(t.labels)} ${n(t.count)}`),r}exports.CONTENT_TYPE="text/plain; version=0.0.4; charset=utf-8",exports.render=function(e){const n=[],s=new Set;for(const a of e)s.has(a.name)||(s.add(a.name),n.push(`# HELP ${a.name} ${a.help.replace(/\n/g," ")}`),n.push(`# TYPE ${a.name} ${a.type}`)),n.push(..."histogram"===a.type?r(a):t(a));return n.length?`${n.join("\n")}\n`:""};
@@ -0,0 +1 @@
1
+ const n="text/plain; version=0.0.4; charset=utf-8";function e(n,e){const t=Object.assign(Object.assign({},n),e),a=Object.keys(t);if(0===a.length)return"";return`{${a.map(n=>{return`${n}="${e=t[n],e.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\n/g,"\\n")}"`;var e}).join(",")}}`}function t(n){return n===1/0?"+Inf":n===-1/0?"-Inf":Number.isNaN(n)?"NaN":String(n)}function a(n){return[`${n.name}${e(n.labels)} ${t(n.value)}`]}function s(n){const a=n.buckets.map(a=>`${n.name}_bucket${e(n.labels,{le:t(a.le)})} ${t(a.count)}`);return a.push(`${n.name}_sum${e(n.labels)} ${t(n.sum)}`),a.push(`${n.name}_count${e(n.labels)} ${t(n.count)}`),a}function u(n){const e=[],t=new Set;for(const u of n)t.has(u.name)||(t.add(u.name),e.push(`# HELP ${u.name} ${u.help.replace(/\n/g," ")}`),e.push(`# TYPE ${u.name} ${u.type}`)),e.push(..."histogram"===u.type?s(u):a(u));return e.length?`${e.join("\n")}\n`:""}export{n as CONTENT_TYPE,u as render};
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Syhemo: in-process runtime monitoring.
3
+ *
4
+ * Node-only, and not re-exported from the package index — reach it by
5
+ * subpath: `import { Syhemo } from "@ecosy/core/syhemo"`.
6
+ *
7
+ * Metrics follow the Prometheus model, so exposing them is a two-line route
8
+ * with no client library involved:
9
+ *
10
+ * ```ts
11
+ * export function GET() {
12
+ * return new Response(monitor.metrics(), {
13
+ * headers: { "Content-Type": CONTENT_TYPE },
14
+ * });
15
+ * }
16
+ * ```
17
+ *
18
+ * Collection runs on this class's own timer, never on a scrape, which is what
19
+ * makes that route safe to call from more than one scraper: reading never
20
+ * mutates a counter.
21
+ */
22
+ import { Subscriber } from "../subscriber";
23
+ import type { Metric, SyhemoDeps, SyhemoOptions, SyhemoState } from "./types";
24
+ export * from "./types";
25
+ export { recordHttpRequest } from "./collectors";
26
+ export { render, CONTENT_TYPE } from "./exposition";
27
+ declare const syhemoEvents: {
28
+ readonly metrics: {
29
+ readonly snapshot: "syhemo:metrics:snapshot";
30
+ };
31
+ };
32
+ type SyhemoEvents = typeof syhemoEvents;
33
+ export declare class Syhemo extends Subscriber<SyhemoState, SyhemoEvents> {
34
+ private timer;
35
+ private readonly logger;
36
+ private readonly source;
37
+ constructor(deps?: SyhemoDeps);
38
+ start(options?: SyhemoOptions): void;
39
+ stop(): void;
40
+ /** The most recent collection, or an empty list before the first one. */
41
+ getMetrics(): Metric[];
42
+ /**
43
+ * The most recent collection in Prometheus text exposition format.
44
+ *
45
+ * Serve it with `CONTENT_TYPE`. Returns an empty string before the first
46
+ * collection, which a scraper reads as "no metrics" rather than an error.
47
+ */
48
+ metrics(): string;
49
+ private collect;
50
+ }
@@ -0,0 +1 @@
1
+ "use strict";var e=require("../subscriber.js"),t=require("../utilities/freeze.js"),s=require("./logger.js"),r=require("./collectors.js"),i=require("./exposition.js");const o=t.freeze({metrics:{snapshot:"syhemo:metrics:snapshot"}});class l extends e.Subscriber{constructor(e={}){var t,r;super({current:null,snapshots:[],logs:[],started:!1},o),this.timer=null,this.logger=null!==(t=e.logger)&&void 0!==t?t:new s.BufferedLogger("Syhemo"),this.source=null!==(r=e.source)&&void 0!==r?r:s.BufferedLogger}start(e={}){if(this.getState().started)return;const{interval:t=5e3,db:s}=e;s&&r.setDbChecker(s),this.setState({started:!0}),this.collect(),this.timer=setInterval(()=>this.collect(),t),this.logger.log(`Started (interval: ${t}ms)`)}stop(){this.timer&&(clearInterval(this.timer),this.timer=null),this.setState({started:!1}),this.logger.log("Stopped")}getMetrics(){var e,t;return null!==(t=null===(e=this.getState().current)||void 0===e?void 0:e.metrics)&&void 0!==t?t:[]}metrics(){return i.render(this.getMetrics())}collect(){try{const e={timestamp:Date.now(),metrics:r.collectMetrics(this.source)},t=this.getState().snapshots,s=t.length>=60?[...t.slice(1),e]:[...t,e];this.setState({current:e,snapshots:s,logs:this.source.getLogs()}),this.dispatch(o.metrics.snapshot,e)}catch(e){this.logger.error(`Collection failed: ${e}`)}}}exports.recordHttpRequest=r.recordHttpRequest,exports.CONTENT_TYPE=i.CONTENT_TYPE,exports.render=i.render,exports.Syhemo=l;
@@ -0,0 +1 @@
1
+ import{Subscriber as t}from"../subscriber.mjs";import{freeze as s}from"../utilities/freeze.mjs";import{BufferedLogger as e}from"./logger.mjs";import{collectMetrics as r,setDbChecker as o}from"./collectors.mjs";export{recordHttpRequest}from"./collectors.mjs";import{render as i}from"./exposition.mjs";export{CONTENT_TYPE}from"./exposition.mjs";const l=s({metrics:{snapshot:"syhemo:metrics:snapshot"}});class c extends t{constructor(t={}){var s,r;super({current:null,snapshots:[],logs:[],started:!1},l),this.timer=null,this.logger=null!==(s=t.logger)&&void 0!==s?s:new e("Syhemo"),this.source=null!==(r=t.source)&&void 0!==r?r:e}start(t={}){if(this.getState().started)return;const{interval:s=5e3,db:e}=t;e&&o(e),this.setState({started:!0}),this.collect(),this.timer=setInterval(()=>this.collect(),s),this.logger.log(`Started (interval: ${s}ms)`)}stop(){this.timer&&(clearInterval(this.timer),this.timer=null),this.setState({started:!1}),this.logger.log("Stopped")}getMetrics(){var t,s;return null!==(s=null===(t=this.getState().current)||void 0===t?void 0:t.metrics)&&void 0!==s?s:[]}metrics(){return i(this.getMetrics())}collect(){try{const t={timestamp:Date.now(),metrics:r(this.source)},s=this.getState().snapshots,e=s.length>=60?[...s.slice(1),t]:[...s,t];this.setState({current:t,snapshots:e,logs:this.source.getLogs()}),this.dispatch(l.metrics.snapshot,t)}catch(t){this.logger.error(`Collection failed: ${t}`)}}}export{c as Syhemo,i as render};
@@ -12,24 +12,32 @@ export interface LogEntry {
12
12
  message: string;
13
13
  }
14
14
  /**
15
- * Isomorphic logger with color-coded output.
15
+ * Syhemo's built-in logging stack: colour-coded output plus the shared buffer
16
+ * and counters the monitor reads.
16
17
  *
17
- * - **Server (Node.js)**: Uses ANSI escape codes for terminal coloring.
18
- * - **Browser**: Uses `%c` CSS formatting for DevTools console.
18
+ * This is NOT a general-purpose logger, and it is not re-exported from the
19
+ * package index reach for `@ecosy/logger` for formatters, deliveries and
20
+ * telemetry standards. What lives here exists so that {@link Syhemo} works out
21
+ * of the box: it is one concrete implementation of the `SyhemoLogger` and
22
+ * `SyhemoLogSource` ports, and Syhemo accepts any other.
19
23
  *
20
- * All log entries are buffered in a shared `globalThis` store so that
21
- * {@link Syhemo} can collect them for monitoring.
24
+ * An app that wants its own logs to appear in monitoring snapshots has to
25
+ * write them through something that fills the same buffer — either this class,
26
+ * or its own implementation of `SyhemoLogSource` handed to Syhemo.
27
+ *
28
+ * - **Server (Node.js)**: ANSI escape codes for terminal colour.
29
+ * - **Browser**: `%c` CSS formatting for DevTools.
22
30
  *
23
31
  * @example
24
32
  * ```ts
25
- * const logger = new Logger("MyModule");
33
+ * const logger = new BufferedLogger("MyModule");
26
34
  * logger.log("Server started on port 3000");
27
35
  * logger.warn("Deprecated API used");
28
36
  * logger.error("Connection failed", error);
29
37
  * logger.debug("Payload:", data);
30
38
  * ```
31
39
  */
32
- export declare class Logger {
40
+ export declare class BufferedLogger {
33
41
  private readonly context;
34
42
  /**
35
43
  * @param context - Label shown in log output, e.g. `[MyModule]`.
@@ -51,14 +59,18 @@ export declare class Logger {
51
59
  verbose(message: string, ...args: unknown[]): void;
52
60
  /**
53
61
  * Returns a shallow copy of all buffered log entries (non-destructive).
54
- * Used by {@link Syhemo} to collect logs for monitoring snapshots.
62
+ * Satisfies the `getLogs` half of Syhemo's log source port.
55
63
  */
56
64
  static getLogs(): LogEntry[];
57
65
  /**
58
- * Returns the current error, warn, and total log counts, then resets
59
- * the counters to zero. Used by {@link Syhemo} for per-interval log rate tracking.
66
+ * Running totals since the process started.
67
+ *
68
+ * Non-destructive on purpose: these become Prometheus counters, and a
69
+ * counter that resets when it is read cannot be shared by two consumers and
70
+ * cannot produce a correct rate. The scraper takes the difference between
71
+ * two reads; that is its job, not this one's.
60
72
  */
61
- static drainCounts(): {
73
+ static counts(): {
62
74
  errors: number;
63
75
  warns: number;
64
76
  total: number;
@@ -0,0 +1 @@
1
+ "use strict";const o=()=>"undefined"==typeof window,t="",r="",e="",n="",s={LOG:"color:#22c55e;font-weight:bold",WARN:"color:#eab308;font-weight:bold",ERROR:"color:#ef4444;font-weight:bold",DEBUG:"color:#a855f7;font-weight:bold",VERBOSE:"color:#06b6d4;font-weight:bold"},c="color:inherit;font-weight:normal",i={LOG:"color:#22c55e",WARN:"color:#eab308",ERROR:"color:#ef4444",DEBUG:"color:#a855f7",VERBOSE:"color:#06b6d4"};function a(){return(new Date).toLocaleString("en-US",{month:"2-digit",day:"2-digit",year:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!0})}const f={LOG:"",WARN:e,ERROR:"",DEBUG:"",VERBOSE:""},l=Symbol.for("@ecosy:logger"),m=globalThis;m[l]||(m[l]={buffer:[],errors:0,warns:0,total:0});const R=m[l];exports.BufferedLogger=class{constructor(o="@ecosy"){this.context=o}format(t,r,...e){const n="ERROR"===t?console.error:"WARN"===t?console.warn:console.log;R.buffer.push({timestamp:Date.now(),level:t,context:this.context,message:r}),R.buffer.length>200&&R.buffer.shift(),R.total++,"ERROR"===t&&R.errors++,"WARN"===t&&R.warns++,o()?this.formatServer(n,t,r,...e):this.formatBrowser(n,t,r,...e)}formatServer(s,c,i,...l){const m=f[c],R=`${n}${a()}${t}`;s(`${`${m}${r}${c.padEnd(7)}${t}`} ${`${e}${o()&&"undefined"!=typeof process?process.pid:0}${t}`} - ${R} ${`${e}[${this.context}]${t}`} ${`${m}${i}${t}`}`,...l)}formatBrowser(o,t,r,...e){o(`%c${t.padEnd(7)}%c - %c${a()}%c %c[${this.context}]%c %c${r}`,s[t],c,"color:#9ca3af;font-weight:normal",c,"color:#eab308;font-weight:normal",c,i[t],...e)}log(o,...t){this.format("LOG",o,...t)}warn(o,...t){this.format("WARN",o,...t)}error(o,...t){this.format("ERROR",o,...t)}debug(o,...t){this.format("DEBUG",o,...t)}verbose(o,...t){this.format("VERBOSE",o,...t)}static getLogs(){return[...R.buffer]}static counts(){return{errors:R.errors,warns:R.warns,total:R.total}}};
@@ -1 +1 @@
1
- const o=()=>"undefined"==typeof window,t="",r="",e="",n="",s={LOG:"color:#22c55e;font-weight:bold",WARN:"color:#eab308;font-weight:bold",ERROR:"color:#ef4444;font-weight:bold",DEBUG:"color:#a855f7;font-weight:bold",VERBOSE:"color:#06b6d4;font-weight:bold"},c="color:inherit;font-weight:normal",a={LOG:"color:#22c55e",WARN:"color:#eab308",ERROR:"color:#ef4444",DEBUG:"color:#a855f7",VERBOSE:"color:#06b6d4"};function i(){return(new Date).toLocaleString("en-US",{month:"2-digit",day:"2-digit",year:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!0})}const f={LOG:"",WARN:e,ERROR:"",DEBUG:"",VERBOSE:""},l=Symbol.for("@ecosy:logger"),m=globalThis;m[l]||(m[l]={buffer:[],errors:0,warns:0,total:0});const R=m[l];class h{constructor(o="@ecosy"){this.context=o}format(t,r,...e){const n="ERROR"===t?console.error:"WARN"===t?console.warn:console.log;R.buffer.push({timestamp:Date.now(),level:t,context:this.context,message:r}),R.buffer.length>200&&R.buffer.shift(),R.total++,"ERROR"===t&&R.errors++,"WARN"===t&&R.warns++,o()?this.formatServer(n,t,r,...e):this.formatBrowser(n,t,r,...e)}formatServer(s,c,a,...l){const m=f[c],R=`${n}${i()}${t}`;s(`${`${m}${r}${c.padEnd(7)}${t}`} ${`${e}${o()&&"undefined"!=typeof process?process.pid:0}${t}`} - ${R} ${`${e}[${this.context}]${t}`} ${`${m}${a}${t}`}`,...l)}formatBrowser(o,t,r,...e){o(`%c${t.padEnd(7)}%c - %c${i()}%c %c[${this.context}]%c %c${r}`,s[t],c,"color:#9ca3af;font-weight:normal",c,"color:#eab308;font-weight:normal",c,a[t],...e)}log(o,...t){this.format("LOG",o,...t)}warn(o,...t){this.format("WARN",o,...t)}error(o,...t){this.format("ERROR",o,...t)}debug(o,...t){this.format("DEBUG",o,...t)}verbose(o,...t){this.format("VERBOSE",o,...t)}static getLogs(){return[...R.buffer]}static drainCounts(){const o={errors:R.errors,warns:R.warns,total:R.total};return R.errors=0,R.warns=0,R.total=0,o}}export{h as Logger};
1
+ const o=()=>"undefined"==typeof window,t="",r="",e="",n="",c={LOG:"color:#22c55e;font-weight:bold",WARN:"color:#eab308;font-weight:bold",ERROR:"color:#ef4444;font-weight:bold",DEBUG:"color:#a855f7;font-weight:bold",VERBOSE:"color:#06b6d4;font-weight:bold"},s="color:inherit;font-weight:normal",i={LOG:"color:#22c55e",WARN:"color:#eab308",ERROR:"color:#ef4444",DEBUG:"color:#a855f7",VERBOSE:"color:#06b6d4"};function a(){return(new Date).toLocaleString("en-US",{month:"2-digit",day:"2-digit",year:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!0})}const f={LOG:"",WARN:e,ERROR:"",DEBUG:"",VERBOSE:""},l=Symbol.for("@ecosy:logger"),m=globalThis;m[l]||(m[l]={buffer:[],errors:0,warns:0,total:0});const R=m[l];class h{constructor(o="@ecosy"){this.context=o}format(t,r,...e){const n="ERROR"===t?console.error:"WARN"===t?console.warn:console.log;R.buffer.push({timestamp:Date.now(),level:t,context:this.context,message:r}),R.buffer.length>200&&R.buffer.shift(),R.total++,"ERROR"===t&&R.errors++,"WARN"===t&&R.warns++,o()?this.formatServer(n,t,r,...e):this.formatBrowser(n,t,r,...e)}formatServer(c,s,i,...l){const m=f[s],R=`${n}${a()}${t}`;c(`${`${m}${r}${s.padEnd(7)}${t}`} ${`${e}${o()&&"undefined"!=typeof process?process.pid:0}${t}`} - ${R} ${`${e}[${this.context}]${t}`} ${`${m}${i}${t}`}`,...l)}formatBrowser(o,t,r,...e){o(`%c${t.padEnd(7)}%c - %c${a()}%c %c[${this.context}]%c %c${r}`,c[t],s,"color:#9ca3af;font-weight:normal",s,"color:#eab308;font-weight:normal",s,i[t],...e)}log(o,...t){this.format("LOG",o,...t)}warn(o,...t){this.format("WARN",o,...t)}error(o,...t){this.format("ERROR",o,...t)}debug(o,...t){this.format("DEBUG",o,...t)}verbose(o,...t){this.format("VERBOSE",o,...t)}static getLogs(){return[...R.buffer]}static counts(){return{errors:R.errors,warns:R.warns,total:R.total}}}export{h as BufferedLogger};
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Every shape Syhemo produces or consumes.
3
+ *
4
+ * The metric model below follows Prometheus, so a project that wants a
5
+ * `/metrics` endpoint only has to return what Syhemo already produces — no
6
+ * client library, no registry, no push gateway. That constrains three things
7
+ * which are easy to get wrong and expensive to fix later:
8
+ *
9
+ * - **Base units.** Bytes, seconds, and ratios between 0 and 1. Never MB, ms
10
+ * or percentages: a query that has to know which unit a metric used is a
11
+ * query that will eventually be wrong.
12
+ * - **Counters are monotonic.** `counter` *means* never-decreasing; a scraper
13
+ * computes rates from the difference between two scrapes. A value that
14
+ * resets itself is a gauge wearing a counter's name, and the dashboards it
15
+ * produces look plausible while being wrong.
16
+ * - **Full precision.** Values are not rounded here. Rounding is a display
17
+ * decision, and doing it at the source cannot be undone.
18
+ */
19
+ import type { LogEntry, LogLevel } from "./logger";
20
+ export type { LogEntry, LogLevel };
21
+ /**
22
+ * `counter` only ever grows. `gauge` moves in both directions. `histogram`
23
+ * carries cumulative buckets plus a sum and a count.
24
+ *
25
+ * Summaries are deliberately absent: their quantiles cannot be aggregated
26
+ * across instances, which is the whole reason histograms exist.
27
+ */
28
+ export type MetricType = "counter" | "gauge" | "histogram";
29
+ export type MetricLabels = Record<string, string>;
30
+ /** One cumulative bucket: how many observations were `<= le`. */
31
+ export interface HistogramBucket {
32
+ /** Upper bound, inclusive. `Infinity` renders as `+Inf`. */
33
+ le: number;
34
+ count: number;
35
+ }
36
+ interface MetricBase {
37
+ /** `<namespace>_<subsystem>_<name>_<unit>`, e.g. `process_resident_memory_bytes`. */
38
+ name: string;
39
+ help: string;
40
+ labels?: MetricLabels;
41
+ }
42
+ export interface ScalarMetric extends MetricBase {
43
+ type: "counter" | "gauge";
44
+ value: number;
45
+ }
46
+ export interface HistogramMetric extends MetricBase {
47
+ type: "histogram";
48
+ /** Cumulative: each bucket counts everything at or below its bound. */
49
+ buckets: HistogramBucket[];
50
+ sum: number;
51
+ count: number;
52
+ }
53
+ export type Metric = ScalarMetric | HistogramMetric;
54
+ /** One collection pass. */
55
+ export interface MetricsSnapshot {
56
+ /** Unix milliseconds. */
57
+ timestamp: number;
58
+ metrics: Metric[];
59
+ }
60
+ export interface SyhemoState {
61
+ current: MetricsSnapshot | null;
62
+ snapshots: MetricsSnapshot[];
63
+ logs: LogEntry[];
64
+ started: boolean;
65
+ }
66
+ /** Monotonic log totals since the process started. */
67
+ export interface LogCounts {
68
+ errors: number;
69
+ warns: number;
70
+ total: number;
71
+ }
72
+ /**
73
+ * Where Syhemo writes its own diagnostics.
74
+ *
75
+ * Structurally an `ILogger`, so `@ecosy/logger` — or a bare `console` — is
76
+ * already one. Declared here rather than imported because it is the port the
77
+ * monitor needs, which is what keeps this package free of any dependency on a
78
+ * logging library.
79
+ */
80
+ export interface SyhemoLogger {
81
+ log(message: string, ...args: unknown[]): void;
82
+ warn(message: string, ...args: unknown[]): void;
83
+ error(message: string, ...args: unknown[]): void;
84
+ }
85
+ /**
86
+ * Where Syhemo reads log activity from.
87
+ *
88
+ * A logging library does not normally provide this — writing logs and being
89
+ * able to count them are different jobs — which is why it is a separate port.
90
+ * `BufferedLogger` implements both; pairing `@ecosy/logger` with monitoring
91
+ * means giving it a delivery that fills something shaped like this.
92
+ */
93
+ export interface SyhemoLogSource {
94
+ /** Recent entries. Non-destructive. */
95
+ getLogs(): LogEntry[];
96
+ /**
97
+ * Running totals since the process started.
98
+ *
99
+ * Must be monotonic and non-destructive: these become counters, and a
100
+ * counter that resets when it is read cannot be shared by two consumers and
101
+ * cannot produce a correct rate.
102
+ */
103
+ counts(): LogCounts;
104
+ }
105
+ export interface SyhemoOptions {
106
+ interval?: number;
107
+ db?: () => boolean;
108
+ }
109
+ export interface SyhemoDeps {
110
+ /** Defaults to the built-in `BufferedLogger`. */
111
+ logger?: SyhemoLogger;
112
+ /** Defaults to the built-in buffer behind `BufferedLogger`. */
113
+ source?: SyhemoLogSource;
114
+ }
@@ -10,4 +10,6 @@ export { isLiteralObject, isComplexObject, isObject, isObjectable, hasOwnPropert
10
10
  export { merge } from "./merge";
11
11
  export { isFormData, objectToFormData } from "./formdata";
12
12
  export { MIME_REGEX, sanitizeMime } from "./sanitize-mime";
13
+ export { searchify, type SearchPosition, type SearchResult } from "./searchify";
14
+ export { slugify, DEFAULT_TRANSFORMER, type SlugifyOptions } from "./slugify";
13
15
  export { toString, ucfirst, pascalToKebab } from "./string";