@jeffjassky/telemetry 0.2.0 → 0.3.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.
package/dist/core.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server/registry.ts","../src/client/core.ts"],"names":["uuidv7"],"mappings":";;;;;;AAoGO,SAAS,eAAyC,KAAA,EAAa;AACpE,EAAA,OAAO,KAAA;AACT;AASO,SAAS,WAAA,GAA8D;AAC5E,EAAA,MAAM,SAAS,CAAC,CAAA,KACd,MAAM,IAAA,IAAQ,OAAO,MAAM,SAAA,IAC1B,OAAO,MAAM,QAAA,IAAY,MAAA,CAAO,SAAS,CAAC,CAAA,IAC1C,OAAO,CAAA,KAAM,QAAA,IAAY,EAAE,MAAA,IAAU,GAAA;AAExC,EAAA,MAAM,IAAA,GAAO,CAAC,CAAA,KAAwB;AACpC,IAAA,IAAI,MAAA,CAAO,CAAC,CAAA,EAAG,OAAO,IAAA;AACtB,IAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,CAAC,CAAA,EAAG,OAAO,EAAE,MAAA,IAAU,EAAA,IAAM,CAAA,CAAE,KAAA,CAAM,MAAM,CAAA;AAC7D,IAAA,IAAI,KAAK,OAAO,CAAA,KAAM,QAAA,IAAY,EAAE,aAAa,IAAA,CAAA,EAAO;AACtD,MAAA,MAAM,OAAA,GAAU,MAAA,CAAO,OAAA,CAAQ,CAA4B,CAAA;AAC3D,MAAA,OAAO,OAAA,CAAQ,MAAA,IAAU,EAAA,IAAM,OAAA,CAAQ,KAAA,CAAM,CAAC,GAAG,CAAC,CAAA,KAAM,MAAA,CAAO,CAAC,CAAC,CAAA;AAAA,IACnE;AACA,IAAA,OAAO,KAAA;AAAA,EACT,CAAA;AAEA,EAAA,MAAM,OAAA,GAAU,CAAC,CAAA,KAA6C;AAC5D,IAAA,IAAI,CAAC,CAAA,IAAK,OAAO,CAAA,KAAM,QAAA,IAAY,KAAA,CAAM,OAAA,CAAQ,CAAC,CAAA,IAAK,CAAA,YAAa,IAAA,EAAM,OAAO,KAAA;AACjF,IAAA,MAAM,OAAA,GAAU,MAAA,CAAO,OAAA,CAAQ,CAA4B,CAAA;AAC3D,IAAA,IAAI,OAAA,CAAQ,MAAA,GAAS,EAAA,EAAI,OAAO,KAAA;AAChC,IAAA,IAAI,CAAC,OAAA,CAAQ,KAAA,CAAM,CAAC,GAAG,CAAC,CAAA,KAAM,IAAA,CAAK,CAAC,CAAC,CAAA,EAAG,OAAO,KAAA;AAC/C,IAAA,IAAI;AACF,MAAA,OAAO,IAAA,CAAK,SAAA,CAAU,CAAC,CAAA,CAAE,MAAA,IAAU,IAAA;AAAA,IACrC,CAAA,CAAA,MAAQ;AACN,MAAA,OAAO,KAAA;AAAA,IACT;AAAA,EACF,CAAA;AAEA,EAAA,OAAO,CAAA,CAAE,OAAA,EAAQ,CAAE,SAAA,CAAU,CAAC,MAAO,OAAA,CAAQ,CAAC,CAAA,GAAK,CAAA,GAAgC,MAAU,CAAA;AAC/F;;;AC3HO,IAAM,KAAA,GAAQA;AAuFrB,IAAM,GAAA,GAAM,EAAE,IAAA,EAAM,uBAAA,EAAyB,SAAS,GAAA,EAAI;AAE1D,IAAM,gBAAA,GAA8B,OAAO,GAAA,EAAK,IAAA,EAAM,OAAA,KAAY;AAChE,EAAA,MAAM,GAAA,GAAM,MAAM,KAAA,CAAM,GAAA,EAAK,EAAE,MAAA,EAAQ,MAAA,EAAQ,OAAA,EAAS,IAAA,EAAM,SAAA,EAAW,IAAA,EAAM,CAAA;AAC/E,EAAA,OAAO,EAAE,EAAA,EAAI,GAAA,CAAI,EAAA,EAAI,MAAA,EAAQ,IAAI,MAAA,EAAO;AAC1C,CAAA;AAEA,IAAM,gBAAgB,MAAqB;AACzC,EAAA,MAAM,CAAA,uBAAQ,GAAA,EAAoB;AAClC,EAAA,OAAO,EAAE,GAAA,EAAK,CAAC,CAAA,KAAM,CAAA,CAAE,IAAI,CAAC,CAAA,EAAG,GAAA,EAAK,CAAC,GAAG,CAAA,KAAM,KAAK,EAAE,GAAA,CAAI,CAAA,EAAG,CAAC,CAAA,EAAE;AACjE,CAAA;AAGA,IAAM,WAAA,GAAc,CAAC,IAAA,EAAc,OAAA,EAAiB,KAAA,KAA0B;AAC5E,EAAA,MAAM,CAAA,GAAI,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,QAAQ,OAAA,CAAQ,MAAA,EAAQ,GAAG,CAAA,CAAE,KAAA,CAAM,CAAA,EAAG,GAAG,CAAC,IAAI,KAAK,CAAA,CAAA;AACxE,EAAA,IAAI,CAAA,GAAI,IAAA;AACR,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,CAAA,CAAE,MAAA,EAAQ,CAAA,EAAA,EAAK,CAAA,GAAA,CAAM,CAAA,IAAK,CAAA,IAAK,CAAA,GAAI,CAAA,CAAE,UAAA,CAAW,CAAC,CAAA,KAAO,CAAA;AAC5E,EAAA,OAAO,CAAA,CAAE,SAAS,EAAE,CAAA;AACtB,CAAA;AAEA,IAAM,QAAA,GAAW,sDAAA;AAEjB,IAAM,WAAA,GAAc,CAAC,KAAA,KAAA,CAClB,KAAA,IAAS,EAAA,EACP,KAAA,CAAM,IAAI,CAAA,CACV,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA,CACX,GAAA,CAAI,CAAC,IAAA,KAAS,QAAA,CAAS,IAAA,CAAK,IAAI,CAAC,CAAA,CACjC,MAAA,CAAO,CAAC,CAAA,KAA4B,CAAC,CAAC,CAAC,CAAA,CACvC,GAAA,CAAI,CAAC,CAAA,MAAO;AAAA,EACX,EAAA,EAAI,EAAE,CAAC,CAAA;AAAA,EACP,QAAA,EAAU,EAAE,CAAC,CAAA;AAAA,EACb,MAAA,EAAQ,MAAA,CAAO,CAAA,CAAE,CAAC,CAAC,CAAA;AAAA,EACnB,KAAA,EAAO,MAAA,CAAO,CAAA,CAAE,CAAC,CAAC;AACpB,CAAA,CAAE,CAAA;AAEC,SAAS,aAAa,OAAA,EAA8B;AACzD,EAAA,MAAM;AAAA,IACJ,GAAA;AAAA,IACA,GAAA;AAAA,IACA,OAAA;AAAA,IACA,eAAA,GAAkB,GAAA;AAAA,IAClB,YAAA,GAAe,EAAA;AAAA,IACf,YAAA,GAAe,GAAA;AAAA,IACf,UAAA,GAAa,CAAA;AAAA,IACb,SAAA,GAAY,gBAAA;AAAA,IACZ,UAAU,aAAA,EAAc;AAAA,IACxB,UAAU,MAAM,IAAA;AAAA,IAChB,SAAA,GAAY,iBAAA;AAAA,IACZ,UAAU,MAAM;AAAA,IAAC;AAAA,GACnB,GAAI,OAAA;AAIJ,EAAA,MAAM,aAAA,GAAgB;AAAA,IACpB,QAAA,EAAU,OAAO,MAAA,KAAW,WAAA,GAAc,QAAA,GAAW,KAAA;AAAA,IACrD,YAAY,OAAA,IAAW,SAAA;AAAA,IACvB,GAAG,OAAA,CAAQ;AAAA,GACb;AAGA,EAAA,IAAI,MAAA,GAAS,OAAA,CAAQ,GAAA,CAAI,gBAAgB,CAAA,IAAK,IAAA;AAC9C,EAAA,IAAI,CAAC,MAAA,EAAQ;AACX,IAAA,MAAA,GAAS,CAAA,KAAA,EAAQ,OAAO,CAAA,CAAA;AACxB,IAAA,IAAI;AACF,MAAA,OAAA,CAAQ,GAAA,CAAI,kBAAkB,MAAM,CAAA;AAAA,IACtC,SAAS,CAAA,EAAG;AACV,MAAA,OAAA,CAAQ,CAAC,CAAA;AAAA,IACX;AAAA,EACF;AACA,EAAA,MAAM,SAAA,GAAY,CAAA,IAAA,EAAO,KAAA,EAAO,CAAA,CAAA;AAEhC,EAAA,MAAM,QAAA,uBAAe,GAAA,CAAoB;AAAA,IACvC,CAAC,QAAQ,MAAM,CAAA;AAAA,IACf,CAAC,WAAW,SAAS;AAAA,GACtB,CAAA;AACD,EAAA,IAAI,KAAA;AAGJ,EAAA,MAAM,QAAsB,EAAC;AAC7B,EAAA,IAAI,OAAA,GAAU,CAAA;AACd,EAAA,IAAI,QAAA,GAAW,CAAA;AACf,EAAA,IAAI,QAAA,GAAiC,IAAA;AACrC,EAAA,IAAI,KAAA;AAEJ,EAAA,MAAM,OAAA,GAAU,CAAC,GAAA,KAAoB;AACnC,IAAA,KAAA,CAAM,KAAK,GAAG,CAAA;AACd,IAAA,OAAO,KAAA,CAAM,SAAS,YAAA,EAAc;AAClC,MAAA,KAAA,CAAM,KAAA,EAAM;AACZ,MAAA,OAAA,EAAA;AAAA,IACF;AAAA,EACF,CAAA;AAGA,EAAA,MAAM,YAAwD,EAAC;AAC/D,EAAA,MAAM,WAAA,GAAc,MAAM,SAAA,CAAU,SAAA,CAAU,SAAS,CAAC,CAAA;AAExD,EAAA,MAAM,UAAU,YAA2B;AACzC,IAAA,IAAI,CAAC,MAAM,MAAA,EAAQ;AACnB,IAAA,IAAI,CAAC,SAAQ,EAAG;AACd,MAAA,KAAA,CAAM,MAAA,GAAS,CAAA;AACf,MAAA;AAAA,IACF;AACA,IAAA,OAAO,MAAM,MAAA,EAAQ;AACnB,MAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,KAAA,CAAM,CAAA,EAAG,YAAY,CAAA;AACzC,MAAA,MAAM,IAAA,GAAO,KAAK,SAAA,CAAU;AAAA,QAC1B,GAAA,EAAK,GAAA;AAAA,QACL,MAAA,EAAA,iBAAQ,IAAI,IAAA,EAAK,EAAE,WAAA,EAAY;AAAA,QAC/B,OAAA;AAAA,QACA,MAAA,EAAQ,aAAA;AAAA,QACR,OAAA,EAAS;AAAA,UACP,QAAA,EAAU,CAAC,GAAG,QAAQ,EAAE,GAAA,CAAI,CAAC,CAAC,IAAA,EAAM,EAAE,CAAA,MAAO,EAAE,IAAA,EAAM,IAAG,CAAE,CAAA;AAAA,UAC1D,GAAI,KAAA,GAAQ,EAAE,KAAA,KAAU;AAAC,SAC3B;AAAA,QACA,OAAA,EAAS;AAAA,OACV,CAAA;AACD,MAAA,IAAI;AACF,QAAA,MAAM,GAAA,GAAM,MAAM,SAAA,CAAU,GAAA,EAAK,IAAA,EAAM;AAAA,UACrC,cAAA,EAAgB,kBAAA;AAAA,UAChB,aAAA,EAAe,UAAU,GAAG,CAAA;AAAA,SAC7B,CAAA;AACD,QAAA,IAAI,CAAC,IAAI,EAAA,EAAI,MAAM,IAAI,KAAA,CAAM,CAAA,OAAA,EAAU,GAAA,CAAI,MAAM,CAAA,CAAE,CAAA;AACnD,QAAA,KAAA,CAAM,MAAA,CAAO,CAAA,EAAG,KAAA,CAAM,MAAM,CAAA;AAC5B,QAAA,QAAA,GAAW,CAAA;AAAA,MACb,SAAS,CAAA,EAAG;AAEV,QAAA,QAAA,EAAA;AACA,QAAA,IAAI,YAAY,UAAA,EAAY;AAC1B,UAAA,KAAA,CAAM,MAAA,CAAO,CAAA,EAAG,KAAA,CAAM,MAAM,CAAA;AAC5B,UAAA,OAAA,IAAW,KAAA,CAAM,MAAA;AACjB,UAAA,QAAA,GAAW,CAAA;AAAA,QACb;AACA,QAAA,OAAA,CAAQ,CAAC,CAAA;AACT,QAAA;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,KAAA,GAAQ,MAAO,QAAA,KAAa,OAAA,GAAU,OAAA,CAAQ,MAAO,WAAW,IAAK,CAAA;AAE3E,EAAA,IAAI,kBAAkB,CAAA,EAAG;AACvB,IAAA,KAAA,GAAQ,WAAA,CAAY,MAAM,KAAK,KAAA,IAAS,eAAe,CAAA;AACvD,IAAC,MAAiC,KAAA,IAAQ;AAAA,EAC5C;AAEA,EAAA,MAAM,MAAA,GAAS;AAAA,IACb,KAAA,CACE,IAAA,EACA,IAAA,GAOI,EAAC,EACL;AACA,MAAA,MAAM,QAAQ,WAAA,EAAY;AAC1B,MAAA,OAAA,CAAQ;AAAA,QACN,KAAK,KAAA,EAAM;AAAA,QACX,IAAA;AAAA,QACA,aAAa,IAAA,CAAK,UAAA,oBAAc,IAAI,IAAA,IAAQ,WAAA,EAAY;AAAA,QACxD,GAAI,KAAK,KAAA,GAAQ,EAAE,OAAO,IAAA,CAAK,KAAA,KAAU,EAAC;AAAA,QAC1C,GAAI,KAAK,OAAA,GAAU,EAAE,SAAS,IAAA,CAAK,OAAA,KAAY,EAAC;AAAA,QAChD,GAAI,KAAK,IAAA,GAAO,EAAE,MAAM,IAAA,CAAK,IAAA,KAAS,EAAC;AAAA,QACvC,GAAI,KAAK,QAAA,GAAW,EAAE,UAAU,IAAA,CAAK,QAAA,KAAa,EAAC;AAAA,QACnD,GAAI,KAAK,QAAA,GAAW,EAAE,UAAU,IAAA,CAAK,QAAA,KAAa,EAAC;AAAA,QACnD,GAAI,KAAA,GAAQ,EAAE,OAAA,EAAS,KAAA,CAAM,SAAS,QAAA,EAAU,KAAA,CAAM,MAAA,EAAO,GAAI;AAAC,OACnE,CAAA;AAAA,IACH,CAAA;AAAA,IAEA,YAAA,CAAa,GAAA,EAAc,GAAA,GAA4E,EAAC,EAAG;AACzG,MAAA,MAAM,CAAA,GAAI,eAAe,KAAA,GAAQ,GAAA,GAAM,IAAI,KAAA,CAAM,MAAA,CAAO,GAAG,CAAC,CAAA;AAC5D,MAAA,MAAM,MAAA,GAAS,WAAA,CAAY,CAAA,CAAE,KAAK,CAAA;AAClC,MAAA,MAAM,QAAQ,WAAA,EAAY;AAC1B,MAAA,OAAA,CAAQ;AAAA,QACN,KAAK,KAAA,EAAM;AAAA,QACX,IAAA,EAAM,IAAI,IAAA,IAAQ,SAAA;AAAA,QAClB,UAAA,EAAA,iBAAY,IAAI,IAAA,EAAK,EAAE,WAAA,EAAY;AAAA,QACnC,QAAA,EAAU,OAAA;AAAA,QACV,GAAI,IAAI,KAAA,GAAQ,EAAE,OAAO,GAAA,CAAI,KAAA,KAAU,EAAC;AAAA,QACxC,GAAI,KAAA,GAAQ,EAAE,OAAA,EAAS,KAAA,CAAM,SAAS,QAAA,EAAU,KAAA,CAAM,MAAA,EAAO,GAAI,EAAC;AAAA,QAClE,KAAA,EAAO;AAAA,UACL,IAAA,EAAM,EAAE,IAAA,IAAQ,OAAA;AAAA,UAChB,SAAS,CAAA,CAAE,OAAA;AAAA,UACX,OAAA,EAAS,IAAI,OAAA,IAAW,IAAA;AAAA,UACxB,WAAA,EAAa,WAAA,CAAY,CAAA,CAAE,IAAA,EAAM,CAAA,CAAE,SAAS,MAAA,CAAO,CAAC,CAAA,EAAG,QAAA,IAAY,EAAE,CAAA;AAAA,UACrE;AAAA;AACF,OACD,CAAA;AAAA,IACH,CAAA;AAAA,IAEA,SAAA,CAAU,IAAA,EAAc,IAAA,GAA2C,EAAC,EAAS;AAC3E,MAAA,MAAM,SAAS,WAAA,EAAY;AAC3B,MAAA,MAAM,OAAA,GAAU,MAAA,EAAQ,OAAA,IAAW,KAAA,EAAM;AACzC,MAAA,MAAM,SAAS,KAAA,EAAM;AACrB,MAAA,MAAM,SAAA,GAAY,KAAK,GAAA,EAAI;AAC3B,MAAA,MAAM,KAAA,GAAQ,EAAE,OAAA,EAAS,MAAA,EAAO;AAChC,MAAA,SAAA,CAAU,KAAK,KAAK,CAAA;AACpB,MAAA,IAAI,KAAA,GAAQ,KAAA;AACZ,MAAA,OAAO;AAAA,QACL,OAAA;AAAA,QACA,MAAA;AAAA,QACA,GAAA,EAAK,CAAC,KAAA,GAAQ,EAAC,KAAM;AACnB,UAAA,IAAI,KAAA,EAAO;AACX,UAAA,KAAA,GAAQ,IAAA;AACR,UAAA,MAAM,CAAA,GAAI,SAAA,CAAU,OAAA,CAAQ,KAAK,CAAA;AACjC,UAAA,IAAI,CAAA,IAAK,CAAA,EAAG,SAAA,CAAU,MAAA,CAAO,GAAG,CAAC,CAAA;AACjC,UAAA,OAAA,CAAQ;AAAA,YACN,KAAK,KAAA,EAAM;AAAA,YACX,IAAA;AAAA,YACA,UAAA,EAAY,IAAI,IAAA,CAAK,SAAS,EAAE,WAAA,EAAY;AAAA,YAC5C,OAAA;AAAA,YACA,MAAA;AAAA,YACA,GAAI,MAAA,GAAS,EAAE,UAAU,MAAA,CAAO,MAAA,KAAW,EAAC;AAAA,YAC5C,UAAA,EAAY,IAAA,CAAK,GAAA,EAAI,GAAI,SAAA;AAAA,YACzB,GAAI,IAAA,CAAK,KAAA,IAAS,KAAA,CAAM,KAAA,GAAQ,EAAE,KAAA,EAAO,EAAE,GAAG,IAAA,CAAK,OAAO,GAAG,KAAA,CAAM,KAAA,EAAM,KAAM,EAAC;AAAA,YAChF,GAAI,MAAM,OAAA,GAAU,EAAE,SAAS,KAAA,CAAM,OAAA,KAAY;AAAC,WACnD,CAAA;AAAA,QACH;AAAA,OACF;AAAA,IACF,CAAA;AAAA,IAEA,KAAA,CAAM,MAAc,EAAA,EAA0E;AAC5F,MAAA,OAAA,CAAQ,EAAE,GAAA,EAAK,KAAA,EAAM,EAAG,IAAA,EAAM,UAAA,EAAA,iBAAY,IAAI,IAAA,EAAK,EAAE,WAAA,EAAY,EAAG,KAAA,EAAO,IAAI,CAAA;AAAA,IACjF,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOA,SAAS,GAAA,EAAgD;AACvD,MAAA,MAAM,OAAA,GAAU,QAAA,CAAS,GAAA,CAAI,MAAM,CAAA;AACnC,MAAA,KAAA,MAAW,CAAC,IAAA,EAAM,EAAE,KAAK,MAAA,CAAO,OAAA,CAAQ,GAAG,CAAA,EAAG;AAC5C,QAAA,IAAI,EAAA,IAAM,IAAA,EAAM,QAAA,CAAS,MAAA,CAAO,IAAI,CAAA;AAAA,aAC/B,QAAA,CAAS,GAAA,CAAI,IAAA,EAAM,MAAA,CAAO,EAAE,CAAC,CAAA;AAAA,MACpC;AACA,MAAA,MAAM,IAAA,GAAO,QAAA,CAAS,GAAA,CAAI,MAAM,CAAA;AAChC,MAAA,IAAI,IAAA,IAAQ,IAAA,KAAS,OAAA,IAAW,MAAA,EAAQ;AACtC,QAAA,OAAA,CAAQ;AAAA,UACN,KAAK,KAAA,EAAM;AAAA,UACX,IAAA,EAAM,WAAA;AAAA,UACN,UAAA,EAAA,iBAAY,IAAI,IAAA,EAAK,EAAE,WAAA,EAAY;AAAA,UACnC,OAAA,EAAS,QAAQ,MAAM,CAAA,CAAA;AAAA,UACvB,OAAA,EAAS,QAAQ,IAAI,CAAA;AAAA,SACtB,CAAA;AAAA,MACH;AACA,MAAA,IAAI,QAAA,CAAS,IAAI,MAAM,CAAA,UAAW,CAAA,KAAA,EAAQ,QAAA,CAAS,GAAA,CAAI,MAAM,CAAC,CAAA,CAAA;AAAA,IAChE,CAAA;AAAA,IAEA,SAAS,GAAA,EAAyB;AAChC,MAAA,KAAA,GAAQ,GAAA;AAAA,IACV,CAAA;AAAA,IAEA,KAAA;AAAA,IAEA,MAAM,QAAA,GAAW;AACf,MAAA,IAAI,KAAA,gBAAqB,KAAK,CAAA;AAC9B,MAAA,MAAM,KAAA,EAAM;AAAA,IACd,CAAA;AAAA;AAAA,IAGA,SAAA,EAAW;AAAA,MACT,KAAA;AAAA,MACA,QAAA;AAAA,MACA,IAAI,MAAA,GAAS;AACX,QAAA,OAAO,MAAA;AAAA,MACT,CAAA;AAAA,MACA,SAAA;AAAA,MACA,IAAI,OAAA,GAAU;AACZ,QAAA,OAAO,OAAA;AAAA,MACT,CAAA;AAAA,MACA;AAAA;AACF,GACF;AAEA,EAAA,OAAO,MAAA;AACT","file":"core.js","sourcesContent":["import { z } from 'zod';\nimport { TelemetryKind, TELEMETRY_KINDS } from './types.js';\n\n/**\n * The registry is the single contract: it drives validation, TypeScript types,\n * derived rollups, and index creation. It is HOST-OWNED — the host authors it\n * in their own code and passes it to createTelemetry(); this package never\n * ships event names (schema §4.2, instrumentation §8).\n */\n\n/**\n * A dimension source. `subject` fans the rollup out over matching subject refs;\n * `attr:`/`field:` read one value off the record.\n */\nexport type DimSource = 'subject' | `attr:${string}` | `field:${string}`;\n\n/**\n * Derived aggregate maintained on write (schema §4.5). One primitive, four\n * jobs: milestones, issue grouping, windowed spend, activity/retention.\n */\nexport interface RollupSpec {\n /** rollup family — several event names may feed one. Default: the event name */\n as?: string;\n /** dimensions, in order. At most one `subject`. */\n by: readonly DimSource[];\n /** when `by` includes `subject`, restrict to these subject types */\n subjects?: readonly string[];\n /**\n * Actor TYPE allowlist — a record whose `actor` has a type not listed here\n * skips this rollup; the raw row is unaffected. Absent = all actors; a record\n * with no actor always passes. This is how \"admin support browsing never\n * moves a customer aggregate\" is declared rather than disciplined.\n */\n actors?: readonly string[];\n /** time bucket (UTC). Omit for a lifetime rollup — that is the classic milestone.\n * `hour` is for incident-grade dashboards; anything finer is the wrong store. */\n bucket?: 'hour' | 'day' | 'week' | 'month';\n /** metric keys accumulated with $add */\n sum?: readonly string[];\n /** dimension sources snapshotted at FIRST occurrence — cohort dimensions */\n capture?: readonly DimSource[];\n /**\n * Bucket name for a non-subject dim that resolves null/empty. Absent keeps\n * the default: skip the record and count it in `rollupSkipped`. Present makes\n * \"no value\" an explicit, queryable group instead of a silent drop.\n *\n * The SUBJECT dim is unaffected — see the fan-out comment in rollups.ts.\n */\n dimDefault?: string;\n /** rollup TTL. Omit or null = immortal (bucketed rollups usually want a number) */\n retentionDays?: number | null;\n}\n\nexport interface EventSpec {\n kind: TelemetryKind;\n origin: 'server' | 'client' | 'any';\n /** subject TYPES that must be present (any number of ids per type) */\n subjects: readonly string[];\n /** attrs values are STRINGS after Mongoose casting — use z.string()/z.enum()/z.coerce.* */\n attrs?: z.ZodObject<any>;\n metrics?: z.ZodObject<any>;\n /**\n * `data` is UNSTORED unless declared here. Closes the erasure hole.\n *\n * Declare an OBJECT schema (or `boundedMeta()`). The type is the looser\n * `ZodType` so `boundedMeta()`'s transform fits, but `EmitInput.data` is an\n * object — a scalar schema like `z.string()` is expressible here and\n * unreachable through emit().\n */\n data?: z.ZodType<any>;\n /** attrs keys that get a real partial compound index built at boot */\n indexedAttrs?: readonly string[];\n /** metrics keys, same machinery — enables indexed numeric range queries */\n indexedMetrics?: readonly string[];\n /** derived aggregates maintained on write — funnels, issues, spend, retention */\n rollups?: readonly RollupSpec[];\n /** overrides RETENTION_DAYS[kind]. null = immortal */\n retentionDays?: number | null;\n /** overrides SAMPLE_RATE[kind]. Dormant by default — every kind ships at 1. */\n sampleRate?: number;\n /**\n * Rate cap on RAW storage per resolved key, per minute. Rollups still see\n * every record, so counts stay exact while an error storm or a client in a\n * retry loop cannot flood the collection. Omit `key` to cap the name whole.\n */\n burst?: { key?: DimSource; maxPerMinute: number };\n /**\n * Await the write with `{w:'majority', j:true}` and rethrow on failure — the\n * contract usage has always had, now declarable. For the host whose cost\n * ledger is a span: without it the only way to await was t.flush(), which\n * drains every in-flight write globally and serializes a busy worker.\n * kind=usage is durable unconditionally, with or without this flag.\n */\n durable?: boolean;\n description: string;\n}\n\nexport type Registry = Record<string, EventSpec>;\n\n/** Identity with a `const` type parameter so literal specs keep their shapes. */\nexport function defineRegistry<const R extends Registry>(specs: R): R {\n return specs;\n}\n\n/**\n * The maxed-shaped `data` escape hatch (build-plan §0.5): scalars only, ≤12\n * keys, strings ≤200 chars, one nesting level, ≤4 KB serialized. Violation\n * drops the WHOLE object — never truncates — so a stored payload is always\n * exactly what the caller sent. Parsing never throws; out-of-bounds resolves\n * to undefined and emit() counts the drop.\n */\nexport function boundedMeta(): z.ZodType<Record<string, unknown> | undefined> {\n const scalar = (v: unknown) =>\n v === null || typeof v === 'boolean' ||\n (typeof v === 'number' && Number.isFinite(v)) ||\n (typeof v === 'string' && v.length <= 200);\n\n const flat = (v: unknown): boolean => {\n if (scalar(v)) return true;\n if (Array.isArray(v)) return v.length <= 20 && v.every(scalar);\n if (v && typeof v === 'object' && !(v instanceof Date)) {\n const entries = Object.entries(v as Record<string, unknown>);\n return entries.length <= 12 && entries.every(([, x]) => scalar(x));\n }\n return false;\n };\n\n const bounded = (v: unknown): v is Record<string, unknown> => {\n if (!v || typeof v !== 'object' || Array.isArray(v) || v instanceof Date) return false;\n const entries = Object.entries(v as Record<string, unknown>);\n if (entries.length > 12) return false;\n if (!entries.every(([, x]) => flat(x))) return false;\n try {\n return JSON.stringify(v).length <= 4096;\n } catch {\n return false; // circular\n }\n };\n\n return z.unknown().transform((v) => (bounded(v) ? (v as Record<string, unknown>) : undefined));\n}\n\nconst DIM_RE = /^(subject|attr:.+|field:.+)$/;\n\n/**\n * Boot-time registry validation — misconfiguration fails deploy, not\n * dashboards. Covers per-spec sanity AND the cross-spec family-shape rule:\n * two events feeding one `as` with different `by` shapes produce docs whose\n * dims array means different things at the same position — silently\n * unqueryable (schema §4.5).\n */\nexport function validateRegistry(registry: Registry): void {\n const shapes = new Map<string, string>();\n\n for (const [name, spec] of Object.entries(registry)) {\n const fail = (msg: string) => {\n throw new Error(`telemetry: registry \"${name}\": ${msg}`);\n };\n\n if (!TELEMETRY_KINDS.includes(spec.kind)) fail(`unknown kind \"${spec.kind}\"`);\n if (!['server', 'client', 'any'].includes(spec.origin)) fail(`unknown origin \"${spec.origin}\"`);\n if (!Array.isArray(spec.subjects)) fail('`subjects` must be an array of subject types');\n if (spec.sampleRate != null && !(spec.sampleRate > 0 && spec.sampleRate <= 1)) {\n fail(`sampleRate ${spec.sampleRate} outside (0, 1]`);\n }\n if (spec.burst && !(spec.burst.maxPerMinute > 0)) fail('burst.maxPerMinute must be positive');\n if (spec.burst?.key && !DIM_RE.test(spec.burst.key)) fail(`bad burst key \"${spec.burst.key}\"`);\n for (const k of spec.indexedAttrs ?? []) {\n if (!spec.attrs || !(k in spec.attrs.shape)) fail(`indexedAttrs \"${k}\" not declared in attrs`);\n }\n for (const k of spec.indexedMetrics ?? []) {\n if (!spec.metrics || !(k in spec.metrics.shape)) fail(`indexedMetrics \"${k}\" not declared in metrics`);\n }\n\n for (const r of spec.rollups ?? []) {\n if (!r.by?.length) fail('rollup has empty `by`');\n for (const d of r.by) if (!DIM_RE.test(d)) fail(`bad dim source \"${d}\"`);\n if (r.by.filter((d) => d === 'subject').length > 1) {\n fail('rollup has more than one subject dim');\n }\n if (r.by.includes('subject') && !r.subjects?.length) {\n fail('rollup uses subject without `subjects`');\n }\n if (r.actors && !r.actors.length) fail('rollup `actors` must be non-empty when present');\n if (r.dimDefault !== undefined) {\n // dims are `label=value` and the rollup `_id` joins them on `|`, so\n // either character in the bucket name corrupts the key itself\n if (typeof r.dimDefault !== 'string' || !r.dimDefault) {\n fail('rollup `dimDefault` must be a non-empty string');\n }\n if (/[|=]/.test(r.dimDefault)) fail(`rollup dimDefault \"${r.dimDefault}\" may not contain \"|\" or \"=\"`);\n }\n\n const as = r.as ?? name;\n // shape = dims + bucket + subject classes: any mismatch makes docs in one\n // family mean different things at the same array position (or triggers\n // the null-join inflation of schema §5.4), so all three pin per family.\n // dimDefault is NOT part of the shape — it changes which bucket a record\n // lands in, never what a position MEANS, so two names may feed one family\n // with different fallbacks.\n const shape =\n r.by.join(',') + '|' + (r.bucket ?? '') + '|' + [...(r.subjects ?? [])].sort().join(',');\n const seen = shapes.get(as);\n if (seen && seen !== shape) {\n throw new Error(`telemetry: rollup family \"${as}\" declared with two shapes: ${seen} / ${shape}`);\n }\n shapes.set(as, shape);\n }\n }\n}\n","import { uuidv7 } from 'uuidv7';\n\ndeclare const window: unknown;\n\n/**\n * The isomorphic SDK core (instrumentation §6) — ~80% of every client by\n * value. Zero heavy dependencies; platform entries are thin wiring over this.\n *\n * The wire contract it upholds:\n * - every record carries a client-generated UUIDv7 `_id` (the idempotency\n * contract — transports are at-least-once)\n * - trace ids are UUIDv7 too: schema §2.6 samples on the random hex tail\n * - batch context carries subjects/actor once, not per record\n * - `identify()` swaps subjects AND posts one `$identify` control record —\n * the input to the server-side stitching job\n */\n\nexport const newId = uuidv7;\n\nexport interface ClientContextInput {\n platform?: string;\n appVersion?: string;\n userAgent?: string;\n os?: string;\n osVersion?: string;\n browser?: string;\n browserVersion?: string;\n deviceType?: string;\n locale?: string;\n timezone?: string;\n screenW?: number;\n screenH?: number;\n viewportW?: number;\n viewportH?: number;\n connection?: string;\n online?: boolean;\n}\n\nexport interface TransportResult {\n ok: boolean;\n status?: number;\n}\n\nexport type Transport = (url: string, body: string, headers: Record<string, string>) => Promise<TransportResult>;\n\nexport interface ClientStorage {\n get(key: string): string | null | undefined;\n set(key: string, value: string): void;\n}\n\nexport interface WireRecord {\n _id: string;\n name: string;\n occurredAt: string;\n attrs?: Record<string, string>;\n metrics?: Record<string, number>;\n data?: Record<string, unknown>;\n body?: string;\n severity?: string;\n subjects?: Array<{ type: string; id: string; role?: string }>;\n actor?: string;\n traceId?: string;\n spanId?: string;\n parentId?: string;\n durationMs?: number;\n error?: unknown;\n state?: unknown;\n usage?: unknown;\n anonRef?: string;\n userRef?: string;\n}\n\nexport interface CreateClientOptions {\n /** pk_ for anything shipped to users; sk_ only in trusted processes */\n key: string;\n /** the mounted ingest endpoint, e.g. https://app.example.com/telemetry/ingest */\n url: string;\n /** injected at build; the key supplies service/env */\n release?: string;\n flushIntervalMs?: number;\n /** records per POST */\n maxBatchSize?: number;\n /** ring buffer cap — drop-OLDEST beyond it; new telemetry outranks stale */\n maxQueueSize?: number;\n /** failed flushes retry with backoff; a batch is abandoned after this many */\n maxRetries?: number;\n transport?: Transport;\n /** persists the anon id (and lets platforms persist queues); memory default */\n storage?: ClientStorage;\n clientContext?: ClientContextInput;\n /** false = drop instead of send. Web adapter wires DNT/GPC/consent here. */\n consent?: () => boolean;\n /** registry name used by captureError. Convention: 'error.unhandled'. */\n errorName?: string;\n /** SDK-internal failures — never thrown at the app */\n onError?: (e: unknown) => void;\n}\n\nexport interface Span {\n readonly traceId: string;\n readonly spanId: string;\n end(extra?: { attrs?: Record<string, string>; metrics?: Record<string, number> }): void;\n}\n\nconst SDK = { name: '@jeffjassky/telemetry', version: '0' };\n\nconst defaultTransport: Transport = async (url, body, headers) => {\n const res = await fetch(url, { method: 'POST', headers, body, keepalive: true });\n return { ok: res.ok, status: res.status };\n};\n\nconst memoryStorage = (): ClientStorage => {\n const m = new Map<string, string>();\n return { get: (k) => m.get(k), set: (k, v) => void m.set(k, v) };\n};\n\n/** stable, cheap error grouping — server-side fingerprinting can refine later */\nconst fingerprint = (type: string, message: string, frame: string): string => {\n const s = `${type}|${message.replace(/\\d+/g, 'N').slice(0, 200)}|${frame}`;\n let h = 5381;\n for (let i = 0; i < s.length; i++) h = ((h << 5) + h + s.charCodeAt(i)) >>> 0;\n return h.toString(16);\n};\n\nconst FRAME_RE = /^\\s*at\\s+(?:(.+?)\\s+\\()?(?:(.+?):(\\d+):(\\d+))\\)?\\s*$/;\n\nconst parseFrames = (stack: string | undefined) =>\n (stack ?? '')\n .split('\\n')\n .slice(1, 21)\n .map((line) => FRAME_RE.exec(line))\n .filter((m): m is RegExpExecArray => !!m)\n .map((m) => ({\n fn: m[1],\n filename: m[2],\n lineno: Number(m[3]),\n colno: Number(m[4]),\n }));\n\nexport function createClient(options: CreateClientOptions) {\n const {\n key,\n url,\n release,\n flushIntervalMs = 5000,\n maxBatchSize = 50,\n maxQueueSize = 1000,\n maxRetries = 5,\n transport = defaultTransport,\n storage = memoryStorage(),\n consent = () => true,\n errorName = 'error.unhandled',\n onError = () => {},\n } = options;\n\n // ClientContext must always be storable — platform/appVersion are required\n // by the envelope, so the core guarantees them and adapters refine them\n const clientContext = {\n platform: typeof window === 'undefined' ? 'server' : 'web',\n appVersion: release ?? 'unknown',\n ...options.clientContext,\n };\n\n // ── identity context ──\n let anonId = storage.get('telemetry_anon') ?? null;\n if (!anonId) {\n anonId = `anon_${newId()}`;\n try {\n storage.set('telemetry_anon', anonId);\n } catch (e) {\n onError(e);\n }\n }\n const sessionId = `ses_${newId()}`;\n /** current subjects, by type. anon + session ride every batch (schema §2.3). */\n const subjects = new Map<string, string>([\n ['anon', anonId],\n ['session', sessionId],\n ]);\n let actor: string | undefined;\n\n // ── queue ──\n const queue: WireRecord[] = [];\n let dropped = 0;\n let attempts = 0;\n let flushing: Promise<void> | null = null;\n let timer: ReturnType<typeof setInterval> | undefined;\n\n const enqueue = (rec: WireRecord) => {\n queue.push(rec);\n while (queue.length > maxQueueSize) {\n queue.shift(); // drop-oldest: fresh telemetry outranks stale\n dropped++;\n }\n };\n\n // ── tracing ──\n const spanStack: Array<{ traceId: string; spanId: string }> = [];\n const activeTrace = () => spanStack[spanStack.length - 1];\n\n const doFlush = async (): Promise<void> => {\n if (!queue.length) return;\n if (!consent()) {\n queue.length = 0; // no consent = no send AND no hoard\n return;\n }\n while (queue.length) {\n const batch = queue.slice(0, maxBatchSize);\n const body = JSON.stringify({\n sdk: SDK,\n sentAt: new Date().toISOString(),\n release,\n client: clientContext,\n context: {\n subjects: [...subjects].map(([type, id]) => ({ type, id })),\n ...(actor ? { actor } : {}),\n },\n records: batch,\n });\n try {\n const res = await transport(url, body, {\n 'content-type': 'application/json',\n authorization: `Bearer ${key}`,\n });\n if (!res.ok) throw new Error(`ingest ${res.status}`);\n queue.splice(0, batch.length);\n attempts = 0;\n } catch (e) {\n // records stay queued; the SAME _ids retry, so the server dedupes\n attempts++;\n if (attempts >= maxRetries) {\n queue.splice(0, batch.length);\n dropped += batch.length;\n attempts = 0;\n }\n onError(e);\n return; // back off until the next interval\n }\n }\n };\n\n const flush = () => (flushing ??= doFlush().finally(() => (flushing = null)));\n\n if (flushIntervalMs > 0) {\n timer = setInterval(() => void flush(), flushIntervalMs);\n (timer as { unref?: () => void }).unref?.(); // never hold a node process open\n }\n\n const client = {\n track(\n name: string,\n opts: {\n attrs?: Record<string, string>;\n metrics?: Record<string, number>;\n data?: Record<string, unknown>;\n occurredAt?: Date;\n subjects?: Array<{ type: string; id: string; role?: string }>;\n severity?: string;\n } = {},\n ) {\n const trace = activeTrace();\n enqueue({\n _id: newId(),\n name,\n occurredAt: (opts.occurredAt ?? new Date()).toISOString(),\n ...(opts.attrs ? { attrs: opts.attrs } : {}),\n ...(opts.metrics ? { metrics: opts.metrics } : {}),\n ...(opts.data ? { data: opts.data } : {}),\n ...(opts.subjects ? { subjects: opts.subjects } : {}),\n ...(opts.severity ? { severity: opts.severity } : {}),\n ...(trace ? { traceId: trace.traceId, parentId: trace.spanId } : {}),\n });\n },\n\n captureError(err: unknown, ctx: { handled?: boolean; name?: string; attrs?: Record<string, string> } = {}) {\n const e = err instanceof Error ? err : new Error(String(err));\n const frames = parseFrames(e.stack);\n const trace = activeTrace();\n enqueue({\n _id: newId(),\n name: ctx.name ?? errorName,\n occurredAt: new Date().toISOString(),\n severity: 'error',\n ...(ctx.attrs ? { attrs: ctx.attrs } : {}),\n ...(trace ? { traceId: trace.traceId, parentId: trace.spanId } : {}),\n error: {\n type: e.name || 'Error',\n message: e.message,\n handled: ctx.handled ?? true,\n fingerprint: fingerprint(e.name, e.message, frames[0]?.filename ?? ''),\n frames,\n },\n });\n },\n\n startSpan(name: string, opts: { attrs?: Record<string, string> } = {}): Span {\n const parent = activeTrace();\n const traceId = parent?.traceId ?? newId();\n const spanId = newId();\n const startedAt = Date.now();\n const frame = { traceId, spanId };\n spanStack.push(frame);\n let ended = false;\n return {\n traceId,\n spanId,\n end: (extra = {}) => {\n if (ended) return;\n ended = true;\n const i = spanStack.indexOf(frame);\n if (i >= 0) spanStack.splice(i, 1);\n enqueue({\n _id: newId(),\n name,\n occurredAt: new Date(startedAt).toISOString(),\n traceId,\n spanId,\n ...(parent ? { parentId: parent.spanId } : {}),\n durationMs: Date.now() - startedAt,\n ...(opts.attrs || extra.attrs ? { attrs: { ...opts.attrs, ...extra.attrs } } : {}),\n ...(extra.metrics ? { metrics: extra.metrics } : {}),\n });\n },\n };\n },\n\n state(name: string, st: { key: string; from?: string; to: string; previousSinceMs?: number }) {\n enqueue({ _id: newId(), name, occurredAt: new Date().toISOString(), state: st });\n },\n\n /**\n * Swap in the real identity. Future batches carry the new subjects; one\n * `$identify` control record links anon → user for the stitching job.\n * The anon subject stays on the context — stitching consumes the alias.\n */\n identify(ids: Record<string, string | null | undefined>) {\n const hadUser = subjects.get('user');\n for (const [type, id] of Object.entries(ids)) {\n if (id == null) subjects.delete(type);\n else subjects.set(type, String(id));\n }\n const user = subjects.get('user');\n if (user && user !== hadUser && anonId) {\n enqueue({\n _id: newId(),\n name: '$identify',\n occurredAt: new Date().toISOString(),\n anonRef: `anon:${anonId}`,\n userRef: `user:${user}`,\n });\n }\n if (subjects.has('user')) actor = `user:${subjects.get('user')}`;\n },\n\n setActor(ref: string | undefined) {\n actor = ref;\n },\n\n flush,\n\n async shutdown() {\n if (timer) clearInterval(timer);\n await flush();\n },\n\n /** introspection for adapters and tests — not a public contract */\n _internal: {\n queue,\n subjects,\n get anonId() {\n return anonId!;\n },\n sessionId,\n get dropped() {\n return dropped;\n },\n enqueue,\n },\n };\n\n return client;\n}\n\nexport type TelemetryClient = ReturnType<typeof createClient>;\n\n// The isomorphic registry surface (instrumentation §8): the host's registry\n// file imports defineRegistry from '/core' so server AND clients can import\n// it — server at runtime, clients `import type` only, so zod never reaches a\n// bundle. This chain must stay mongoose/express-free.\nexport { defineRegistry, boundedMeta } from '../server/registry.js';\nexport type { Registry, EventSpec, RollupSpec, DimSource } from '../server/registry.js';\n"]}
1
+ {"version":3,"sources":["../src/server/registry.ts","../src/client/core.ts"],"names":["uuidv7"],"mappings":"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","file":"core.js","sourcesContent":["import { z } from 'zod';\nimport { TelemetryKind, TELEMETRY_KINDS } from './types.js';\n\n/**\n * The registry is the single contract: it drives validation, TypeScript types,\n * derived rollups, and index creation. It is HOST-OWNED — the host authors it\n * in their own code and passes it to createTelemetry(); this package never\n * ships event names (schema §4.2, instrumentation §8).\n */\n\n/**\n * A dimension source. `subject` fans the rollup out over matching subject refs;\n * `attr:`/`field:` read one value off the record.\n */\nexport type DimSource = 'subject' | `attr:${string}` | `field:${string}`;\n\n/**\n * Derived aggregate maintained on write (schema §4.5). One primitive, four\n * jobs: milestones, issue grouping, windowed spend, activity/retention.\n */\nexport interface RollupSpec {\n /** rollup family — several event names may feed one. Default: the event name */\n as?: string;\n /** dimensions, in order. At most one `subject`. */\n by: readonly DimSource[];\n /** when `by` includes `subject`, restrict to these subject types */\n subjects?: readonly string[];\n /**\n * Actor TYPE allowlist — a record whose `actor` has a type not listed here\n * skips this rollup; the raw row is unaffected. Absent = all actors; a record\n * with no actor always passes. This is how \"admin support browsing never\n * moves a customer aggregate\" is declared rather than disciplined.\n */\n actors?: readonly string[];\n /** time bucket (UTC). Omit for a lifetime rollup — that is the classic milestone.\n * `hour` is for incident-grade dashboards; anything finer is the wrong store. */\n bucket?: 'hour' | 'day' | 'week' | 'month';\n /** metric keys accumulated with $add */\n sum?: readonly string[];\n /** dimension sources snapshotted at FIRST occurrence — cohort dimensions */\n capture?: readonly DimSource[];\n /**\n * Bucket name for a non-subject dim that resolves null/empty. Absent keeps\n * the default: skip the record and count it in `rollupSkipped`. Present makes\n * \"no value\" an explicit, queryable group instead of a silent drop.\n *\n * The SUBJECT dim is unaffected — see the fan-out comment in rollups.ts.\n */\n dimDefault?: string;\n /** rollup TTL. Omit or null = immortal (bucketed rollups usually want a number) */\n retentionDays?: number | null;\n}\n\nexport interface EventSpec {\n kind: TelemetryKind;\n origin: 'server' | 'client' | 'any';\n /** subject TYPES that must be present (any number of ids per type) */\n subjects: readonly string[];\n /** attrs values are STRINGS after Mongoose casting — use z.string()/z.enum()/z.coerce.* */\n attrs?: z.ZodObject<any>;\n metrics?: z.ZodObject<any>;\n /**\n * `data` is UNSTORED unless declared here. Closes the erasure hole.\n *\n * Declare an OBJECT schema (or `boundedMeta()`). The type is the looser\n * `ZodType` so `boundedMeta()`'s transform fits, but `EmitInput.data` is an\n * object — a scalar schema like `z.string()` is expressible here and\n * unreachable through emit().\n */\n data?: z.ZodType<any>;\n /** attrs keys that get a real partial compound index built at boot */\n indexedAttrs?: readonly string[];\n /** metrics keys, same machinery — enables indexed numeric range queries */\n indexedMetrics?: readonly string[];\n /** derived aggregates maintained on write — funnels, issues, spend, retention */\n rollups?: readonly RollupSpec[];\n /** overrides RETENTION_DAYS[kind]. null = immortal */\n retentionDays?: number | null;\n /** overrides SAMPLE_RATE[kind]. Dormant by default — every kind ships at 1. */\n sampleRate?: number;\n /**\n * Rate cap on RAW storage per resolved key, per minute. Rollups still see\n * every record, so counts stay exact while an error storm or a client in a\n * retry loop cannot flood the collection. Omit `key` to cap the name whole.\n */\n burst?: { key?: DimSource; maxPerMinute: number };\n /**\n * Await the write with `{w:'majority', j:true}` and rethrow on failure — the\n * contract usage has always had, now declarable. For the host whose cost\n * ledger is a span: without it the only way to await was t.flush(), which\n * drains every in-flight write globally and serializes a busy worker.\n * kind=usage is durable unconditionally, with or without this flag.\n */\n durable?: boolean;\n description: string;\n}\n\nexport type Registry = Record<string, EventSpec>;\n\n/** Identity with a `const` type parameter so literal specs keep their shapes. */\nexport function defineRegistry<const R extends Registry>(specs: R): R {\n return specs;\n}\n\n/**\n * The maxed-shaped `data` escape hatch (build-plan §0.5): scalars only, ≤12\n * keys, strings ≤200 chars, one nesting level, ≤4 KB serialized. Violation\n * drops the WHOLE object — never truncates — so a stored payload is always\n * exactly what the caller sent. Parsing never throws; out-of-bounds resolves\n * to undefined and emit() counts the drop.\n */\nexport function boundedMeta(): z.ZodType<Record<string, unknown> | undefined> {\n const scalar = (v: unknown) =>\n v === null || typeof v === 'boolean' ||\n (typeof v === 'number' && Number.isFinite(v)) ||\n (typeof v === 'string' && v.length <= 200);\n\n const flat = (v: unknown): boolean => {\n if (scalar(v)) return true;\n if (Array.isArray(v)) return v.length <= 20 && v.every(scalar);\n if (v && typeof v === 'object' && !(v instanceof Date)) {\n const entries = Object.entries(v as Record<string, unknown>);\n return entries.length <= 12 && entries.every(([, x]) => scalar(x));\n }\n return false;\n };\n\n const bounded = (v: unknown): v is Record<string, unknown> => {\n if (!v || typeof v !== 'object' || Array.isArray(v) || v instanceof Date) return false;\n const entries = Object.entries(v as Record<string, unknown>);\n if (entries.length > 12) return false;\n if (!entries.every(([, x]) => flat(x))) return false;\n try {\n return JSON.stringify(v).length <= 4096;\n } catch {\n return false; // circular\n }\n };\n\n return z.unknown().transform((v) => (bounded(v) ? (v as Record<string, unknown>) : undefined));\n}\n\nconst DIM_RE = /^(subject|attr:.+|field:.+)$/;\n\n/**\n * Boot-time registry validation — misconfiguration fails deploy, not\n * dashboards. Covers per-spec sanity AND the cross-spec family-shape rule:\n * two events feeding one `as` with different `by` shapes produce docs whose\n * dims array means different things at the same position — silently\n * unqueryable (schema §4.5).\n */\nexport function validateRegistry(registry: Registry): void {\n const shapes = new Map<string, string>();\n\n for (const [name, spec] of Object.entries(registry)) {\n const fail = (msg: string) => {\n throw new Error(`telemetry: registry \"${name}\": ${msg}`);\n };\n\n if (!TELEMETRY_KINDS.includes(spec.kind)) fail(`unknown kind \"${spec.kind}\"`);\n if (!['server', 'client', 'any'].includes(spec.origin)) fail(`unknown origin \"${spec.origin}\"`);\n if (!Array.isArray(spec.subjects)) fail('`subjects` must be an array of subject types');\n if (spec.sampleRate != null && !(spec.sampleRate > 0 && spec.sampleRate <= 1)) {\n fail(`sampleRate ${spec.sampleRate} outside (0, 1]`);\n }\n if (spec.burst && !(spec.burst.maxPerMinute > 0)) fail('burst.maxPerMinute must be positive');\n if (spec.burst?.key && !DIM_RE.test(spec.burst.key)) fail(`bad burst key \"${spec.burst.key}\"`);\n for (const k of spec.indexedAttrs ?? []) {\n if (!spec.attrs || !(k in spec.attrs.shape)) fail(`indexedAttrs \"${k}\" not declared in attrs`);\n }\n for (const k of spec.indexedMetrics ?? []) {\n if (!spec.metrics || !(k in spec.metrics.shape)) fail(`indexedMetrics \"${k}\" not declared in metrics`);\n }\n\n for (const r of spec.rollups ?? []) {\n if (!r.by?.length) fail('rollup has empty `by`');\n for (const d of r.by) if (!DIM_RE.test(d)) fail(`bad dim source \"${d}\"`);\n if (r.by.filter((d) => d === 'subject').length > 1) {\n fail('rollup has more than one subject dim');\n }\n if (r.by.includes('subject') && !r.subjects?.length) {\n fail('rollup uses subject without `subjects`');\n }\n if (r.actors && !r.actors.length) fail('rollup `actors` must be non-empty when present');\n if (r.dimDefault !== undefined) {\n // dims are `label=value` and the rollup `_id` joins them on `|`, so\n // either character in the bucket name corrupts the key itself\n if (typeof r.dimDefault !== 'string' || !r.dimDefault) {\n fail('rollup `dimDefault` must be a non-empty string');\n }\n if (/[|=]/.test(r.dimDefault)) fail(`rollup dimDefault \"${r.dimDefault}\" may not contain \"|\" or \"=\"`);\n }\n\n const as = r.as ?? name;\n // shape = dims + bucket + subject classes: any mismatch makes docs in one\n // family mean different things at the same array position (or triggers\n // the null-join inflation of schema §5.4), so all three pin per family.\n // dimDefault is NOT part of the shape — it changes which bucket a record\n // lands in, never what a position MEANS, so two names may feed one family\n // with different fallbacks.\n const shape =\n r.by.join(',') + '|' + (r.bucket ?? '') + '|' + [...(r.subjects ?? [])].sort().join(',');\n const seen = shapes.get(as);\n if (seen && seen !== shape) {\n throw new Error(`telemetry: rollup family \"${as}\" declared with two shapes: ${seen} / ${shape}`);\n }\n shapes.set(as, shape);\n }\n }\n}\n","import { uuidv7 } from 'uuidv7';\n\ndeclare const window: unknown;\n\n/**\n * The isomorphic SDK core (instrumentation §6) — ~80% of every client by\n * value. Zero heavy dependencies; platform entries are thin wiring over this.\n *\n * The wire contract it upholds:\n * - every record carries a client-generated UUIDv7 `_id` (the idempotency\n * contract — transports are at-least-once)\n * - trace ids are UUIDv7 too: schema §2.6 samples on the random hex tail\n * - batch context carries subjects/actor once, not per record\n * - `identify()` swaps subjects AND posts one `$identify` control record —\n * the input to the server-side stitching job\n */\n\nexport const newId = uuidv7;\n\nexport interface ClientContextInput {\n platform?: string;\n appVersion?: string;\n userAgent?: string;\n os?: string;\n osVersion?: string;\n browser?: string;\n browserVersion?: string;\n deviceType?: string;\n locale?: string;\n timezone?: string;\n screenW?: number;\n screenH?: number;\n viewportW?: number;\n viewportH?: number;\n connection?: string;\n online?: boolean;\n}\n\nexport interface TransportResult {\n ok: boolean;\n status?: number;\n}\n\nexport type Transport = (url: string, body: string, headers: Record<string, string>) => Promise<TransportResult>;\n\nexport interface ClientStorage {\n get(key: string): string | null | undefined;\n set(key: string, value: string): void;\n}\n\nexport interface WireRecord {\n _id: string;\n name: string;\n occurredAt: string;\n attrs?: Record<string, string>;\n metrics?: Record<string, number>;\n data?: Record<string, unknown>;\n body?: string;\n severity?: string;\n subjects?: Array<{ type: string; id: string; role?: string }>;\n actor?: string;\n traceId?: string;\n spanId?: string;\n parentId?: string;\n durationMs?: number;\n error?: unknown;\n state?: unknown;\n usage?: unknown;\n anonRef?: string;\n userRef?: string;\n}\n\nexport interface CreateClientOptions {\n /** pk_ for anything shipped to users; sk_ only in trusted processes */\n key: string;\n /** the mounted ingest endpoint, e.g. https://app.example.com/telemetry/ingest */\n url: string;\n /** injected at build; the key supplies service/env */\n release?: string;\n flushIntervalMs?: number;\n /** records per POST */\n maxBatchSize?: number;\n /** ring buffer cap — drop-OLDEST beyond it; new telemetry outranks stale */\n maxQueueSize?: number;\n /** failed flushes retry with backoff; a batch is abandoned after this many */\n maxRetries?: number;\n transport?: Transport;\n /** persists the anon id (and lets platforms persist queues); memory default */\n storage?: ClientStorage;\n clientContext?: ClientContextInput;\n /** false = drop instead of send. Web adapter wires DNT/GPC/consent here. */\n consent?: () => boolean;\n /** registry name used by captureError. Convention: 'error.unhandled'. */\n errorName?: string;\n /**\n * Last gate before a record joins the queue — the one place every kind\n * (event, error, span, state, usage) passes through. Return the record to\n * keep it, a modified copy to redact it, or `null` to drop it silently.\n *\n * This is where noise dies. A host that filters at the platform level\n * instead — swallowing `window.onerror` before the SDK sees it — has to win\n * a listener-registration race to do it, which is a real footgun; the whole\n * point of putting the hook here is that the host never has to think about\n * ordering.\n *\n * A throwing hook DROPS the record and reports via `onError`. Fail-closed is\n * deliberate: the common use is redaction, and a half-applied redaction that\n * still ships is worse than a lost record.\n */\n beforeSend?: (rec: WireRecord) => WireRecord | null | undefined | void;\n /** SDK-internal failures — never thrown at the app */\n onError?: (e: unknown) => void;\n}\n\nexport interface Span {\n readonly traceId: string;\n readonly spanId: string;\n end(extra?: { attrs?: Record<string, string>; metrics?: Record<string, number> }): void;\n}\n\nconst SDK = { name: '@jeffjassky/telemetry', version: '0' };\n\nconst defaultTransport: Transport = async (url, body, headers) => {\n const res = await fetch(url, { method: 'POST', headers, body, keepalive: true });\n return { ok: res.ok, status: res.status };\n};\n\nconst memoryStorage = (): ClientStorage => {\n const m = new Map<string, string>();\n return { get: (k) => m.get(k), set: (k, v) => void m.set(k, v) };\n};\n\n/** stable, cheap error grouping — server-side fingerprinting can refine later */\nconst fingerprint = (type: string, message: string, frame: string): string => {\n const s = `${type}|${message.replace(/\\d+/g, 'N').slice(0, 200)}|${frame}`;\n let h = 5381;\n for (let i = 0; i < s.length; i++) h = ((h << 5) + h + s.charCodeAt(i)) >>> 0;\n return h.toString(16);\n};\n\nconst FRAME_RE = /^\\s*at\\s+(?:(.+?)\\s+\\()?(?:(.+?):(\\d+):(\\d+))\\)?\\s*$/;\n\nconst parseFrames = (stack: string | undefined) =>\n (stack ?? '')\n .split('\\n')\n .slice(1, 21)\n .map((line) => FRAME_RE.exec(line))\n .filter((m): m is RegExpExecArray => !!m)\n .map((m) => ({\n fn: m[1],\n filename: m[2],\n lineno: Number(m[3]),\n colno: Number(m[4]),\n }));\n\nexport function createClient(options: CreateClientOptions) {\n const {\n key,\n url,\n release,\n flushIntervalMs = 5000,\n maxBatchSize = 50,\n maxQueueSize = 1000,\n maxRetries = 5,\n transport = defaultTransport,\n storage = memoryStorage(),\n consent = () => true,\n errorName = 'error.unhandled',\n beforeSend,\n onError = () => {},\n } = options;\n\n // ClientContext must always be storable — platform/appVersion are required\n // by the envelope, so the core guarantees them and adapters refine them\n const clientContext = {\n platform: typeof window === 'undefined' ? 'server' : 'web',\n appVersion: release ?? 'unknown',\n ...options.clientContext,\n };\n\n // ── identity context ──\n let anonId = storage.get('telemetry_anon') ?? null;\n if (!anonId) {\n anonId = `anon_${newId()}`;\n try {\n storage.set('telemetry_anon', anonId);\n } catch (e) {\n onError(e);\n }\n }\n const sessionId = `ses_${newId()}`;\n /** current subjects, by type. anon + session ride every batch (schema §2.3). */\n const subjects = new Map<string, string>([\n ['anon', anonId],\n ['session', sessionId],\n ]);\n let actor: string | undefined;\n\n // ── queue ──\n const queue: WireRecord[] = [];\n let dropped = 0;\n let attempts = 0;\n let flushing: Promise<void> | null = null;\n let timer: ReturnType<typeof setInterval> | undefined;\n\n const enqueue = (rec: WireRecord) => {\n let out: WireRecord | null | undefined | void = rec;\n if (beforeSend) {\n try {\n out = beforeSend(rec);\n } catch (e) {\n onError(e); // fail-closed: a broken filter must not leak what it was filtering\n return;\n }\n }\n if (!out) return;\n queue.push(out);\n while (queue.length > maxQueueSize) {\n queue.shift(); // drop-oldest: fresh telemetry outranks stale\n dropped++;\n }\n };\n\n // ── tracing ──\n const spanStack: Array<{ traceId: string; spanId: string }> = [];\n const activeTrace = () => spanStack[spanStack.length - 1];\n\n const doFlush = async (): Promise<void> => {\n if (!queue.length) return;\n if (!consent()) {\n queue.length = 0; // no consent = no send AND no hoard\n return;\n }\n while (queue.length) {\n const batch = queue.slice(0, maxBatchSize);\n const body = JSON.stringify({\n sdk: SDK,\n sentAt: new Date().toISOString(),\n release,\n client: clientContext,\n context: {\n subjects: [...subjects].map(([type, id]) => ({ type, id })),\n ...(actor ? { actor } : {}),\n },\n records: batch,\n });\n try {\n const res = await transport(url, body, {\n 'content-type': 'application/json',\n authorization: `Bearer ${key}`,\n });\n if (!res.ok) throw new Error(`ingest ${res.status}`);\n queue.splice(0, batch.length);\n attempts = 0;\n } catch (e) {\n // records stay queued; the SAME _ids retry, so the server dedupes\n attempts++;\n if (attempts >= maxRetries) {\n queue.splice(0, batch.length);\n dropped += batch.length;\n attempts = 0;\n }\n onError(e);\n return; // back off until the next interval\n }\n }\n };\n\n const flush = () => (flushing ??= doFlush().finally(() => (flushing = null)));\n\n if (flushIntervalMs > 0) {\n timer = setInterval(() => void flush(), flushIntervalMs);\n (timer as { unref?: () => void }).unref?.(); // never hold a node process open\n }\n\n const client = {\n track(\n name: string,\n opts: {\n attrs?: Record<string, string>;\n metrics?: Record<string, number>;\n data?: Record<string, unknown>;\n occurredAt?: Date;\n subjects?: Array<{ type: string; id: string; role?: string }>;\n severity?: string;\n } = {},\n ) {\n const trace = activeTrace();\n enqueue({\n _id: newId(),\n name,\n occurredAt: (opts.occurredAt ?? new Date()).toISOString(),\n ...(opts.attrs ? { attrs: opts.attrs } : {}),\n ...(opts.metrics ? { metrics: opts.metrics } : {}),\n ...(opts.data ? { data: opts.data } : {}),\n ...(opts.subjects ? { subjects: opts.subjects } : {}),\n ...(opts.severity ? { severity: opts.severity } : {}),\n ...(trace ? { traceId: trace.traceId, parentId: trace.spanId } : {}),\n });\n },\n\n captureError(err: unknown, ctx: { handled?: boolean; name?: string; attrs?: Record<string, string> } = {}) {\n const e = err instanceof Error ? err : new Error(String(err));\n const frames = parseFrames(e.stack);\n const trace = activeTrace();\n enqueue({\n _id: newId(),\n name: ctx.name ?? errorName,\n occurredAt: new Date().toISOString(),\n severity: 'error',\n ...(ctx.attrs ? { attrs: ctx.attrs } : {}),\n ...(trace ? { traceId: trace.traceId, parentId: trace.spanId } : {}),\n error: {\n type: e.name || 'Error',\n message: e.message,\n handled: ctx.handled ?? true,\n fingerprint: fingerprint(e.name, e.message, frames[0]?.filename ?? ''),\n frames,\n },\n });\n },\n\n startSpan(name: string, opts: { attrs?: Record<string, string> } = {}): Span {\n const parent = activeTrace();\n const traceId = parent?.traceId ?? newId();\n const spanId = newId();\n const startedAt = Date.now();\n const frame = { traceId, spanId };\n spanStack.push(frame);\n let ended = false;\n return {\n traceId,\n spanId,\n end: (extra = {}) => {\n if (ended) return;\n ended = true;\n const i = spanStack.indexOf(frame);\n if (i >= 0) spanStack.splice(i, 1);\n enqueue({\n _id: newId(),\n name,\n occurredAt: new Date(startedAt).toISOString(),\n traceId,\n spanId,\n ...(parent ? { parentId: parent.spanId } : {}),\n durationMs: Date.now() - startedAt,\n ...(opts.attrs || extra.attrs ? { attrs: { ...opts.attrs, ...extra.attrs } } : {}),\n ...(extra.metrics ? { metrics: extra.metrics } : {}),\n });\n },\n };\n },\n\n state(name: string, st: { key: string; from?: string; to: string; previousSinceMs?: number }) {\n enqueue({ _id: newId(), name, occurredAt: new Date().toISOString(), state: st });\n },\n\n /**\n * Swap in the real identity. Future batches carry the new subjects; one\n * `$identify` control record links anon → user for the stitching job.\n * The anon subject stays on the context — stitching consumes the alias.\n */\n identify(ids: Record<string, string | null | undefined>) {\n const hadUser = subjects.get('user');\n for (const [type, id] of Object.entries(ids)) {\n if (id == null) subjects.delete(type);\n else subjects.set(type, String(id));\n }\n const user = subjects.get('user');\n if (user && user !== hadUser && anonId) {\n enqueue({\n _id: newId(),\n name: '$identify',\n occurredAt: new Date().toISOString(),\n anonRef: `anon:${anonId}`,\n userRef: `user:${user}`,\n });\n }\n if (subjects.has('user')) actor = `user:${subjects.get('user')}`;\n },\n\n setActor(ref: string | undefined) {\n actor = ref;\n },\n\n flush,\n\n async shutdown() {\n if (timer) clearInterval(timer);\n await flush();\n },\n\n /** introspection for adapters and tests — not a public contract */\n _internal: {\n queue,\n subjects,\n get anonId() {\n return anonId!;\n },\n sessionId,\n get dropped() {\n return dropped;\n },\n enqueue,\n },\n };\n\n return client;\n}\n\nexport type TelemetryClient = ReturnType<typeof createClient>;\n\n// The isomorphic registry surface (instrumentation §8): the host's registry\n// file imports defineRegistry from '/core' so server AND clients can import\n// it — server at runtime, clients `import type` only, so zod never reaches a\n// bundle. This chain must stay mongoose/express-free.\nexport { defineRegistry, boundedMeta } from '../server/registry.js';\nexport type { Registry, EventSpec, RollupSpec, DimSource } from '../server/registry.js';\n"]}
package/dist/electron.cjs CHANGED
@@ -42,6 +42,7 @@ function createClient(options) {
42
42
  storage = memoryStorage(),
43
43
  consent = () => true,
44
44
  errorName = "error.unhandled",
45
+ beforeSend,
45
46
  onError = () => {
46
47
  }
47
48
  } = options;
@@ -71,7 +72,17 @@ function createClient(options) {
71
72
  let flushing = null;
72
73
  let timer;
73
74
  const enqueue = (rec) => {
74
- queue.push(rec);
75
+ let out = rec;
76
+ if (beforeSend) {
77
+ try {
78
+ out = beforeSend(rec);
79
+ } catch (e) {
80
+ onError(e);
81
+ return;
82
+ }
83
+ }
84
+ if (!out) return;
85
+ queue.push(out);
75
86
  while (queue.length > maxQueueSize) {
76
87
  queue.shift();
77
88
  dropped++;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client/core.ts","../src/client/electron.ts"],"names":["uuidv7"],"mappings":"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","file":"electron.cjs","sourcesContent":["import { uuidv7 } from 'uuidv7';\n\ndeclare const window: unknown;\n\n/**\n * The isomorphic SDK core (instrumentation §6) — ~80% of every client by\n * value. Zero heavy dependencies; platform entries are thin wiring over this.\n *\n * The wire contract it upholds:\n * - every record carries a client-generated UUIDv7 `_id` (the idempotency\n * contract — transports are at-least-once)\n * - trace ids are UUIDv7 too: schema §2.6 samples on the random hex tail\n * - batch context carries subjects/actor once, not per record\n * - `identify()` swaps subjects AND posts one `$identify` control record —\n * the input to the server-side stitching job\n */\n\nexport const newId = uuidv7;\n\nexport interface ClientContextInput {\n platform?: string;\n appVersion?: string;\n userAgent?: string;\n os?: string;\n osVersion?: string;\n browser?: string;\n browserVersion?: string;\n deviceType?: string;\n locale?: string;\n timezone?: string;\n screenW?: number;\n screenH?: number;\n viewportW?: number;\n viewportH?: number;\n connection?: string;\n online?: boolean;\n}\n\nexport interface TransportResult {\n ok: boolean;\n status?: number;\n}\n\nexport type Transport = (url: string, body: string, headers: Record<string, string>) => Promise<TransportResult>;\n\nexport interface ClientStorage {\n get(key: string): string | null | undefined;\n set(key: string, value: string): void;\n}\n\nexport interface WireRecord {\n _id: string;\n name: string;\n occurredAt: string;\n attrs?: Record<string, string>;\n metrics?: Record<string, number>;\n data?: Record<string, unknown>;\n body?: string;\n severity?: string;\n subjects?: Array<{ type: string; id: string; role?: string }>;\n actor?: string;\n traceId?: string;\n spanId?: string;\n parentId?: string;\n durationMs?: number;\n error?: unknown;\n state?: unknown;\n usage?: unknown;\n anonRef?: string;\n userRef?: string;\n}\n\nexport interface CreateClientOptions {\n /** pk_ for anything shipped to users; sk_ only in trusted processes */\n key: string;\n /** the mounted ingest endpoint, e.g. https://app.example.com/telemetry/ingest */\n url: string;\n /** injected at build; the key supplies service/env */\n release?: string;\n flushIntervalMs?: number;\n /** records per POST */\n maxBatchSize?: number;\n /** ring buffer cap — drop-OLDEST beyond it; new telemetry outranks stale */\n maxQueueSize?: number;\n /** failed flushes retry with backoff; a batch is abandoned after this many */\n maxRetries?: number;\n transport?: Transport;\n /** persists the anon id (and lets platforms persist queues); memory default */\n storage?: ClientStorage;\n clientContext?: ClientContextInput;\n /** false = drop instead of send. Web adapter wires DNT/GPC/consent here. */\n consent?: () => boolean;\n /** registry name used by captureError. Convention: 'error.unhandled'. */\n errorName?: string;\n /** SDK-internal failures — never thrown at the app */\n onError?: (e: unknown) => void;\n}\n\nexport interface Span {\n readonly traceId: string;\n readonly spanId: string;\n end(extra?: { attrs?: Record<string, string>; metrics?: Record<string, number> }): void;\n}\n\nconst SDK = { name: '@jeffjassky/telemetry', version: '0' };\n\nconst defaultTransport: Transport = async (url, body, headers) => {\n const res = await fetch(url, { method: 'POST', headers, body, keepalive: true });\n return { ok: res.ok, status: res.status };\n};\n\nconst memoryStorage = (): ClientStorage => {\n const m = new Map<string, string>();\n return { get: (k) => m.get(k), set: (k, v) => void m.set(k, v) };\n};\n\n/** stable, cheap error grouping — server-side fingerprinting can refine later */\nconst fingerprint = (type: string, message: string, frame: string): string => {\n const s = `${type}|${message.replace(/\\d+/g, 'N').slice(0, 200)}|${frame}`;\n let h = 5381;\n for (let i = 0; i < s.length; i++) h = ((h << 5) + h + s.charCodeAt(i)) >>> 0;\n return h.toString(16);\n};\n\nconst FRAME_RE = /^\\s*at\\s+(?:(.+?)\\s+\\()?(?:(.+?):(\\d+):(\\d+))\\)?\\s*$/;\n\nconst parseFrames = (stack: string | undefined) =>\n (stack ?? '')\n .split('\\n')\n .slice(1, 21)\n .map((line) => FRAME_RE.exec(line))\n .filter((m): m is RegExpExecArray => !!m)\n .map((m) => ({\n fn: m[1],\n filename: m[2],\n lineno: Number(m[3]),\n colno: Number(m[4]),\n }));\n\nexport function createClient(options: CreateClientOptions) {\n const {\n key,\n url,\n release,\n flushIntervalMs = 5000,\n maxBatchSize = 50,\n maxQueueSize = 1000,\n maxRetries = 5,\n transport = defaultTransport,\n storage = memoryStorage(),\n consent = () => true,\n errorName = 'error.unhandled',\n onError = () => {},\n } = options;\n\n // ClientContext must always be storable — platform/appVersion are required\n // by the envelope, so the core guarantees them and adapters refine them\n const clientContext = {\n platform: typeof window === 'undefined' ? 'server' : 'web',\n appVersion: release ?? 'unknown',\n ...options.clientContext,\n };\n\n // ── identity context ──\n let anonId = storage.get('telemetry_anon') ?? null;\n if (!anonId) {\n anonId = `anon_${newId()}`;\n try {\n storage.set('telemetry_anon', anonId);\n } catch (e) {\n onError(e);\n }\n }\n const sessionId = `ses_${newId()}`;\n /** current subjects, by type. anon + session ride every batch (schema §2.3). */\n const subjects = new Map<string, string>([\n ['anon', anonId],\n ['session', sessionId],\n ]);\n let actor: string | undefined;\n\n // ── queue ──\n const queue: WireRecord[] = [];\n let dropped = 0;\n let attempts = 0;\n let flushing: Promise<void> | null = null;\n let timer: ReturnType<typeof setInterval> | undefined;\n\n const enqueue = (rec: WireRecord) => {\n queue.push(rec);\n while (queue.length > maxQueueSize) {\n queue.shift(); // drop-oldest: fresh telemetry outranks stale\n dropped++;\n }\n };\n\n // ── tracing ──\n const spanStack: Array<{ traceId: string; spanId: string }> = [];\n const activeTrace = () => spanStack[spanStack.length - 1];\n\n const doFlush = async (): Promise<void> => {\n if (!queue.length) return;\n if (!consent()) {\n queue.length = 0; // no consent = no send AND no hoard\n return;\n }\n while (queue.length) {\n const batch = queue.slice(0, maxBatchSize);\n const body = JSON.stringify({\n sdk: SDK,\n sentAt: new Date().toISOString(),\n release,\n client: clientContext,\n context: {\n subjects: [...subjects].map(([type, id]) => ({ type, id })),\n ...(actor ? { actor } : {}),\n },\n records: batch,\n });\n try {\n const res = await transport(url, body, {\n 'content-type': 'application/json',\n authorization: `Bearer ${key}`,\n });\n if (!res.ok) throw new Error(`ingest ${res.status}`);\n queue.splice(0, batch.length);\n attempts = 0;\n } catch (e) {\n // records stay queued; the SAME _ids retry, so the server dedupes\n attempts++;\n if (attempts >= maxRetries) {\n queue.splice(0, batch.length);\n dropped += batch.length;\n attempts = 0;\n }\n onError(e);\n return; // back off until the next interval\n }\n }\n };\n\n const flush = () => (flushing ??= doFlush().finally(() => (flushing = null)));\n\n if (flushIntervalMs > 0) {\n timer = setInterval(() => void flush(), flushIntervalMs);\n (timer as { unref?: () => void }).unref?.(); // never hold a node process open\n }\n\n const client = {\n track(\n name: string,\n opts: {\n attrs?: Record<string, string>;\n metrics?: Record<string, number>;\n data?: Record<string, unknown>;\n occurredAt?: Date;\n subjects?: Array<{ type: string; id: string; role?: string }>;\n severity?: string;\n } = {},\n ) {\n const trace = activeTrace();\n enqueue({\n _id: newId(),\n name,\n occurredAt: (opts.occurredAt ?? new Date()).toISOString(),\n ...(opts.attrs ? { attrs: opts.attrs } : {}),\n ...(opts.metrics ? { metrics: opts.metrics } : {}),\n ...(opts.data ? { data: opts.data } : {}),\n ...(opts.subjects ? { subjects: opts.subjects } : {}),\n ...(opts.severity ? { severity: opts.severity } : {}),\n ...(trace ? { traceId: trace.traceId, parentId: trace.spanId } : {}),\n });\n },\n\n captureError(err: unknown, ctx: { handled?: boolean; name?: string; attrs?: Record<string, string> } = {}) {\n const e = err instanceof Error ? err : new Error(String(err));\n const frames = parseFrames(e.stack);\n const trace = activeTrace();\n enqueue({\n _id: newId(),\n name: ctx.name ?? errorName,\n occurredAt: new Date().toISOString(),\n severity: 'error',\n ...(ctx.attrs ? { attrs: ctx.attrs } : {}),\n ...(trace ? { traceId: trace.traceId, parentId: trace.spanId } : {}),\n error: {\n type: e.name || 'Error',\n message: e.message,\n handled: ctx.handled ?? true,\n fingerprint: fingerprint(e.name, e.message, frames[0]?.filename ?? ''),\n frames,\n },\n });\n },\n\n startSpan(name: string, opts: { attrs?: Record<string, string> } = {}): Span {\n const parent = activeTrace();\n const traceId = parent?.traceId ?? newId();\n const spanId = newId();\n const startedAt = Date.now();\n const frame = { traceId, spanId };\n spanStack.push(frame);\n let ended = false;\n return {\n traceId,\n spanId,\n end: (extra = {}) => {\n if (ended) return;\n ended = true;\n const i = spanStack.indexOf(frame);\n if (i >= 0) spanStack.splice(i, 1);\n enqueue({\n _id: newId(),\n name,\n occurredAt: new Date(startedAt).toISOString(),\n traceId,\n spanId,\n ...(parent ? { parentId: parent.spanId } : {}),\n durationMs: Date.now() - startedAt,\n ...(opts.attrs || extra.attrs ? { attrs: { ...opts.attrs, ...extra.attrs } } : {}),\n ...(extra.metrics ? { metrics: extra.metrics } : {}),\n });\n },\n };\n },\n\n state(name: string, st: { key: string; from?: string; to: string; previousSinceMs?: number }) {\n enqueue({ _id: newId(), name, occurredAt: new Date().toISOString(), state: st });\n },\n\n /**\n * Swap in the real identity. Future batches carry the new subjects; one\n * `$identify` control record links anon → user for the stitching job.\n * The anon subject stays on the context — stitching consumes the alias.\n */\n identify(ids: Record<string, string | null | undefined>) {\n const hadUser = subjects.get('user');\n for (const [type, id] of Object.entries(ids)) {\n if (id == null) subjects.delete(type);\n else subjects.set(type, String(id));\n }\n const user = subjects.get('user');\n if (user && user !== hadUser && anonId) {\n enqueue({\n _id: newId(),\n name: '$identify',\n occurredAt: new Date().toISOString(),\n anonRef: `anon:${anonId}`,\n userRef: `user:${user}`,\n });\n }\n if (subjects.has('user')) actor = `user:${subjects.get('user')}`;\n },\n\n setActor(ref: string | undefined) {\n actor = ref;\n },\n\n flush,\n\n async shutdown() {\n if (timer) clearInterval(timer);\n await flush();\n },\n\n /** introspection for adapters and tests — not a public contract */\n _internal: {\n queue,\n subjects,\n get anonId() {\n return anonId!;\n },\n sessionId,\n get dropped() {\n return dropped;\n },\n enqueue,\n },\n };\n\n return client;\n}\n\nexport type TelemetryClient = ReturnType<typeof createClient>;\n\n// The isomorphic registry surface (instrumentation §8): the host's registry\n// file imports defineRegistry from '/core' so server AND clients can import\n// it — server at runtime, clients `import type` only, so zod never reaches a\n// bundle. This chain must stay mongoose/express-free.\nexport { defineRegistry, boundedMeta } from '../server/registry.js';\nexport type { Registry, EventSpec, RollupSpec, DimSource } from '../server/registry.js';\n","import { createClient, type CreateClientOptions, type TelemetryClient, type Transport } from './core.js';\n\n/**\n * Electron wiring (instrumentation §7). The shape that matters: records from\n * the RENDERER route through MAIN over IPC, so the key never reaches the\n * renderer, there is one queue, and offline behavior lives in one place.\n * Electron itself is structural-typed — nothing imports 'electron' at runtime.\n */\n\nexport const IPC_CHANNEL = 'telemetry:batch';\n\ninterface IpcMainLike {\n handle(channel: string, listener: (event: unknown, ...args: any[]) => unknown): void;\n}\n\ninterface IpcRendererLike {\n invoke(channel: string, ...args: any[]): Promise<any>;\n}\n\nexport interface MainTelemetryOptions extends CreateClientOptions {\n /** wire process-level crash handlers (default true) */\n captureProcessErrors?: boolean;\n /** pass electron's ipcMain to accept renderer batches over IPC */\n ipcMain?: IpcMainLike;\n}\n\n/** Main process: node transport, crash handlers, the single real queue. */\nexport function createMainTelemetry(opts: MainTelemetryOptions): TelemetryClient {\n const { captureProcessErrors = true, ipcMain, clientContext, ...rest } = opts;\n const client = createClient({\n ...rest,\n clientContext: { platform: 'electron', appVersion: opts.release ?? 'unknown', ...clientContext },\n });\n\n if (captureProcessErrors && typeof process !== 'undefined') {\n process.on('uncaughtException', (err) => client.captureError(err, { handled: false }));\n process.on('unhandledRejection', (reason) => client.captureError(reason, { handled: false }));\n }\n\n // renderer batches arrive as raw wire records — enqueue into the one queue\n ipcMain?.handle(IPC_CHANNEL, (_event, records: unknown) => {\n if (Array.isArray(records)) {\n for (const rec of records) {\n if (rec && typeof rec._id === 'string' && typeof rec.name === 'string') {\n client._internal.enqueue(rec);\n }\n }\n }\n return { ok: true };\n });\n\n return client;\n}\n\n/**\n * Renderer: the web-shaped client with an IPC transport. The URL/key options\n * are unused here — main owns the credential — so this takes only behavior.\n */\nexport function createRendererTelemetry(\n ipcRenderer: IpcRendererLike,\n opts: Omit<CreateClientOptions, 'key' | 'url' | 'transport'> = {},\n): TelemetryClient {\n const transport: Transport = async (_url, body) => {\n const batch = JSON.parse(body);\n await ipcRenderer.invoke(IPC_CHANNEL, batch.records);\n return { ok: true };\n };\n return createClient({\n ...opts,\n key: 'pk_ipc_tk_000000000000000000000000', // never leaves the process\n url: 'ipc://main',\n transport,\n clientContext: { platform: 'electron', appVersion: opts.release ?? 'unknown', ...opts.clientContext },\n });\n}\n\nexport { createClient } from './core.js';\nexport type { TelemetryClient } from './core.js';\n"]}
1
+ {"version":3,"sources":["../src/client/core.ts","../src/client/electron.ts"],"names":["uuidv7"],"mappings":"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","file":"electron.cjs","sourcesContent":["import { uuidv7 } from 'uuidv7';\n\ndeclare const window: unknown;\n\n/**\n * The isomorphic SDK core (instrumentation §6) — ~80% of every client by\n * value. Zero heavy dependencies; platform entries are thin wiring over this.\n *\n * The wire contract it upholds:\n * - every record carries a client-generated UUIDv7 `_id` (the idempotency\n * contract — transports are at-least-once)\n * - trace ids are UUIDv7 too: schema §2.6 samples on the random hex tail\n * - batch context carries subjects/actor once, not per record\n * - `identify()` swaps subjects AND posts one `$identify` control record —\n * the input to the server-side stitching job\n */\n\nexport const newId = uuidv7;\n\nexport interface ClientContextInput {\n platform?: string;\n appVersion?: string;\n userAgent?: string;\n os?: string;\n osVersion?: string;\n browser?: string;\n browserVersion?: string;\n deviceType?: string;\n locale?: string;\n timezone?: string;\n screenW?: number;\n screenH?: number;\n viewportW?: number;\n viewportH?: number;\n connection?: string;\n online?: boolean;\n}\n\nexport interface TransportResult {\n ok: boolean;\n status?: number;\n}\n\nexport type Transport = (url: string, body: string, headers: Record<string, string>) => Promise<TransportResult>;\n\nexport interface ClientStorage {\n get(key: string): string | null | undefined;\n set(key: string, value: string): void;\n}\n\nexport interface WireRecord {\n _id: string;\n name: string;\n occurredAt: string;\n attrs?: Record<string, string>;\n metrics?: Record<string, number>;\n data?: Record<string, unknown>;\n body?: string;\n severity?: string;\n subjects?: Array<{ type: string; id: string; role?: string }>;\n actor?: string;\n traceId?: string;\n spanId?: string;\n parentId?: string;\n durationMs?: number;\n error?: unknown;\n state?: unknown;\n usage?: unknown;\n anonRef?: string;\n userRef?: string;\n}\n\nexport interface CreateClientOptions {\n /** pk_ for anything shipped to users; sk_ only in trusted processes */\n key: string;\n /** the mounted ingest endpoint, e.g. https://app.example.com/telemetry/ingest */\n url: string;\n /** injected at build; the key supplies service/env */\n release?: string;\n flushIntervalMs?: number;\n /** records per POST */\n maxBatchSize?: number;\n /** ring buffer cap — drop-OLDEST beyond it; new telemetry outranks stale */\n maxQueueSize?: number;\n /** failed flushes retry with backoff; a batch is abandoned after this many */\n maxRetries?: number;\n transport?: Transport;\n /** persists the anon id (and lets platforms persist queues); memory default */\n storage?: ClientStorage;\n clientContext?: ClientContextInput;\n /** false = drop instead of send. Web adapter wires DNT/GPC/consent here. */\n consent?: () => boolean;\n /** registry name used by captureError. Convention: 'error.unhandled'. */\n errorName?: string;\n /**\n * Last gate before a record joins the queue — the one place every kind\n * (event, error, span, state, usage) passes through. Return the record to\n * keep it, a modified copy to redact it, or `null` to drop it silently.\n *\n * This is where noise dies. A host that filters at the platform level\n * instead — swallowing `window.onerror` before the SDK sees it — has to win\n * a listener-registration race to do it, which is a real footgun; the whole\n * point of putting the hook here is that the host never has to think about\n * ordering.\n *\n * A throwing hook DROPS the record and reports via `onError`. Fail-closed is\n * deliberate: the common use is redaction, and a half-applied redaction that\n * still ships is worse than a lost record.\n */\n beforeSend?: (rec: WireRecord) => WireRecord | null | undefined | void;\n /** SDK-internal failures — never thrown at the app */\n onError?: (e: unknown) => void;\n}\n\nexport interface Span {\n readonly traceId: string;\n readonly spanId: string;\n end(extra?: { attrs?: Record<string, string>; metrics?: Record<string, number> }): void;\n}\n\nconst SDK = { name: '@jeffjassky/telemetry', version: '0' };\n\nconst defaultTransport: Transport = async (url, body, headers) => {\n const res = await fetch(url, { method: 'POST', headers, body, keepalive: true });\n return { ok: res.ok, status: res.status };\n};\n\nconst memoryStorage = (): ClientStorage => {\n const m = new Map<string, string>();\n return { get: (k) => m.get(k), set: (k, v) => void m.set(k, v) };\n};\n\n/** stable, cheap error grouping — server-side fingerprinting can refine later */\nconst fingerprint = (type: string, message: string, frame: string): string => {\n const s = `${type}|${message.replace(/\\d+/g, 'N').slice(0, 200)}|${frame}`;\n let h = 5381;\n for (let i = 0; i < s.length; i++) h = ((h << 5) + h + s.charCodeAt(i)) >>> 0;\n return h.toString(16);\n};\n\nconst FRAME_RE = /^\\s*at\\s+(?:(.+?)\\s+\\()?(?:(.+?):(\\d+):(\\d+))\\)?\\s*$/;\n\nconst parseFrames = (stack: string | undefined) =>\n (stack ?? '')\n .split('\\n')\n .slice(1, 21)\n .map((line) => FRAME_RE.exec(line))\n .filter((m): m is RegExpExecArray => !!m)\n .map((m) => ({\n fn: m[1],\n filename: m[2],\n lineno: Number(m[3]),\n colno: Number(m[4]),\n }));\n\nexport function createClient(options: CreateClientOptions) {\n const {\n key,\n url,\n release,\n flushIntervalMs = 5000,\n maxBatchSize = 50,\n maxQueueSize = 1000,\n maxRetries = 5,\n transport = defaultTransport,\n storage = memoryStorage(),\n consent = () => true,\n errorName = 'error.unhandled',\n beforeSend,\n onError = () => {},\n } = options;\n\n // ClientContext must always be storable — platform/appVersion are required\n // by the envelope, so the core guarantees them and adapters refine them\n const clientContext = {\n platform: typeof window === 'undefined' ? 'server' : 'web',\n appVersion: release ?? 'unknown',\n ...options.clientContext,\n };\n\n // ── identity context ──\n let anonId = storage.get('telemetry_anon') ?? null;\n if (!anonId) {\n anonId = `anon_${newId()}`;\n try {\n storage.set('telemetry_anon', anonId);\n } catch (e) {\n onError(e);\n }\n }\n const sessionId = `ses_${newId()}`;\n /** current subjects, by type. anon + session ride every batch (schema §2.3). */\n const subjects = new Map<string, string>([\n ['anon', anonId],\n ['session', sessionId],\n ]);\n let actor: string | undefined;\n\n // ── queue ──\n const queue: WireRecord[] = [];\n let dropped = 0;\n let attempts = 0;\n let flushing: Promise<void> | null = null;\n let timer: ReturnType<typeof setInterval> | undefined;\n\n const enqueue = (rec: WireRecord) => {\n let out: WireRecord | null | undefined | void = rec;\n if (beforeSend) {\n try {\n out = beforeSend(rec);\n } catch (e) {\n onError(e); // fail-closed: a broken filter must not leak what it was filtering\n return;\n }\n }\n if (!out) return;\n queue.push(out);\n while (queue.length > maxQueueSize) {\n queue.shift(); // drop-oldest: fresh telemetry outranks stale\n dropped++;\n }\n };\n\n // ── tracing ──\n const spanStack: Array<{ traceId: string; spanId: string }> = [];\n const activeTrace = () => spanStack[spanStack.length - 1];\n\n const doFlush = async (): Promise<void> => {\n if (!queue.length) return;\n if (!consent()) {\n queue.length = 0; // no consent = no send AND no hoard\n return;\n }\n while (queue.length) {\n const batch = queue.slice(0, maxBatchSize);\n const body = JSON.stringify({\n sdk: SDK,\n sentAt: new Date().toISOString(),\n release,\n client: clientContext,\n context: {\n subjects: [...subjects].map(([type, id]) => ({ type, id })),\n ...(actor ? { actor } : {}),\n },\n records: batch,\n });\n try {\n const res = await transport(url, body, {\n 'content-type': 'application/json',\n authorization: `Bearer ${key}`,\n });\n if (!res.ok) throw new Error(`ingest ${res.status}`);\n queue.splice(0, batch.length);\n attempts = 0;\n } catch (e) {\n // records stay queued; the SAME _ids retry, so the server dedupes\n attempts++;\n if (attempts >= maxRetries) {\n queue.splice(0, batch.length);\n dropped += batch.length;\n attempts = 0;\n }\n onError(e);\n return; // back off until the next interval\n }\n }\n };\n\n const flush = () => (flushing ??= doFlush().finally(() => (flushing = null)));\n\n if (flushIntervalMs > 0) {\n timer = setInterval(() => void flush(), flushIntervalMs);\n (timer as { unref?: () => void }).unref?.(); // never hold a node process open\n }\n\n const client = {\n track(\n name: string,\n opts: {\n attrs?: Record<string, string>;\n metrics?: Record<string, number>;\n data?: Record<string, unknown>;\n occurredAt?: Date;\n subjects?: Array<{ type: string; id: string; role?: string }>;\n severity?: string;\n } = {},\n ) {\n const trace = activeTrace();\n enqueue({\n _id: newId(),\n name,\n occurredAt: (opts.occurredAt ?? new Date()).toISOString(),\n ...(opts.attrs ? { attrs: opts.attrs } : {}),\n ...(opts.metrics ? { metrics: opts.metrics } : {}),\n ...(opts.data ? { data: opts.data } : {}),\n ...(opts.subjects ? { subjects: opts.subjects } : {}),\n ...(opts.severity ? { severity: opts.severity } : {}),\n ...(trace ? { traceId: trace.traceId, parentId: trace.spanId } : {}),\n });\n },\n\n captureError(err: unknown, ctx: { handled?: boolean; name?: string; attrs?: Record<string, string> } = {}) {\n const e = err instanceof Error ? err : new Error(String(err));\n const frames = parseFrames(e.stack);\n const trace = activeTrace();\n enqueue({\n _id: newId(),\n name: ctx.name ?? errorName,\n occurredAt: new Date().toISOString(),\n severity: 'error',\n ...(ctx.attrs ? { attrs: ctx.attrs } : {}),\n ...(trace ? { traceId: trace.traceId, parentId: trace.spanId } : {}),\n error: {\n type: e.name || 'Error',\n message: e.message,\n handled: ctx.handled ?? true,\n fingerprint: fingerprint(e.name, e.message, frames[0]?.filename ?? ''),\n frames,\n },\n });\n },\n\n startSpan(name: string, opts: { attrs?: Record<string, string> } = {}): Span {\n const parent = activeTrace();\n const traceId = parent?.traceId ?? newId();\n const spanId = newId();\n const startedAt = Date.now();\n const frame = { traceId, spanId };\n spanStack.push(frame);\n let ended = false;\n return {\n traceId,\n spanId,\n end: (extra = {}) => {\n if (ended) return;\n ended = true;\n const i = spanStack.indexOf(frame);\n if (i >= 0) spanStack.splice(i, 1);\n enqueue({\n _id: newId(),\n name,\n occurredAt: new Date(startedAt).toISOString(),\n traceId,\n spanId,\n ...(parent ? { parentId: parent.spanId } : {}),\n durationMs: Date.now() - startedAt,\n ...(opts.attrs || extra.attrs ? { attrs: { ...opts.attrs, ...extra.attrs } } : {}),\n ...(extra.metrics ? { metrics: extra.metrics } : {}),\n });\n },\n };\n },\n\n state(name: string, st: { key: string; from?: string; to: string; previousSinceMs?: number }) {\n enqueue({ _id: newId(), name, occurredAt: new Date().toISOString(), state: st });\n },\n\n /**\n * Swap in the real identity. Future batches carry the new subjects; one\n * `$identify` control record links anon → user for the stitching job.\n * The anon subject stays on the context — stitching consumes the alias.\n */\n identify(ids: Record<string, string | null | undefined>) {\n const hadUser = subjects.get('user');\n for (const [type, id] of Object.entries(ids)) {\n if (id == null) subjects.delete(type);\n else subjects.set(type, String(id));\n }\n const user = subjects.get('user');\n if (user && user !== hadUser && anonId) {\n enqueue({\n _id: newId(),\n name: '$identify',\n occurredAt: new Date().toISOString(),\n anonRef: `anon:${anonId}`,\n userRef: `user:${user}`,\n });\n }\n if (subjects.has('user')) actor = `user:${subjects.get('user')}`;\n },\n\n setActor(ref: string | undefined) {\n actor = ref;\n },\n\n flush,\n\n async shutdown() {\n if (timer) clearInterval(timer);\n await flush();\n },\n\n /** introspection for adapters and tests — not a public contract */\n _internal: {\n queue,\n subjects,\n get anonId() {\n return anonId!;\n },\n sessionId,\n get dropped() {\n return dropped;\n },\n enqueue,\n },\n };\n\n return client;\n}\n\nexport type TelemetryClient = ReturnType<typeof createClient>;\n\n// The isomorphic registry surface (instrumentation §8): the host's registry\n// file imports defineRegistry from '/core' so server AND clients can import\n// it — server at runtime, clients `import type` only, so zod never reaches a\n// bundle. This chain must stay mongoose/express-free.\nexport { defineRegistry, boundedMeta } from '../server/registry.js';\nexport type { Registry, EventSpec, RollupSpec, DimSource } from '../server/registry.js';\n","import { createClient, type CreateClientOptions, type TelemetryClient, type Transport } from './core.js';\n\n/**\n * Electron wiring (instrumentation §7). The shape that matters: records from\n * the RENDERER route through MAIN over IPC, so the key never reaches the\n * renderer, there is one queue, and offline behavior lives in one place.\n * Electron itself is structural-typed — nothing imports 'electron' at runtime.\n */\n\nexport const IPC_CHANNEL = 'telemetry:batch';\n\ninterface IpcMainLike {\n handle(channel: string, listener: (event: unknown, ...args: any[]) => unknown): void;\n}\n\ninterface IpcRendererLike {\n invoke(channel: string, ...args: any[]): Promise<any>;\n}\n\nexport interface MainTelemetryOptions extends CreateClientOptions {\n /** wire process-level crash handlers (default true) */\n captureProcessErrors?: boolean;\n /** pass electron's ipcMain to accept renderer batches over IPC */\n ipcMain?: IpcMainLike;\n}\n\n/** Main process: node transport, crash handlers, the single real queue. */\nexport function createMainTelemetry(opts: MainTelemetryOptions): TelemetryClient {\n const { captureProcessErrors = true, ipcMain, clientContext, ...rest } = opts;\n const client = createClient({\n ...rest,\n clientContext: { platform: 'electron', appVersion: opts.release ?? 'unknown', ...clientContext },\n });\n\n if (captureProcessErrors && typeof process !== 'undefined') {\n process.on('uncaughtException', (err) => client.captureError(err, { handled: false }));\n process.on('unhandledRejection', (reason) => client.captureError(reason, { handled: false }));\n }\n\n // renderer batches arrive as raw wire records — enqueue into the one queue\n ipcMain?.handle(IPC_CHANNEL, (_event, records: unknown) => {\n if (Array.isArray(records)) {\n for (const rec of records) {\n if (rec && typeof rec._id === 'string' && typeof rec.name === 'string') {\n client._internal.enqueue(rec);\n }\n }\n }\n return { ok: true };\n });\n\n return client;\n}\n\n/**\n * Renderer: the web-shaped client with an IPC transport. The URL/key options\n * are unused here — main owns the credential — so this takes only behavior.\n */\nexport function createRendererTelemetry(\n ipcRenderer: IpcRendererLike,\n opts: Omit<CreateClientOptions, 'key' | 'url' | 'transport'> = {},\n): TelemetryClient {\n const transport: Transport = async (_url, body) => {\n const batch = JSON.parse(body);\n await ipcRenderer.invoke(IPC_CHANNEL, batch.records);\n return { ok: true };\n };\n return createClient({\n ...opts,\n key: 'pk_ipc_tk_000000000000000000000000', // never leaves the process\n url: 'ipc://main',\n transport,\n clientContext: { platform: 'electron', appVersion: opts.release ?? 'unknown', ...opts.clientContext },\n });\n}\n\nexport { createClient } from './core.js';\nexport type { TelemetryClient } from './core.js';\n"]}
package/dist/electron.js CHANGED
@@ -40,6 +40,7 @@ function createClient(options) {
40
40
  storage = memoryStorage(),
41
41
  consent = () => true,
42
42
  errorName = "error.unhandled",
43
+ beforeSend,
43
44
  onError = () => {
44
45
  }
45
46
  } = options;
@@ -69,7 +70,17 @@ function createClient(options) {
69
70
  let flushing = null;
70
71
  let timer;
71
72
  const enqueue = (rec) => {
72
- queue.push(rec);
73
+ let out = rec;
74
+ if (beforeSend) {
75
+ try {
76
+ out = beforeSend(rec);
77
+ } catch (e) {
78
+ onError(e);
79
+ return;
80
+ }
81
+ }
82
+ if (!out) return;
83
+ queue.push(out);
73
84
  while (queue.length > maxQueueSize) {
74
85
  queue.shift();
75
86
  dropped++;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client/core.ts","../src/client/electron.ts"],"names":["uuidv7"],"mappings":"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","file":"electron.js","sourcesContent":["import { uuidv7 } from 'uuidv7';\n\ndeclare const window: unknown;\n\n/**\n * The isomorphic SDK core (instrumentation §6) — ~80% of every client by\n * value. Zero heavy dependencies; platform entries are thin wiring over this.\n *\n * The wire contract it upholds:\n * - every record carries a client-generated UUIDv7 `_id` (the idempotency\n * contract — transports are at-least-once)\n * - trace ids are UUIDv7 too: schema §2.6 samples on the random hex tail\n * - batch context carries subjects/actor once, not per record\n * - `identify()` swaps subjects AND posts one `$identify` control record —\n * the input to the server-side stitching job\n */\n\nexport const newId = uuidv7;\n\nexport interface ClientContextInput {\n platform?: string;\n appVersion?: string;\n userAgent?: string;\n os?: string;\n osVersion?: string;\n browser?: string;\n browserVersion?: string;\n deviceType?: string;\n locale?: string;\n timezone?: string;\n screenW?: number;\n screenH?: number;\n viewportW?: number;\n viewportH?: number;\n connection?: string;\n online?: boolean;\n}\n\nexport interface TransportResult {\n ok: boolean;\n status?: number;\n}\n\nexport type Transport = (url: string, body: string, headers: Record<string, string>) => Promise<TransportResult>;\n\nexport interface ClientStorage {\n get(key: string): string | null | undefined;\n set(key: string, value: string): void;\n}\n\nexport interface WireRecord {\n _id: string;\n name: string;\n occurredAt: string;\n attrs?: Record<string, string>;\n metrics?: Record<string, number>;\n data?: Record<string, unknown>;\n body?: string;\n severity?: string;\n subjects?: Array<{ type: string; id: string; role?: string }>;\n actor?: string;\n traceId?: string;\n spanId?: string;\n parentId?: string;\n durationMs?: number;\n error?: unknown;\n state?: unknown;\n usage?: unknown;\n anonRef?: string;\n userRef?: string;\n}\n\nexport interface CreateClientOptions {\n /** pk_ for anything shipped to users; sk_ only in trusted processes */\n key: string;\n /** the mounted ingest endpoint, e.g. https://app.example.com/telemetry/ingest */\n url: string;\n /** injected at build; the key supplies service/env */\n release?: string;\n flushIntervalMs?: number;\n /** records per POST */\n maxBatchSize?: number;\n /** ring buffer cap — drop-OLDEST beyond it; new telemetry outranks stale */\n maxQueueSize?: number;\n /** failed flushes retry with backoff; a batch is abandoned after this many */\n maxRetries?: number;\n transport?: Transport;\n /** persists the anon id (and lets platforms persist queues); memory default */\n storage?: ClientStorage;\n clientContext?: ClientContextInput;\n /** false = drop instead of send. Web adapter wires DNT/GPC/consent here. */\n consent?: () => boolean;\n /** registry name used by captureError. Convention: 'error.unhandled'. */\n errorName?: string;\n /** SDK-internal failures — never thrown at the app */\n onError?: (e: unknown) => void;\n}\n\nexport interface Span {\n readonly traceId: string;\n readonly spanId: string;\n end(extra?: { attrs?: Record<string, string>; metrics?: Record<string, number> }): void;\n}\n\nconst SDK = { name: '@jeffjassky/telemetry', version: '0' };\n\nconst defaultTransport: Transport = async (url, body, headers) => {\n const res = await fetch(url, { method: 'POST', headers, body, keepalive: true });\n return { ok: res.ok, status: res.status };\n};\n\nconst memoryStorage = (): ClientStorage => {\n const m = new Map<string, string>();\n return { get: (k) => m.get(k), set: (k, v) => void m.set(k, v) };\n};\n\n/** stable, cheap error grouping — server-side fingerprinting can refine later */\nconst fingerprint = (type: string, message: string, frame: string): string => {\n const s = `${type}|${message.replace(/\\d+/g, 'N').slice(0, 200)}|${frame}`;\n let h = 5381;\n for (let i = 0; i < s.length; i++) h = ((h << 5) + h + s.charCodeAt(i)) >>> 0;\n return h.toString(16);\n};\n\nconst FRAME_RE = /^\\s*at\\s+(?:(.+?)\\s+\\()?(?:(.+?):(\\d+):(\\d+))\\)?\\s*$/;\n\nconst parseFrames = (stack: string | undefined) =>\n (stack ?? '')\n .split('\\n')\n .slice(1, 21)\n .map((line) => FRAME_RE.exec(line))\n .filter((m): m is RegExpExecArray => !!m)\n .map((m) => ({\n fn: m[1],\n filename: m[2],\n lineno: Number(m[3]),\n colno: Number(m[4]),\n }));\n\nexport function createClient(options: CreateClientOptions) {\n const {\n key,\n url,\n release,\n flushIntervalMs = 5000,\n maxBatchSize = 50,\n maxQueueSize = 1000,\n maxRetries = 5,\n transport = defaultTransport,\n storage = memoryStorage(),\n consent = () => true,\n errorName = 'error.unhandled',\n onError = () => {},\n } = options;\n\n // ClientContext must always be storable — platform/appVersion are required\n // by the envelope, so the core guarantees them and adapters refine them\n const clientContext = {\n platform: typeof window === 'undefined' ? 'server' : 'web',\n appVersion: release ?? 'unknown',\n ...options.clientContext,\n };\n\n // ── identity context ──\n let anonId = storage.get('telemetry_anon') ?? null;\n if (!anonId) {\n anonId = `anon_${newId()}`;\n try {\n storage.set('telemetry_anon', anonId);\n } catch (e) {\n onError(e);\n }\n }\n const sessionId = `ses_${newId()}`;\n /** current subjects, by type. anon + session ride every batch (schema §2.3). */\n const subjects = new Map<string, string>([\n ['anon', anonId],\n ['session', sessionId],\n ]);\n let actor: string | undefined;\n\n // ── queue ──\n const queue: WireRecord[] = [];\n let dropped = 0;\n let attempts = 0;\n let flushing: Promise<void> | null = null;\n let timer: ReturnType<typeof setInterval> | undefined;\n\n const enqueue = (rec: WireRecord) => {\n queue.push(rec);\n while (queue.length > maxQueueSize) {\n queue.shift(); // drop-oldest: fresh telemetry outranks stale\n dropped++;\n }\n };\n\n // ── tracing ──\n const spanStack: Array<{ traceId: string; spanId: string }> = [];\n const activeTrace = () => spanStack[spanStack.length - 1];\n\n const doFlush = async (): Promise<void> => {\n if (!queue.length) return;\n if (!consent()) {\n queue.length = 0; // no consent = no send AND no hoard\n return;\n }\n while (queue.length) {\n const batch = queue.slice(0, maxBatchSize);\n const body = JSON.stringify({\n sdk: SDK,\n sentAt: new Date().toISOString(),\n release,\n client: clientContext,\n context: {\n subjects: [...subjects].map(([type, id]) => ({ type, id })),\n ...(actor ? { actor } : {}),\n },\n records: batch,\n });\n try {\n const res = await transport(url, body, {\n 'content-type': 'application/json',\n authorization: `Bearer ${key}`,\n });\n if (!res.ok) throw new Error(`ingest ${res.status}`);\n queue.splice(0, batch.length);\n attempts = 0;\n } catch (e) {\n // records stay queued; the SAME _ids retry, so the server dedupes\n attempts++;\n if (attempts >= maxRetries) {\n queue.splice(0, batch.length);\n dropped += batch.length;\n attempts = 0;\n }\n onError(e);\n return; // back off until the next interval\n }\n }\n };\n\n const flush = () => (flushing ??= doFlush().finally(() => (flushing = null)));\n\n if (flushIntervalMs > 0) {\n timer = setInterval(() => void flush(), flushIntervalMs);\n (timer as { unref?: () => void }).unref?.(); // never hold a node process open\n }\n\n const client = {\n track(\n name: string,\n opts: {\n attrs?: Record<string, string>;\n metrics?: Record<string, number>;\n data?: Record<string, unknown>;\n occurredAt?: Date;\n subjects?: Array<{ type: string; id: string; role?: string }>;\n severity?: string;\n } = {},\n ) {\n const trace = activeTrace();\n enqueue({\n _id: newId(),\n name,\n occurredAt: (opts.occurredAt ?? new Date()).toISOString(),\n ...(opts.attrs ? { attrs: opts.attrs } : {}),\n ...(opts.metrics ? { metrics: opts.metrics } : {}),\n ...(opts.data ? { data: opts.data } : {}),\n ...(opts.subjects ? { subjects: opts.subjects } : {}),\n ...(opts.severity ? { severity: opts.severity } : {}),\n ...(trace ? { traceId: trace.traceId, parentId: trace.spanId } : {}),\n });\n },\n\n captureError(err: unknown, ctx: { handled?: boolean; name?: string; attrs?: Record<string, string> } = {}) {\n const e = err instanceof Error ? err : new Error(String(err));\n const frames = parseFrames(e.stack);\n const trace = activeTrace();\n enqueue({\n _id: newId(),\n name: ctx.name ?? errorName,\n occurredAt: new Date().toISOString(),\n severity: 'error',\n ...(ctx.attrs ? { attrs: ctx.attrs } : {}),\n ...(trace ? { traceId: trace.traceId, parentId: trace.spanId } : {}),\n error: {\n type: e.name || 'Error',\n message: e.message,\n handled: ctx.handled ?? true,\n fingerprint: fingerprint(e.name, e.message, frames[0]?.filename ?? ''),\n frames,\n },\n });\n },\n\n startSpan(name: string, opts: { attrs?: Record<string, string> } = {}): Span {\n const parent = activeTrace();\n const traceId = parent?.traceId ?? newId();\n const spanId = newId();\n const startedAt = Date.now();\n const frame = { traceId, spanId };\n spanStack.push(frame);\n let ended = false;\n return {\n traceId,\n spanId,\n end: (extra = {}) => {\n if (ended) return;\n ended = true;\n const i = spanStack.indexOf(frame);\n if (i >= 0) spanStack.splice(i, 1);\n enqueue({\n _id: newId(),\n name,\n occurredAt: new Date(startedAt).toISOString(),\n traceId,\n spanId,\n ...(parent ? { parentId: parent.spanId } : {}),\n durationMs: Date.now() - startedAt,\n ...(opts.attrs || extra.attrs ? { attrs: { ...opts.attrs, ...extra.attrs } } : {}),\n ...(extra.metrics ? { metrics: extra.metrics } : {}),\n });\n },\n };\n },\n\n state(name: string, st: { key: string; from?: string; to: string; previousSinceMs?: number }) {\n enqueue({ _id: newId(), name, occurredAt: new Date().toISOString(), state: st });\n },\n\n /**\n * Swap in the real identity. Future batches carry the new subjects; one\n * `$identify` control record links anon → user for the stitching job.\n * The anon subject stays on the context — stitching consumes the alias.\n */\n identify(ids: Record<string, string | null | undefined>) {\n const hadUser = subjects.get('user');\n for (const [type, id] of Object.entries(ids)) {\n if (id == null) subjects.delete(type);\n else subjects.set(type, String(id));\n }\n const user = subjects.get('user');\n if (user && user !== hadUser && anonId) {\n enqueue({\n _id: newId(),\n name: '$identify',\n occurredAt: new Date().toISOString(),\n anonRef: `anon:${anonId}`,\n userRef: `user:${user}`,\n });\n }\n if (subjects.has('user')) actor = `user:${subjects.get('user')}`;\n },\n\n setActor(ref: string | undefined) {\n actor = ref;\n },\n\n flush,\n\n async shutdown() {\n if (timer) clearInterval(timer);\n await flush();\n },\n\n /** introspection for adapters and tests — not a public contract */\n _internal: {\n queue,\n subjects,\n get anonId() {\n return anonId!;\n },\n sessionId,\n get dropped() {\n return dropped;\n },\n enqueue,\n },\n };\n\n return client;\n}\n\nexport type TelemetryClient = ReturnType<typeof createClient>;\n\n// The isomorphic registry surface (instrumentation §8): the host's registry\n// file imports defineRegistry from '/core' so server AND clients can import\n// it — server at runtime, clients `import type` only, so zod never reaches a\n// bundle. This chain must stay mongoose/express-free.\nexport { defineRegistry, boundedMeta } from '../server/registry.js';\nexport type { Registry, EventSpec, RollupSpec, DimSource } from '../server/registry.js';\n","import { createClient, type CreateClientOptions, type TelemetryClient, type Transport } from './core.js';\n\n/**\n * Electron wiring (instrumentation §7). The shape that matters: records from\n * the RENDERER route through MAIN over IPC, so the key never reaches the\n * renderer, there is one queue, and offline behavior lives in one place.\n * Electron itself is structural-typed — nothing imports 'electron' at runtime.\n */\n\nexport const IPC_CHANNEL = 'telemetry:batch';\n\ninterface IpcMainLike {\n handle(channel: string, listener: (event: unknown, ...args: any[]) => unknown): void;\n}\n\ninterface IpcRendererLike {\n invoke(channel: string, ...args: any[]): Promise<any>;\n}\n\nexport interface MainTelemetryOptions extends CreateClientOptions {\n /** wire process-level crash handlers (default true) */\n captureProcessErrors?: boolean;\n /** pass electron's ipcMain to accept renderer batches over IPC */\n ipcMain?: IpcMainLike;\n}\n\n/** Main process: node transport, crash handlers, the single real queue. */\nexport function createMainTelemetry(opts: MainTelemetryOptions): TelemetryClient {\n const { captureProcessErrors = true, ipcMain, clientContext, ...rest } = opts;\n const client = createClient({\n ...rest,\n clientContext: { platform: 'electron', appVersion: opts.release ?? 'unknown', ...clientContext },\n });\n\n if (captureProcessErrors && typeof process !== 'undefined') {\n process.on('uncaughtException', (err) => client.captureError(err, { handled: false }));\n process.on('unhandledRejection', (reason) => client.captureError(reason, { handled: false }));\n }\n\n // renderer batches arrive as raw wire records — enqueue into the one queue\n ipcMain?.handle(IPC_CHANNEL, (_event, records: unknown) => {\n if (Array.isArray(records)) {\n for (const rec of records) {\n if (rec && typeof rec._id === 'string' && typeof rec.name === 'string') {\n client._internal.enqueue(rec);\n }\n }\n }\n return { ok: true };\n });\n\n return client;\n}\n\n/**\n * Renderer: the web-shaped client with an IPC transport. The URL/key options\n * are unused here — main owns the credential — so this takes only behavior.\n */\nexport function createRendererTelemetry(\n ipcRenderer: IpcRendererLike,\n opts: Omit<CreateClientOptions, 'key' | 'url' | 'transport'> = {},\n): TelemetryClient {\n const transport: Transport = async (_url, body) => {\n const batch = JSON.parse(body);\n await ipcRenderer.invoke(IPC_CHANNEL, batch.records);\n return { ok: true };\n };\n return createClient({\n ...opts,\n key: 'pk_ipc_tk_000000000000000000000000', // never leaves the process\n url: 'ipc://main',\n transport,\n clientContext: { platform: 'electron', appVersion: opts.release ?? 'unknown', ...opts.clientContext },\n });\n}\n\nexport { createClient } from './core.js';\nexport type { TelemetryClient } from './core.js';\n"]}
1
+ {"version":3,"sources":["../src/client/core.ts","../src/client/electron.ts"],"names":["uuidv7"],"mappings":"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","file":"electron.js","sourcesContent":["import { uuidv7 } from 'uuidv7';\n\ndeclare const window: unknown;\n\n/**\n * The isomorphic SDK core (instrumentation §6) — ~80% of every client by\n * value. Zero heavy dependencies; platform entries are thin wiring over this.\n *\n * The wire contract it upholds:\n * - every record carries a client-generated UUIDv7 `_id` (the idempotency\n * contract — transports are at-least-once)\n * - trace ids are UUIDv7 too: schema §2.6 samples on the random hex tail\n * - batch context carries subjects/actor once, not per record\n * - `identify()` swaps subjects AND posts one `$identify` control record —\n * the input to the server-side stitching job\n */\n\nexport const newId = uuidv7;\n\nexport interface ClientContextInput {\n platform?: string;\n appVersion?: string;\n userAgent?: string;\n os?: string;\n osVersion?: string;\n browser?: string;\n browserVersion?: string;\n deviceType?: string;\n locale?: string;\n timezone?: string;\n screenW?: number;\n screenH?: number;\n viewportW?: number;\n viewportH?: number;\n connection?: string;\n online?: boolean;\n}\n\nexport interface TransportResult {\n ok: boolean;\n status?: number;\n}\n\nexport type Transport = (url: string, body: string, headers: Record<string, string>) => Promise<TransportResult>;\n\nexport interface ClientStorage {\n get(key: string): string | null | undefined;\n set(key: string, value: string): void;\n}\n\nexport interface WireRecord {\n _id: string;\n name: string;\n occurredAt: string;\n attrs?: Record<string, string>;\n metrics?: Record<string, number>;\n data?: Record<string, unknown>;\n body?: string;\n severity?: string;\n subjects?: Array<{ type: string; id: string; role?: string }>;\n actor?: string;\n traceId?: string;\n spanId?: string;\n parentId?: string;\n durationMs?: number;\n error?: unknown;\n state?: unknown;\n usage?: unknown;\n anonRef?: string;\n userRef?: string;\n}\n\nexport interface CreateClientOptions {\n /** pk_ for anything shipped to users; sk_ only in trusted processes */\n key: string;\n /** the mounted ingest endpoint, e.g. https://app.example.com/telemetry/ingest */\n url: string;\n /** injected at build; the key supplies service/env */\n release?: string;\n flushIntervalMs?: number;\n /** records per POST */\n maxBatchSize?: number;\n /** ring buffer cap — drop-OLDEST beyond it; new telemetry outranks stale */\n maxQueueSize?: number;\n /** failed flushes retry with backoff; a batch is abandoned after this many */\n maxRetries?: number;\n transport?: Transport;\n /** persists the anon id (and lets platforms persist queues); memory default */\n storage?: ClientStorage;\n clientContext?: ClientContextInput;\n /** false = drop instead of send. Web adapter wires DNT/GPC/consent here. */\n consent?: () => boolean;\n /** registry name used by captureError. Convention: 'error.unhandled'. */\n errorName?: string;\n /**\n * Last gate before a record joins the queue — the one place every kind\n * (event, error, span, state, usage) passes through. Return the record to\n * keep it, a modified copy to redact it, or `null` to drop it silently.\n *\n * This is where noise dies. A host that filters at the platform level\n * instead — swallowing `window.onerror` before the SDK sees it — has to win\n * a listener-registration race to do it, which is a real footgun; the whole\n * point of putting the hook here is that the host never has to think about\n * ordering.\n *\n * A throwing hook DROPS the record and reports via `onError`. Fail-closed is\n * deliberate: the common use is redaction, and a half-applied redaction that\n * still ships is worse than a lost record.\n */\n beforeSend?: (rec: WireRecord) => WireRecord | null | undefined | void;\n /** SDK-internal failures — never thrown at the app */\n onError?: (e: unknown) => void;\n}\n\nexport interface Span {\n readonly traceId: string;\n readonly spanId: string;\n end(extra?: { attrs?: Record<string, string>; metrics?: Record<string, number> }): void;\n}\n\nconst SDK = { name: '@jeffjassky/telemetry', version: '0' };\n\nconst defaultTransport: Transport = async (url, body, headers) => {\n const res = await fetch(url, { method: 'POST', headers, body, keepalive: true });\n return { ok: res.ok, status: res.status };\n};\n\nconst memoryStorage = (): ClientStorage => {\n const m = new Map<string, string>();\n return { get: (k) => m.get(k), set: (k, v) => void m.set(k, v) };\n};\n\n/** stable, cheap error grouping — server-side fingerprinting can refine later */\nconst fingerprint = (type: string, message: string, frame: string): string => {\n const s = `${type}|${message.replace(/\\d+/g, 'N').slice(0, 200)}|${frame}`;\n let h = 5381;\n for (let i = 0; i < s.length; i++) h = ((h << 5) + h + s.charCodeAt(i)) >>> 0;\n return h.toString(16);\n};\n\nconst FRAME_RE = /^\\s*at\\s+(?:(.+?)\\s+\\()?(?:(.+?):(\\d+):(\\d+))\\)?\\s*$/;\n\nconst parseFrames = (stack: string | undefined) =>\n (stack ?? '')\n .split('\\n')\n .slice(1, 21)\n .map((line) => FRAME_RE.exec(line))\n .filter((m): m is RegExpExecArray => !!m)\n .map((m) => ({\n fn: m[1],\n filename: m[2],\n lineno: Number(m[3]),\n colno: Number(m[4]),\n }));\n\nexport function createClient(options: CreateClientOptions) {\n const {\n key,\n url,\n release,\n flushIntervalMs = 5000,\n maxBatchSize = 50,\n maxQueueSize = 1000,\n maxRetries = 5,\n transport = defaultTransport,\n storage = memoryStorage(),\n consent = () => true,\n errorName = 'error.unhandled',\n beforeSend,\n onError = () => {},\n } = options;\n\n // ClientContext must always be storable — platform/appVersion are required\n // by the envelope, so the core guarantees them and adapters refine them\n const clientContext = {\n platform: typeof window === 'undefined' ? 'server' : 'web',\n appVersion: release ?? 'unknown',\n ...options.clientContext,\n };\n\n // ── identity context ──\n let anonId = storage.get('telemetry_anon') ?? null;\n if (!anonId) {\n anonId = `anon_${newId()}`;\n try {\n storage.set('telemetry_anon', anonId);\n } catch (e) {\n onError(e);\n }\n }\n const sessionId = `ses_${newId()}`;\n /** current subjects, by type. anon + session ride every batch (schema §2.3). */\n const subjects = new Map<string, string>([\n ['anon', anonId],\n ['session', sessionId],\n ]);\n let actor: string | undefined;\n\n // ── queue ──\n const queue: WireRecord[] = [];\n let dropped = 0;\n let attempts = 0;\n let flushing: Promise<void> | null = null;\n let timer: ReturnType<typeof setInterval> | undefined;\n\n const enqueue = (rec: WireRecord) => {\n let out: WireRecord | null | undefined | void = rec;\n if (beforeSend) {\n try {\n out = beforeSend(rec);\n } catch (e) {\n onError(e); // fail-closed: a broken filter must not leak what it was filtering\n return;\n }\n }\n if (!out) return;\n queue.push(out);\n while (queue.length > maxQueueSize) {\n queue.shift(); // drop-oldest: fresh telemetry outranks stale\n dropped++;\n }\n };\n\n // ── tracing ──\n const spanStack: Array<{ traceId: string; spanId: string }> = [];\n const activeTrace = () => spanStack[spanStack.length - 1];\n\n const doFlush = async (): Promise<void> => {\n if (!queue.length) return;\n if (!consent()) {\n queue.length = 0; // no consent = no send AND no hoard\n return;\n }\n while (queue.length) {\n const batch = queue.slice(0, maxBatchSize);\n const body = JSON.stringify({\n sdk: SDK,\n sentAt: new Date().toISOString(),\n release,\n client: clientContext,\n context: {\n subjects: [...subjects].map(([type, id]) => ({ type, id })),\n ...(actor ? { actor } : {}),\n },\n records: batch,\n });\n try {\n const res = await transport(url, body, {\n 'content-type': 'application/json',\n authorization: `Bearer ${key}`,\n });\n if (!res.ok) throw new Error(`ingest ${res.status}`);\n queue.splice(0, batch.length);\n attempts = 0;\n } catch (e) {\n // records stay queued; the SAME _ids retry, so the server dedupes\n attempts++;\n if (attempts >= maxRetries) {\n queue.splice(0, batch.length);\n dropped += batch.length;\n attempts = 0;\n }\n onError(e);\n return; // back off until the next interval\n }\n }\n };\n\n const flush = () => (flushing ??= doFlush().finally(() => (flushing = null)));\n\n if (flushIntervalMs > 0) {\n timer = setInterval(() => void flush(), flushIntervalMs);\n (timer as { unref?: () => void }).unref?.(); // never hold a node process open\n }\n\n const client = {\n track(\n name: string,\n opts: {\n attrs?: Record<string, string>;\n metrics?: Record<string, number>;\n data?: Record<string, unknown>;\n occurredAt?: Date;\n subjects?: Array<{ type: string; id: string; role?: string }>;\n severity?: string;\n } = {},\n ) {\n const trace = activeTrace();\n enqueue({\n _id: newId(),\n name,\n occurredAt: (opts.occurredAt ?? new Date()).toISOString(),\n ...(opts.attrs ? { attrs: opts.attrs } : {}),\n ...(opts.metrics ? { metrics: opts.metrics } : {}),\n ...(opts.data ? { data: opts.data } : {}),\n ...(opts.subjects ? { subjects: opts.subjects } : {}),\n ...(opts.severity ? { severity: opts.severity } : {}),\n ...(trace ? { traceId: trace.traceId, parentId: trace.spanId } : {}),\n });\n },\n\n captureError(err: unknown, ctx: { handled?: boolean; name?: string; attrs?: Record<string, string> } = {}) {\n const e = err instanceof Error ? err : new Error(String(err));\n const frames = parseFrames(e.stack);\n const trace = activeTrace();\n enqueue({\n _id: newId(),\n name: ctx.name ?? errorName,\n occurredAt: new Date().toISOString(),\n severity: 'error',\n ...(ctx.attrs ? { attrs: ctx.attrs } : {}),\n ...(trace ? { traceId: trace.traceId, parentId: trace.spanId } : {}),\n error: {\n type: e.name || 'Error',\n message: e.message,\n handled: ctx.handled ?? true,\n fingerprint: fingerprint(e.name, e.message, frames[0]?.filename ?? ''),\n frames,\n },\n });\n },\n\n startSpan(name: string, opts: { attrs?: Record<string, string> } = {}): Span {\n const parent = activeTrace();\n const traceId = parent?.traceId ?? newId();\n const spanId = newId();\n const startedAt = Date.now();\n const frame = { traceId, spanId };\n spanStack.push(frame);\n let ended = false;\n return {\n traceId,\n spanId,\n end: (extra = {}) => {\n if (ended) return;\n ended = true;\n const i = spanStack.indexOf(frame);\n if (i >= 0) spanStack.splice(i, 1);\n enqueue({\n _id: newId(),\n name,\n occurredAt: new Date(startedAt).toISOString(),\n traceId,\n spanId,\n ...(parent ? { parentId: parent.spanId } : {}),\n durationMs: Date.now() - startedAt,\n ...(opts.attrs || extra.attrs ? { attrs: { ...opts.attrs, ...extra.attrs } } : {}),\n ...(extra.metrics ? { metrics: extra.metrics } : {}),\n });\n },\n };\n },\n\n state(name: string, st: { key: string; from?: string; to: string; previousSinceMs?: number }) {\n enqueue({ _id: newId(), name, occurredAt: new Date().toISOString(), state: st });\n },\n\n /**\n * Swap in the real identity. Future batches carry the new subjects; one\n * `$identify` control record links anon → user for the stitching job.\n * The anon subject stays on the context — stitching consumes the alias.\n */\n identify(ids: Record<string, string | null | undefined>) {\n const hadUser = subjects.get('user');\n for (const [type, id] of Object.entries(ids)) {\n if (id == null) subjects.delete(type);\n else subjects.set(type, String(id));\n }\n const user = subjects.get('user');\n if (user && user !== hadUser && anonId) {\n enqueue({\n _id: newId(),\n name: '$identify',\n occurredAt: new Date().toISOString(),\n anonRef: `anon:${anonId}`,\n userRef: `user:${user}`,\n });\n }\n if (subjects.has('user')) actor = `user:${subjects.get('user')}`;\n },\n\n setActor(ref: string | undefined) {\n actor = ref;\n },\n\n flush,\n\n async shutdown() {\n if (timer) clearInterval(timer);\n await flush();\n },\n\n /** introspection for adapters and tests — not a public contract */\n _internal: {\n queue,\n subjects,\n get anonId() {\n return anonId!;\n },\n sessionId,\n get dropped() {\n return dropped;\n },\n enqueue,\n },\n };\n\n return client;\n}\n\nexport type TelemetryClient = ReturnType<typeof createClient>;\n\n// The isomorphic registry surface (instrumentation §8): the host's registry\n// file imports defineRegistry from '/core' so server AND clients can import\n// it — server at runtime, clients `import type` only, so zod never reaches a\n// bundle. This chain must stay mongoose/express-free.\nexport { defineRegistry, boundedMeta } from '../server/registry.js';\nexport type { Registry, EventSpec, RollupSpec, DimSource } from '../server/registry.js';\n","import { createClient, type CreateClientOptions, type TelemetryClient, type Transport } from './core.js';\n\n/**\n * Electron wiring (instrumentation §7). The shape that matters: records from\n * the RENDERER route through MAIN over IPC, so the key never reaches the\n * renderer, there is one queue, and offline behavior lives in one place.\n * Electron itself is structural-typed — nothing imports 'electron' at runtime.\n */\n\nexport const IPC_CHANNEL = 'telemetry:batch';\n\ninterface IpcMainLike {\n handle(channel: string, listener: (event: unknown, ...args: any[]) => unknown): void;\n}\n\ninterface IpcRendererLike {\n invoke(channel: string, ...args: any[]): Promise<any>;\n}\n\nexport interface MainTelemetryOptions extends CreateClientOptions {\n /** wire process-level crash handlers (default true) */\n captureProcessErrors?: boolean;\n /** pass electron's ipcMain to accept renderer batches over IPC */\n ipcMain?: IpcMainLike;\n}\n\n/** Main process: node transport, crash handlers, the single real queue. */\nexport function createMainTelemetry(opts: MainTelemetryOptions): TelemetryClient {\n const { captureProcessErrors = true, ipcMain, clientContext, ...rest } = opts;\n const client = createClient({\n ...rest,\n clientContext: { platform: 'electron', appVersion: opts.release ?? 'unknown', ...clientContext },\n });\n\n if (captureProcessErrors && typeof process !== 'undefined') {\n process.on('uncaughtException', (err) => client.captureError(err, { handled: false }));\n process.on('unhandledRejection', (reason) => client.captureError(reason, { handled: false }));\n }\n\n // renderer batches arrive as raw wire records — enqueue into the one queue\n ipcMain?.handle(IPC_CHANNEL, (_event, records: unknown) => {\n if (Array.isArray(records)) {\n for (const rec of records) {\n if (rec && typeof rec._id === 'string' && typeof rec.name === 'string') {\n client._internal.enqueue(rec);\n }\n }\n }\n return { ok: true };\n });\n\n return client;\n}\n\n/**\n * Renderer: the web-shaped client with an IPC transport. The URL/key options\n * are unused here — main owns the credential — so this takes only behavior.\n */\nexport function createRendererTelemetry(\n ipcRenderer: IpcRendererLike,\n opts: Omit<CreateClientOptions, 'key' | 'url' | 'transport'> = {},\n): TelemetryClient {\n const transport: Transport = async (_url, body) => {\n const batch = JSON.parse(body);\n await ipcRenderer.invoke(IPC_CHANNEL, batch.records);\n return { ok: true };\n };\n return createClient({\n ...opts,\n key: 'pk_ipc_tk_000000000000000000000000', // never leaves the process\n url: 'ipc://main',\n transport,\n clientContext: { platform: 'electron', appVersion: opts.release ?? 'unknown', ...opts.clientContext },\n });\n}\n\nexport { createClient } from './core.js';\nexport type { TelemetryClient } from './core.js';\n"]}
package/dist/react.cjs CHANGED
@@ -63,6 +63,7 @@ function createClient(options) {
63
63
  storage = memoryStorage(),
64
64
  consent = () => true,
65
65
  errorName = "error.unhandled",
66
+ beforeSend,
66
67
  onError = () => {
67
68
  }
68
69
  } = options;
@@ -92,7 +93,17 @@ function createClient(options) {
92
93
  let flushing = null;
93
94
  let timer;
94
95
  const enqueue = (rec) => {
95
- queue.push(rec);
96
+ let out = rec;
97
+ if (beforeSend) {
98
+ try {
99
+ out = beforeSend(rec);
100
+ } catch (e) {
101
+ onError(e);
102
+ return;
103
+ }
104
+ }
105
+ if (!out) return;
106
+ queue.push(out);
96
107
  while (queue.length > maxQueueSize) {
97
108
  queue.shift();
98
109
  dropped++;