@zuplo/cli 7.4.5 → 7.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/types/jwks/remote.d.ts +4 -4
  2. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/types/jws/general/verify.d.ts +3 -3
  3. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/types/jwt/encrypt.d.ts +8 -14
  4. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/types/jwt/sign.d.ts +8 -14
  5. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/types/jwt/unsecured.d.ts +9 -15
  6. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/jwe/compact/encrypt.js +30 -8
  7. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/jwe/flattened/decrypt.js +8 -4
  8. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/jwe/flattened/encrypt.js +6 -13
  9. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/jwe/general/decrypt.js +19 -19
  10. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/jwe/general/encrypt.js +27 -20
  11. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/jwk/embedded.js +15 -1
  12. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/jwk/thumbprint.js +11 -5
  13. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/jwks/local.js +45 -53
  14. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/jwks/remote.js +82 -103
  15. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/jws/compact/sign.js +10 -9
  16. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/jws/flattened/sign.js +2 -1
  17. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/jws/flattened/verify.js +8 -7
  18. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/jws/general/sign.js +5 -3
  19. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/jws/general/verify.js +51 -21
  20. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/jwt/decrypt.js +7 -9
  21. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/jwt/encrypt.js +21 -50
  22. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/jwt/sign.js +8 -41
  23. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/jwt/unsecured.js +21 -42
  24. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/key/generate_key_pair.js +6 -2
  25. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/key/generate_secret.js +7 -6
  26. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/key/import.js +16 -12
  27. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/lib/asn1.js +7 -2
  28. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/lib/content_encryption.js +7 -15
  29. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/lib/deflate.js +8 -0
  30. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/lib/helpers.js +1 -1
  31. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/lib/jwe_decrypt.js +85 -28
  32. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/lib/jwe_encrypt.js +40 -12
  33. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/lib/jwk_metadata.js +20 -0
  34. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/lib/jws_sign.js +47 -36
  35. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/lib/jws_verify.js +77 -45
  36. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/lib/jwt_claims_set.js +82 -47
  37. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/lib/key.js +24 -9
  38. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/lib/key_management.js +10 -4
  39. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/lib/key_options.js +6 -0
  40. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/lib/options.js +26 -0
  41. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/lib/type_checks.js +11 -13
  42. package/node_modules/@modelcontextprotocol/client/node_modules/jose/package.json +1 -1
  43. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/types/jwks/remote.d.ts +4 -4
  44. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/types/jws/general/verify.d.ts +3 -3
  45. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/types/jwt/encrypt.d.ts +8 -14
  46. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/types/jwt/sign.d.ts +8 -14
  47. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/types/jwt/unsecured.d.ts +9 -15
  48. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/jwe/compact/encrypt.js +30 -8
  49. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/jwe/flattened/decrypt.js +8 -4
  50. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/jwe/flattened/encrypt.js +6 -13
  51. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/jwe/general/decrypt.js +19 -19
  52. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/jwe/general/encrypt.js +27 -20
  53. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/jwk/embedded.js +15 -1
  54. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/jwk/thumbprint.js +11 -5
  55. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/jwks/local.js +45 -53
  56. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/jwks/remote.js +82 -103
  57. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/jws/compact/sign.js +10 -9
  58. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/jws/flattened/sign.js +2 -1
  59. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/jws/flattened/verify.js +8 -7
  60. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/jws/general/sign.js +5 -3
  61. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/jws/general/verify.js +51 -21
  62. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/jwt/decrypt.js +7 -9
  63. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/jwt/encrypt.js +21 -50
  64. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/jwt/sign.js +8 -41
  65. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/jwt/unsecured.js +21 -42
  66. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/key/generate_key_pair.js +6 -2
  67. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/key/generate_secret.js +7 -6
  68. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/key/import.js +16 -12
  69. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/lib/asn1.js +7 -2
  70. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/lib/content_encryption.js +7 -15
  71. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/lib/deflate.js +8 -0
  72. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/lib/helpers.js +1 -1
  73. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/lib/jwe_decrypt.js +85 -28
  74. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/lib/jwe_encrypt.js +40 -12
  75. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/lib/jwk_metadata.js +20 -0
  76. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/lib/jws_sign.js +47 -36
  77. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/lib/jws_verify.js +77 -45
  78. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/lib/jwt_claims_set.js +82 -47
  79. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/lib/key.js +24 -9
  80. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/lib/key_management.js +10 -4
  81. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/lib/key_options.js +6 -0
  82. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/lib/options.js +26 -0
  83. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/lib/type_checks.js +11 -13
  84. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/package.json +1 -1
  85. package/node_modules/@posthog/core/dist/featureFlagLocalEvaluation.d.ts +35 -0
  86. package/node_modules/@posthog/core/dist/featureFlagLocalEvaluation.d.ts.map +1 -0
  87. package/node_modules/@posthog/core/dist/featureFlagLocalEvaluation.js +352 -0
  88. package/node_modules/@posthog/core/dist/featureFlagLocalEvaluation.mjs +294 -0
  89. package/node_modules/@posthog/core/dist/index.d.ts +2 -0
  90. package/node_modules/@posthog/core/dist/index.d.ts.map +1 -1
  91. package/node_modules/@posthog/core/dist/index.js +164 -79
  92. package/node_modules/@posthog/core/dist/index.mjs +2 -1
  93. package/node_modules/@posthog/core/dist/posthog-core-stateless.d.ts +12 -18
  94. package/node_modules/@posthog/core/dist/posthog-core-stateless.d.ts.map +1 -1
  95. package/node_modules/@posthog/core/dist/posthog-core-stateless.js +15 -47
  96. package/node_modules/@posthog/core/dist/posthog-core-stateless.mjs +15 -47
  97. package/node_modules/@posthog/core/dist/surveys/index.d.ts +1 -0
  98. package/node_modules/@posthog/core/dist/surveys/index.d.ts.map +1 -1
  99. package/node_modules/@posthog/core/dist/surveys/index.js +13 -0
  100. package/node_modules/@posthog/core/dist/surveys/index.mjs +2 -1
  101. package/node_modules/@posthog/core/dist/surveys/property-matching.d.ts +11 -0
  102. package/node_modules/@posthog/core/dist/surveys/property-matching.d.ts.map +1 -0
  103. package/node_modules/@posthog/core/dist/surveys/property-matching.js +89 -0
  104. package/node_modules/@posthog/core/dist/surveys/property-matching.mjs +46 -0
  105. package/node_modules/@posthog/core/dist/types.d.ts +15 -4
  106. package/node_modules/@posthog/core/dist/types.d.ts.map +1 -1
  107. package/node_modules/@posthog/core/package.json +2 -2
  108. package/node_modules/@posthog/core/src/featureFlagLocalEvaluation.ts +375 -0
  109. package/node_modules/@posthog/core/src/index.ts +19 -0
  110. package/node_modules/@posthog/core/src/logs/index.spec.ts +77 -0
  111. package/node_modules/@posthog/core/src/logs/index.ts +1 -1
  112. package/node_modules/@posthog/core/src/posthog-core-stateless.ts +40 -65
  113. package/node_modules/@posthog/core/src/surveys/index.ts +1 -0
  114. package/node_modules/@posthog/core/src/surveys/property-matching.spec.ts +174 -0
  115. package/node_modules/@posthog/core/src/surveys/property-matching.ts +75 -0
  116. package/node_modules/@posthog/core/src/types.ts +20 -4
  117. package/node_modules/@posthog/types/dist/index.d.ts +1 -1
  118. package/node_modules/@posthog/types/dist/index.d.ts.map +1 -1
  119. package/node_modules/@posthog/types/dist/posthog-config.d.ts +20 -5
  120. package/node_modules/@posthog/types/dist/posthog-config.d.ts.map +1 -1
  121. package/node_modules/@posthog/types/package.json +1 -1
  122. package/node_modules/@posthog/types/src/index.ts +1 -0
  123. package/node_modules/@posthog/types/src/posthog-config.ts +21 -5
  124. package/node_modules/@zuplo/core/customer.cli.minified.js +269 -269
  125. package/node_modules/@zuplo/core/index.minified.js +276 -276
  126. package/node_modules/@zuplo/core/package.json +1 -1
  127. package/node_modules/@zuplo/graphql/package.json +1 -1
  128. package/node_modules/@zuplo/openapi-tools/package.json +1 -1
  129. package/node_modules/@zuplo/otel/package.json +1 -1
  130. package/node_modules/@zuplo/runtime/out/esm/{chunk-IZE65N7D.js → chunk-CUK3VROF.js} +2 -2
  131. package/node_modules/@zuplo/runtime/out/esm/{chunk-4CDML74A.js → chunk-K4TBJG47.js} +2 -2
  132. package/node_modules/@zuplo/runtime/out/esm/{chunk-4VE55TG3.js → chunk-S4EBLGSU.js} +2 -2
  133. package/node_modules/@zuplo/runtime/out/esm/{chunk-4POCC75H.js → chunk-XF3IIUDX.js} +4 -4
  134. package/node_modules/@zuplo/runtime/out/esm/{chunk-4POCC75H.js.map → chunk-XF3IIUDX.js.map} +1 -1
  135. package/node_modules/@zuplo/runtime/out/esm/chunk-XIFGUD6K.js +403 -0
  136. package/node_modules/@zuplo/runtime/out/esm/chunk-XIFGUD6K.js.map +1 -0
  137. package/node_modules/@zuplo/runtime/out/esm/index.js +1 -1
  138. package/node_modules/@zuplo/runtime/out/esm/internal/index.js +1 -1
  139. package/node_modules/@zuplo/runtime/out/esm/mcp-gateway/index.js +1 -1
  140. package/node_modules/@zuplo/runtime/out/esm/mocks/index.js +1 -1
  141. package/node_modules/@zuplo/runtime/out/esm/sdks/aws/index.js +1 -1
  142. package/node_modules/@zuplo/runtime/package.json +1 -1
  143. package/node_modules/fast-uri/index.js +201 -48
  144. package/node_modules/fast-uri/lib/schemes.js +9 -4
  145. package/node_modules/fast-uri/lib/utils.js +388 -91
  146. package/node_modules/fast-uri/package.json +1 -1
  147. package/node_modules/fast-uri/test/component-safe-serialization.test.js +105 -0
  148. package/node_modules/fast-uri/test/equal.test.js +31 -3
  149. package/node_modules/fast-uri/test/fixtures/uri-js-parse.json +2 -0
  150. package/node_modules/fast-uri/test/ipv6-canonical.test.js +34 -0
  151. package/node_modules/fast-uri/test/ipv6-validation.test.js +90 -0
  152. package/node_modules/fast-uri/test/malformed-percent.test.js +77 -0
  153. package/node_modules/fast-uri/test/malformed-urn.test.js +61 -0
  154. package/node_modules/fast-uri/test/parse.test.js +7 -3
  155. package/node_modules/fast-uri/test/query-fragment-normalization.test.js +33 -0
  156. package/node_modules/fast-uri/test/reserved-path-normalization.test.js +109 -0
  157. package/node_modules/fast-uri/test/scheme-validation.test.js +124 -0
  158. package/node_modules/fast-uri/test/security-normalization.test.js +101 -0
  159. package/node_modules/fast-uri/test/security.test.js +75 -3
  160. package/node_modules/fast-uri/test/urn-full-input.test.js +29 -0
  161. package/node_modules/fast-uri/test/websocket-query-preservation.test.js +24 -0
  162. package/node_modules/hono/dist/cjs/client/client.js +25 -11
  163. package/node_modules/hono/dist/cjs/client/utils.js +3 -0
  164. package/node_modules/hono/dist/cjs/helper/accepts/accepts.js +36 -2
  165. package/node_modules/hono/dist/cjs/jsx/dom/render.js +2 -0
  166. package/node_modules/hono/dist/cjs/middleware/etag/index.js +1 -1
  167. package/node_modules/hono/dist/cjs/request.js +8 -4
  168. package/node_modules/hono/dist/cjs/router/reg-exp-router/node.js +10 -3
  169. package/node_modules/hono/dist/cjs/router/reg-exp-router/router.js +47 -83
  170. package/node_modules/hono/dist/cjs/router/reg-exp-router/trie.js +2 -1
  171. package/node_modules/hono/dist/cjs/router/trie-router/node.js +4 -3
  172. package/node_modules/hono/dist/cjs/router/utils.js +27 -0
  173. package/node_modules/hono/dist/cjs/utils/cookie.js +1 -1
  174. package/node_modules/hono/dist/cjs/utils/stream.js +7 -1
  175. package/node_modules/hono/dist/client/client.js +25 -11
  176. package/node_modules/hono/dist/client/utils.js +3 -0
  177. package/node_modules/hono/dist/helper/accepts/accepts.js +36 -2
  178. package/node_modules/hono/dist/jsx/dom/render.js +2 -0
  179. package/node_modules/hono/dist/middleware/etag/index.js +1 -1
  180. package/node_modules/hono/dist/request.js +8 -4
  181. package/node_modules/hono/dist/router/reg-exp-router/node.js +6 -2
  182. package/node_modules/hono/dist/router/reg-exp-router/router.js +53 -84
  183. package/node_modules/hono/dist/router/reg-exp-router/trie.js +2 -1
  184. package/node_modules/hono/dist/router/trie-router/node.js +4 -3
  185. package/node_modules/hono/dist/router/utils.js +5 -0
  186. package/node_modules/hono/dist/types/router/reg-exp-router/node.d.ts +3 -0
  187. package/node_modules/hono/dist/types/router/utils.d.ts +1 -0
  188. package/node_modules/hono/dist/utils/cookie.js +1 -1
  189. package/node_modules/hono/dist/utils/stream.js +7 -1
  190. package/node_modules/hono/package.json +1 -1
  191. package/package.json +11 -11
  192. package/node_modules/@zuplo/runtime/out/esm/chunk-4SX4HBWT.js +0 -409
  193. package/node_modules/@zuplo/runtime/out/esm/chunk-4SX4HBWT.js.map +0 -1
  194. /package/node_modules/@zuplo/runtime/out/esm/{chunk-IZE65N7D.js.map → chunk-CUK3VROF.js.map} +0 -0
  195. /package/node_modules/@zuplo/runtime/out/esm/{chunk-4CDML74A.js.map → chunk-K4TBJG47.js.map} +0 -0
  196. /package/node_modules/@zuplo/runtime/out/esm/{chunk-4VE55TG3.js.map → chunk-S4EBLGSU.js.map} +0 -0
  197. /package/node_modules/@zuplo/runtime/out/esm/{chunk-4POCC75H.js.LEGAL.txt → chunk-XF3IIUDX.js.LEGAL.txt} +0 -0
  198. /package/node_modules/@zuplo/runtime/out/esm/{chunk-4SX4HBWT.js.LEGAL.txt → chunk-XIFGUD6K.js.LEGAL.txt} +0 -0
@@ -0,0 +1,375 @@
1
+ import { parsePayload } from './featureFlagUtils'
2
+ import type { FeatureFlagValue, JsonType } from './types'
3
+
4
+ export type FeatureFlagPropertyValue = string | number | (string | number)[] | boolean
5
+
6
+ export type FeatureFlagProperty = {
7
+ key: string
8
+ value: FeatureFlagPropertyValue
9
+ operator?: string
10
+ }
11
+
12
+ export type FeatureFlagSemverParsingPolicy = 'strict' | 'legacy-permissive'
13
+
14
+ export type MatchFeatureFlagPropertyOptions = {
15
+ warnFunction?: (message: string) => void
16
+ semverParsingPolicy?: FeatureFlagSemverParsingPolicy
17
+ }
18
+
19
+ export type FeatureFlagVariant = {
20
+ key: string
21
+ rollout_percentage: number
22
+ }
23
+
24
+ export type FeatureFlagVariantLookupEntry = {
25
+ valueMin: number
26
+ valueMax: number
27
+ key: string
28
+ }
29
+
30
+ const NULL_VALUES_ALLOWED_OPERATORS = ['is_not', 'is_set']
31
+
32
+ // This value is intentionally larger than Number.MAX_SAFE_INTEGER. Changing its rounding changes
33
+ // existing rollout and variant assignments.
34
+ // eslint-disable-next-line no-loss-of-precision
35
+ const LONG_SCALE = 0xfffffffffffffff
36
+
37
+ export class InconclusiveMatchError extends Error {
38
+ constructor(message: string) {
39
+ super(message)
40
+ this.name = this.constructor.name
41
+ Object.setPrototypeOf(this, InconclusiveMatchError.prototype)
42
+ }
43
+ }
44
+
45
+ function isValidRegex(regex: string): boolean {
46
+ try {
47
+ new RegExp(regex)
48
+ return true
49
+ } catch {
50
+ return false
51
+ }
52
+ }
53
+
54
+ type SemverTuple = [number, number, number]
55
+
56
+ function parseSemverNumericIdentifier(
57
+ part: string,
58
+ raw: string,
59
+ parsingPolicy: FeatureFlagSemverParsingPolicy
60
+ ): number {
61
+ if (!/^\d+$/.test(part) || (parsingPolicy === 'strict' && part.length > 1 && part[0] === '0')) {
62
+ throw new InconclusiveMatchError(`Invalid semver: ${raw}`)
63
+ }
64
+ return parseInt(part, 10)
65
+ }
66
+
67
+ export function parseFeatureFlagSemver(
68
+ value: string,
69
+ parsingPolicy: FeatureFlagSemverParsingPolicy = 'strict'
70
+ ): SemverTuple {
71
+ const text = String(value).trim().replace(/^[vV]/, '')
72
+ const baseVersion = text.split('-')[0].split('+')[0]
73
+
74
+ if (!baseVersion || baseVersion.startsWith('.')) {
75
+ throw new InconclusiveMatchError(`Invalid semver: ${value}`)
76
+ }
77
+
78
+ const parts = baseVersion.split('.')
79
+ const parsePart = (part: string | undefined): number => {
80
+ if (part === undefined || part === '') return 0
81
+ return parseSemverNumericIdentifier(part, value, parsingPolicy)
82
+ }
83
+
84
+ return [parsePart(parts[0]), parsePart(parts[1]), parsePart(parts[2])]
85
+ }
86
+
87
+ function compareSemverTuples(a: SemverTuple, b: SemverTuple): number {
88
+ for (let i = 0; i < 3; i++) {
89
+ if (a[i] < b[i]) return -1
90
+ if (a[i] > b[i]) return 1
91
+ }
92
+ return 0
93
+ }
94
+
95
+ function computeTildeBounds(
96
+ value: string,
97
+ parsingPolicy: FeatureFlagSemverParsingPolicy
98
+ ): { lower: SemverTuple; upper: SemverTuple } {
99
+ const parsed = parseFeatureFlagSemver(value, parsingPolicy)
100
+ return { lower: [parsed[0], parsed[1], parsed[2]], upper: [parsed[0], parsed[1] + 1, 0] }
101
+ }
102
+
103
+ function computeCaretBounds(
104
+ value: string,
105
+ parsingPolicy: FeatureFlagSemverParsingPolicy
106
+ ): { lower: SemverTuple; upper: SemverTuple } {
107
+ const [major, minor, patch] = parseFeatureFlagSemver(value, parsingPolicy)
108
+ const lower: SemverTuple = [major, minor, patch]
109
+ let upper: SemverTuple
110
+ if (major > 0) upper = [major + 1, 0, 0]
111
+ else if (minor > 0) upper = [0, minor + 1, 0]
112
+ else upper = [0, 0, patch + 1]
113
+ return { lower, upper }
114
+ }
115
+
116
+ function computeWildcardBounds(
117
+ value: string,
118
+ parsingPolicy: FeatureFlagSemverParsingPolicy
119
+ ): { lower: SemverTuple; upper: SemverTuple } {
120
+ const text = String(value).trim().replace(/^[vV]/, '')
121
+ const cleanedText = text.replace(/\.\*$/, '').replace(/\*$/, '')
122
+ if (!cleanedText) throw new InconclusiveMatchError(`Invalid wildcard semver: ${value}`)
123
+
124
+ const parts = cleanedText.split('.')
125
+ const parseWildcardPart = (part: string): number => {
126
+ if (parsingPolicy === 'legacy-permissive') {
127
+ const parsed = parseInt(part, 10)
128
+ if (!isNaN(parsed)) return parsed
129
+ } else {
130
+ try {
131
+ return parseSemverNumericIdentifier(part, value, parsingPolicy)
132
+ } catch {
133
+ // Normalize wildcard parsing failures to the historical wildcard-specific error.
134
+ }
135
+ }
136
+ throw new InconclusiveMatchError(`Invalid wildcard semver: ${value}`)
137
+ }
138
+
139
+ const major = parseWildcardPart(parts[0])
140
+ if (parts.length === 1) {
141
+ return { lower: [major, 0, 0], upper: [major + 1, 0, 0] }
142
+ }
143
+ const minor = parseWildcardPart(parts[1])
144
+ return { lower: [major, minor, 0], upper: [major, minor + 1, 0] }
145
+ }
146
+
147
+ function convertToDateTime(value: FeatureFlagPropertyValue | Date): Date {
148
+ if (value instanceof Date) return value
149
+ if (typeof value === 'string' || typeof value === 'number') {
150
+ const date = new Date(value)
151
+ if (!isNaN(date.valueOf())) return date
152
+ throw new InconclusiveMatchError(`${value} is in an invalid date format`)
153
+ }
154
+ throw new InconclusiveMatchError(`The date provided ${value} must be a string, number, or date object`)
155
+ }
156
+
157
+ export function relativeDateParseForFeatureFlagMatching(value: string): Date | null {
158
+ const regex = /^-?(?<number>[0-9]+)(?<interval>[a-z])$/
159
+ const match = value.match(regex)
160
+ const parsedDt = new Date(new Date().toISOString())
161
+
162
+ if (!match || !match.groups) return null
163
+
164
+ const number = parseInt(match.groups['number'])
165
+ if (number >= 10000) return null
166
+
167
+ const interval = match.groups['interval']
168
+ if (interval === 'h') parsedDt.setUTCHours(parsedDt.getUTCHours() - number)
169
+ else if (interval === 'd') parsedDt.setUTCDate(parsedDt.getUTCDate() - number)
170
+ else if (interval === 'w') parsedDt.setUTCDate(parsedDt.getUTCDate() - number * 7)
171
+ else if (interval === 'm') parsedDt.setUTCMonth(parsedDt.getUTCMonth() - number)
172
+ else if (interval === 'y') parsedDt.setUTCFullYear(parsedDt.getUTCFullYear() - number)
173
+ else return null
174
+
175
+ return parsedDt
176
+ }
177
+
178
+ export function matchFeatureFlagProperty(
179
+ property: FeatureFlagProperty,
180
+ propertyValues: Record<string, any>,
181
+ options: MatchFeatureFlagPropertyOptions = {}
182
+ ): boolean {
183
+ const key = property.key
184
+ const value = property.value
185
+ const operator = property.operator || 'exact'
186
+ const parsingPolicy = options.semverParsingPolicy ?? 'strict'
187
+
188
+ if (!(key in propertyValues)) {
189
+ if (operator === 'is_not_set') return true
190
+ throw new InconclusiveMatchError(`Property ${key} not found in propertyValues`)
191
+ } else if (operator === 'is_not_set') {
192
+ return false
193
+ }
194
+
195
+ const overrideValue = propertyValues[key]
196
+ if (overrideValue == null && !NULL_VALUES_ALLOWED_OPERATORS.includes(operator)) {
197
+ options.warnFunction?.(`Property ${key} cannot have a value of null/undefined with the ${operator} operator`)
198
+ return false
199
+ }
200
+
201
+ const computeExactMatch = (target: any, actual: any): boolean => {
202
+ if (Array.isArray(target)) {
203
+ return target.map((item) => String(item).toLowerCase()).includes(String(actual).toLowerCase())
204
+ }
205
+ return String(target).toLowerCase() === String(actual).toLowerCase()
206
+ }
207
+
208
+ const compare = (lhs: any, rhs: any, comparisonOperator: string): boolean => {
209
+ if (comparisonOperator === 'gt') return lhs > rhs
210
+ if (comparisonOperator === 'gte') return lhs >= rhs
211
+ if (comparisonOperator === 'lt') return lhs < rhs
212
+ if (comparisonOperator === 'lte') return lhs <= rhs
213
+ throw new Error(`Invalid operator: ${comparisonOperator}`)
214
+ }
215
+
216
+ switch (operator) {
217
+ case 'exact':
218
+ return computeExactMatch(value, overrideValue)
219
+ case 'is_not':
220
+ return !computeExactMatch(value, overrideValue)
221
+ case 'is_set':
222
+ return key in propertyValues
223
+ case 'icontains':
224
+ return String(overrideValue).toLowerCase().includes(String(value).toLowerCase())
225
+ case 'not_icontains':
226
+ return !String(overrideValue).toLowerCase().includes(String(value).toLowerCase())
227
+ case 'starts_with':
228
+ return String(overrideValue).toLowerCase().startsWith(String(value).toLowerCase())
229
+ case 'not_starts_with':
230
+ return !String(overrideValue).toLowerCase().startsWith(String(value).toLowerCase())
231
+ case 'ends_with':
232
+ return String(overrideValue).toLowerCase().endsWith(String(value).toLowerCase())
233
+ case 'not_ends_with':
234
+ return !String(overrideValue).toLowerCase().endsWith(String(value).toLowerCase())
235
+ case 'regex':
236
+ return isValidRegex(String(value)) && String(overrideValue).match(String(value)) !== null
237
+ case 'not_regex':
238
+ return isValidRegex(String(value)) && String(overrideValue).match(String(value)) === null
239
+ case 'gt':
240
+ case 'gte':
241
+ case 'lt':
242
+ case 'lte': {
243
+ const parsedValue = typeof value === 'number' ? value : parseFloat(String(value))
244
+ const parsedOverride =
245
+ typeof overrideValue === 'number'
246
+ ? overrideValue
247
+ : overrideValue != null
248
+ ? parseFloat(String(overrideValue))
249
+ : NaN
250
+ if (Number.isFinite(parsedValue) && Number.isFinite(parsedOverride)) {
251
+ return compare(parsedOverride, parsedValue, operator)
252
+ }
253
+ return compare(String(overrideValue), String(value), operator)
254
+ }
255
+ case 'is_date_after':
256
+ case 'is_date_before': {
257
+ if (typeof value === 'boolean') {
258
+ throw new InconclusiveMatchError('Date operations cannot be performed on boolean values')
259
+ }
260
+ let parsedDate = relativeDateParseForFeatureFlagMatching(String(value))
261
+ if (parsedDate == null) parsedDate = convertToDateTime(value)
262
+ const overrideDate = convertToDateTime(overrideValue)
263
+ return operator === 'is_date_before' ? overrideDate < parsedDate : overrideDate > parsedDate
264
+ }
265
+ case 'semver_eq':
266
+ return (
267
+ compareSemverTuples(
268
+ parseFeatureFlagSemver(String(overrideValue), parsingPolicy),
269
+ parseFeatureFlagSemver(String(value), parsingPolicy)
270
+ ) === 0
271
+ )
272
+ case 'semver_neq':
273
+ return (
274
+ compareSemverTuples(
275
+ parseFeatureFlagSemver(String(overrideValue), parsingPolicy),
276
+ parseFeatureFlagSemver(String(value), parsingPolicy)
277
+ ) !== 0
278
+ )
279
+ case 'semver_gt':
280
+ return (
281
+ compareSemverTuples(
282
+ parseFeatureFlagSemver(String(overrideValue), parsingPolicy),
283
+ parseFeatureFlagSemver(String(value), parsingPolicy)
284
+ ) > 0
285
+ )
286
+ case 'semver_gte':
287
+ return (
288
+ compareSemverTuples(
289
+ parseFeatureFlagSemver(String(overrideValue), parsingPolicy),
290
+ parseFeatureFlagSemver(String(value), parsingPolicy)
291
+ ) >= 0
292
+ )
293
+ case 'semver_lt':
294
+ return (
295
+ compareSemverTuples(
296
+ parseFeatureFlagSemver(String(overrideValue), parsingPolicy),
297
+ parseFeatureFlagSemver(String(value), parsingPolicy)
298
+ ) < 0
299
+ )
300
+ case 'semver_lte':
301
+ return (
302
+ compareSemverTuples(
303
+ parseFeatureFlagSemver(String(overrideValue), parsingPolicy),
304
+ parseFeatureFlagSemver(String(value), parsingPolicy)
305
+ ) <= 0
306
+ )
307
+ case 'semver_tilde': {
308
+ const overrideParsed = parseFeatureFlagSemver(String(overrideValue), parsingPolicy)
309
+ const { lower, upper } = computeTildeBounds(String(value), parsingPolicy)
310
+ return compareSemverTuples(overrideParsed, lower) >= 0 && compareSemverTuples(overrideParsed, upper) < 0
311
+ }
312
+ case 'semver_caret': {
313
+ const overrideParsed = parseFeatureFlagSemver(String(overrideValue), parsingPolicy)
314
+ const { lower, upper } = computeCaretBounds(String(value), parsingPolicy)
315
+ return compareSemverTuples(overrideParsed, lower) >= 0 && compareSemverTuples(overrideParsed, upper) < 0
316
+ }
317
+ case 'semver_wildcard': {
318
+ const overrideParsed = parseFeatureFlagSemver(String(overrideValue), parsingPolicy)
319
+ const { lower, upper } = computeWildcardBounds(String(value), parsingPolicy)
320
+ return compareSemverTuples(overrideParsed, lower) >= 0 && compareSemverTuples(overrideParsed, upper) < 0
321
+ }
322
+ default:
323
+ throw new InconclusiveMatchError(`Unknown operator: ${operator}`)
324
+ }
325
+ }
326
+
327
+ export async function hashSHA1(text: string): Promise<string> {
328
+ const subtle = globalThis.crypto?.subtle
329
+ if (!subtle) throw new Error('SubtleCrypto API not available')
330
+
331
+ const hashBuffer = await subtle.digest('SHA-1', new TextEncoder().encode(text))
332
+ return Array.from(new Uint8Array(hashBuffer))
333
+ .map((byte) => byte.toString(16).padStart(2, '0'))
334
+ .join('')
335
+ }
336
+
337
+ export async function getFeatureFlagHash(key: string, bucketingValue: string, salt: string = ''): Promise<number> {
338
+ const hashString = await hashSHA1(`${key}.${bucketingValue}${salt}`)
339
+ return parseInt(hashString.slice(0, 15), 16) / LONG_SCALE
340
+ }
341
+
342
+ export function getFeatureFlagVariantLookupTable(
343
+ variants: readonly FeatureFlagVariant[]
344
+ ): FeatureFlagVariantLookupEntry[] {
345
+ const table: FeatureFlagVariantLookupEntry[] = []
346
+ let valueMin = 0
347
+ for (const variant of variants) {
348
+ const valueMax = valueMin + variant.rollout_percentage / 100.0
349
+ table.push({ valueMin, valueMax, key: variant.key })
350
+ valueMin = valueMax
351
+ }
352
+ return table
353
+ }
354
+
355
+ export async function getFeatureFlagVariant(
356
+ key: string,
357
+ bucketingValue: string,
358
+ variants: readonly FeatureFlagVariant[]
359
+ ): Promise<string | undefined> {
360
+ const hashValue = await getFeatureFlagHash(key, bucketingValue, 'variant')
361
+ return getFeatureFlagVariantLookupTable(variants).find(
362
+ (variant) => hashValue >= variant.valueMin && hashValue < variant.valueMax
363
+ )?.key
364
+ }
365
+
366
+ export function resolveFeatureFlagPayload(
367
+ payloads: Readonly<Record<string, JsonType | undefined>> | null | undefined,
368
+ flagValue: FeatureFlagValue | null | undefined
369
+ ): JsonType | null {
370
+ if (flagValue === false || flagValue === null || flagValue === undefined || !payloads) return null
371
+
372
+ const payloadKey = typeof flagValue === 'boolean' ? flagValue.toString() : flagValue
373
+ const payload = payloads[payloadKey] || null
374
+ return payload == null ? null : (parsePayload(payload) as JsonType)
375
+ }
@@ -7,6 +7,25 @@ export {
7
7
  MINIMAL_FLAG_CALLED_EVENT_CAMPAIGN_PROPERTIES,
8
8
  minimizeFlagCalledEventProperties,
9
9
  } from './featureFlagUtils'
10
+ export {
11
+ getFeatureFlagHash,
12
+ getFeatureFlagVariant,
13
+ getFeatureFlagVariantLookupTable,
14
+ hashSHA1,
15
+ InconclusiveMatchError,
16
+ matchFeatureFlagProperty,
17
+ parseFeatureFlagSemver,
18
+ relativeDateParseForFeatureFlagMatching,
19
+ resolveFeatureFlagPayload,
20
+ } from './featureFlagLocalEvaluation'
21
+ export type {
22
+ FeatureFlagProperty,
23
+ FeatureFlagPropertyValue,
24
+ FeatureFlagSemverParsingPolicy,
25
+ FeatureFlagVariant,
26
+ FeatureFlagVariantLookupEntry,
27
+ MatchFeatureFlagPropertyOptions,
28
+ } from './featureFlagLocalEvaluation'
10
29
  export {
11
30
  gzipCompress,
12
31
  isGzipData,
@@ -1,4 +1,5 @@
1
1
  import { PostHogPersistedProperty } from '../types'
2
+ import { createTestClient, PostHogCoreTestClient } from '../testing'
2
3
  import type { Logger } from '../types'
3
4
  import { PostHogLogs } from './index'
4
5
  import type { BufferedLogEntry, ResolvedPostHogLogsConfig } from './types'
@@ -80,6 +81,82 @@ const getContextFor = (instance: any) => (): { distinctId?: string; sessionId?:
80
81
  sessionId: instance.getSessionId() || undefined,
81
82
  })
82
83
 
84
+ // Drives a real core host rather than a stubbed `_sendLogsBatch`, so the sender's
85
+ // error classification and the queue bookkeeping are exercised together.
86
+ describe('PostHogLogs over the core sender', () => {
87
+ const createLogsOverCore = (status: number): { logs: PostHogLogs; client: PostHogCoreTestClient } => {
88
+ const [client, mocks] = createTestClient('TEST_API_KEY', {
89
+ fetchRetryCount: 0,
90
+ preloadFeatureFlags: false,
91
+ })
92
+ mocks.fetch.mockResolvedValue({
93
+ status,
94
+ text: () => Promise.resolve('err'),
95
+ json: () => Promise.resolve({ status: 'err' }),
96
+ })
97
+ const logs = new PostHogLogs(
98
+ client,
99
+ resolveForTest(),
100
+ createMockLogger(),
101
+ () => ({ distinctId: 'user-123' }),
102
+ immediateOnReady
103
+ )
104
+ return { logs, client }
105
+ }
106
+
107
+ const queueOf = (client: PostHogCoreTestClient): BufferedLogEntry[] =>
108
+ client.getPersistedProperty<BufferedLogEntry[]>(PostHogPersistedProperty.LogsQueue) ?? []
109
+
110
+ it.each([408, 429, 500, 503])('keeps records queued when the endpoint answers %i', async (status) => {
111
+ const { logs, client } = createLogsOverCore(status)
112
+ logs.captureLog({ body: 'keep me' })
113
+
114
+ await expect(logs.flush()).rejects.toHaveProperty('name', 'PostHogFetchHttpError')
115
+
116
+ expect(queueOf(client)).toHaveLength(1)
117
+ })
118
+
119
+ it('retains and resends records after transport retries are exhausted', async () => {
120
+ jest.useRealTimers()
121
+ const [client, mocks] = createTestClient('TEST_API_KEY', {
122
+ fetchRetryCount: 2,
123
+ fetchRetryDelay: 1,
124
+ preloadFeatureFlags: false,
125
+ })
126
+ const unavailableResponse = { status: 503, text: async () => 'unavailable', json: async () => ({}) }
127
+ mocks.fetch
128
+ .mockResolvedValueOnce(unavailableResponse)
129
+ .mockResolvedValueOnce(unavailableResponse)
130
+ .mockResolvedValueOnce(unavailableResponse)
131
+ .mockResolvedValueOnce({ status: 200, text: async () => 'ok', json: async () => ({}) })
132
+ const logs = new PostHogLogs(
133
+ client,
134
+ resolveForTest(),
135
+ createMockLogger(),
136
+ () => ({ distinctId: 'user-123' }),
137
+ immediateOnReady
138
+ )
139
+
140
+ logs.captureLog({ body: 'retry me' })
141
+ await expect(logs.flush()).rejects.toHaveProperty('name', 'PostHogFetchHttpError')
142
+ expect(mocks.fetch).toHaveBeenCalledTimes(3)
143
+ expect(queueOf(client)).toHaveLength(1)
144
+
145
+ await expect(logs.flush()).resolves.toBeUndefined()
146
+ expect(mocks.fetch).toHaveBeenCalledTimes(4)
147
+ expect(queueOf(client)).toHaveLength(0)
148
+ })
149
+
150
+ it('drops the batch when the endpoint answers 401', async () => {
151
+ const { logs, client } = createLogsOverCore(401)
152
+ logs.captureLog({ body: 'unauthorized' })
153
+
154
+ await expect(logs.flush()).rejects.toHaveProperty('name', 'PostHogFetchHttpError')
155
+
156
+ expect(queueOf(client)).toHaveLength(0)
157
+ })
158
+ })
159
+
83
160
  describe('PostHogLogs', () => {
84
161
  let mockInstance: any
85
162
  let logger: Logger
@@ -262,7 +262,7 @@ export class PostHogLogs {
262
262
  }
263
263
 
264
264
  if (outcome.kind === 'retry-later') {
265
- // Network error: keep records in the queue for the next flush cycle
265
+ // Transient failure: keep records in the queue for the next flush cycle
266
266
  // and surface the error so the caller can log/react.
267
267
  throw outcome.error
268
268
  }
@@ -17,6 +17,7 @@ import {
17
17
  FeatureFlagDetail,
18
18
  SurveyResponse,
19
19
  PostHogFetchResponse,
20
+ PostHogFetchBodyBytes,
20
21
  PostHogFetchOptions,
21
22
  PostHogPersistedProperty,
22
23
  PostHogQueueItem,
@@ -221,13 +222,12 @@ function isPostHogEventProperties(value: JsonType | undefined): value is PostHog
221
222
  }
222
223
 
223
224
  /**
224
- * Outcome of a logs batch send. Keeps HTTP error classification inside core
225
- * (single source of truth — same policy events already use in `_flush()`) so
225
+ * Outcome of a logs batch send. Keeps HTTP error classification inside core so
226
226
  * PostHogLogs doesn't need to know about specific error types.
227
227
  *
228
228
  * - ok → records are accepted; drop them from the queue
229
229
  * - too-large → 413; caller should halve batch size and retry same records
230
- * - retry-later → network error; caller keeps records and retries next cycle
230
+ * - retry-later → retryable network or HTTP error; caller keeps records and retries next cycle
231
231
  * - fatal → anything else (auth, malformed, etc.); caller drops the
232
232
  * batch and surfaces the error
233
233
  */
@@ -237,6 +237,17 @@ export type SendLogsBatchOutcome =
237
237
  | { kind: 'retry-later'; error: unknown }
238
238
  | { kind: 'fatal'; error: unknown }
239
239
 
240
+ /**
241
+ * Each signal keeps its own exported outcome type because each belongs to a
242
+ * separate host contract. The wrappers return this value directly, so one
243
+ * drifting out of shape fails to compile.
244
+ */
245
+ type SendOtlpBatchOutcome =
246
+ | { kind: 'ok' }
247
+ | { kind: 'too-large' }
248
+ | { kind: 'retry-later'; error: unknown }
249
+ | { kind: 'fatal'; error: unknown }
250
+
240
251
  export enum QuotaLimitedFeature {
241
252
  FeatureFlags = 'feature_flags',
242
253
  Recordings = 'recordings',
@@ -1447,7 +1458,7 @@ export abstract class PostHogCoreStateless {
1447
1458
  * Compresses an outgoing payload. Runtime-specific clients can override this
1448
1459
  * to avoid using the Web Streams compression implementation.
1449
1460
  */
1450
- protected compressPayload(payload: string): Promise<Blob | null> {
1461
+ protected compressPayload(payload: string): Promise<Blob | PostHogFetchBodyBytes | null> {
1451
1462
  return gzipCompress(payload, this.isDebug)
1452
1463
  }
1453
1464
 
@@ -1632,22 +1643,28 @@ export abstract class PostHogCoreStateless {
1632
1643
  }
1633
1644
 
1634
1645
  /**
1635
- * Sends a pre-built OTLP logs payload to `/i/v1/logs`. Returns a tagged
1636
- * outcome instead of throwing so PostHogLogs doesn't have to know about the
1637
- * core's error class hierarchy. Error classification lives here (single
1638
- * source of truth, same policy the events `_flush()` uses for its own
1639
- * 413 / network / fatal handling).
1646
+ * Shared implementation behind the OTLP senders, which differ only in path.
1647
+ * Returns a tagged outcome instead of throwing so the queue owners don't
1648
+ * have to know the core's error class hierarchy.
1640
1649
  *
1641
- * 413 is passed through as `too-large` (not auto-retried) so the caller can
1642
- * shrink `maxBatchRecordsPerPost` and retry the same records.
1650
+ * Exhausted 408/429/5xx stay `retry-later`, unlike the events `_flush()`
1651
+ * which drops anything that isn't a network error: every OTLP queue is
1652
+ * bounded and retried with backoff, so holding a batch through an outage can
1653
+ * neither grow without limit nor spin.
1643
1654
  */
1644
- async _sendLogsBatch(payload: OtlpLogsPayload): Promise<SendLogsBatchOutcome> {
1655
+ private async _sendOtlpBatch({
1656
+ path,
1657
+ payload,
1658
+ }: {
1659
+ path: 'logs' | 'metrics'
1660
+ payload: OtlpLogsPayload | OtlpMetricsPayload
1661
+ }): Promise<SendOtlpBatchOutcome> {
1645
1662
  if (this.disabled) {
1646
1663
  return { kind: 'fatal', error: new Error('The client is disabled') }
1647
1664
  }
1648
1665
 
1649
1666
  const serialized = JSON.stringify(payload)
1650
- const url = `${this.host}/i/v1/logs?token=${encodeURIComponent(this.apiKey)}`
1667
+ const url = `${this.host}/i/v1/${path}?token=${encodeURIComponent(this.apiKey)}`
1651
1668
 
1652
1669
  const gzippedPayload = !this.disableCompression ? await this.compressPayload(serialized) : null
1653
1670
  const fetchOptions: PostHogFetchOptions = {
@@ -1679,65 +1696,19 @@ export abstract class PostHogCoreStateless {
1679
1696
  if (isPostHogFetchContentTooLargeError(err)) {
1680
1697
  return { kind: 'too-large' }
1681
1698
  }
1682
- if (err instanceof PostHogFetchNetworkError) {
1699
+ if (isPostHogFetchRetryableError(err)) {
1683
1700
  return { kind: 'retry-later', error: err }
1684
1701
  }
1685
1702
  return { kind: 'fatal', error: err }
1686
1703
  }
1687
1704
  }
1688
1705
 
1689
- /**
1690
- * Sends a pre-built OTLP metrics payload to `/i/v1/metrics`. Same tagged
1691
- * outcome contract and error classification as `_sendLogsBatch` — this is
1692
- * the `MetricsHost._sendMetricsBatch` implementation, so `PostHogMetrics`
1693
- * can use any core-based SDK as its host.
1694
- */
1695
- async _sendMetricsBatch(payload: OtlpMetricsPayload): Promise<SendMetricsBatchOutcome> {
1696
- if (this.disabled) {
1697
- return { kind: 'fatal', error: new Error('The client is disabled') }
1698
- }
1699
-
1700
- const serialized = JSON.stringify(payload)
1701
- const url = `${this.host}/i/v1/metrics?token=${encodeURIComponent(this.apiKey)}`
1702
-
1703
- const gzippedPayload = !this.disableCompression ? await this.compressPayload(serialized) : null
1704
- const fetchOptions: PostHogFetchOptions = {
1705
- method: 'POST',
1706
- headers: {
1707
- ...this.getCustomHeaders(),
1708
- 'Content-Type': 'application/json',
1709
- ...(gzippedPayload !== null && { 'Content-Encoding': 'gzip' }),
1710
- },
1711
- body: gzippedPayload || serialized,
1712
- }
1706
+ async _sendLogsBatch(payload: OtlpLogsPayload): Promise<SendLogsBatchOutcome> {
1707
+ return this._sendOtlpBatch({ path: 'logs', payload })
1708
+ }
1713
1709
 
1714
- try {
1715
- await this.fetchWithRetry(
1716
- url,
1717
- fetchOptions,
1718
- { type: 'successful-write' },
1719
- {
1720
- retryCheck: (err) => {
1721
- if (isPostHogFetchContentTooLargeError(err)) {
1722
- return false
1723
- }
1724
- return isPostHogFetchRetryableError(err)
1725
- },
1726
- }
1727
- )
1728
- return { kind: 'ok' }
1729
- } catch (err) {
1730
- if (isPostHogFetchContentTooLargeError(err)) {
1731
- return { kind: 'too-large' }
1732
- }
1733
- // Exhausted retries on a retryable failure (network error, 408/429/5xx)
1734
- // still classify as retry-later so the window rides the next flush; only
1735
- // non-retryable HTTP errors (and 413 above) drop the batch.
1736
- if (isPostHogFetchRetryableError(err)) {
1737
- return { kind: 'retry-later', error: err }
1738
- }
1739
- return { kind: 'fatal', error: err }
1740
- }
1710
+ async _sendMetricsBatch(payload: OtlpMetricsPayload): Promise<SendMetricsBatchOutcome> {
1711
+ return this._sendOtlpBatch({ path: 'metrics', payload })
1741
1712
  }
1742
1713
 
1743
1714
  private fetchWithRetry<T>(
@@ -1766,12 +1737,16 @@ export abstract class PostHogCoreStateless {
1766
1737
  try {
1767
1738
  if (body instanceof Blob) {
1768
1739
  reqByteLength = body.size
1740
+ } else if (body instanceof Uint8Array) {
1741
+ reqByteLength = body.byteLength
1769
1742
  } else {
1770
1743
  reqByteLength = Buffer.byteLength(body, STRING_FORMAT)
1771
1744
  }
1772
1745
  } catch {
1773
1746
  if (body instanceof Blob) {
1774
1747
  reqByteLength = body.size
1748
+ } else if (body instanceof Uint8Array) {
1749
+ reqByteLength = body.byteLength
1775
1750
  } else {
1776
1751
  const encoded = new TextEncoder().encode(body)
1777
1752
  reqByteLength = encoded.length
@@ -18,3 +18,4 @@ export {
18
18
  } from './translations'
19
19
  export { canSurveyActivateRepeatedly, doesSurveyActivateByEvent, isSurveyIterationBased } from './activation'
20
20
  export { getSurveyIterationKey, isSurveyKeyForSurvey, type SurveyWithIteration } from './keys'
21
+ export { isMatchingRegex, isValidRegex, matchPropertyFilters, propertyComparisons } from './property-matching'