ad2app-lib 1.20.0 → 1.21.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/api/utils.js CHANGED
@@ -4,9 +4,18 @@ exports.insertParams = exports.extractParams = exports.createQueryString = expor
4
4
  const concatApiPaths = (apiUrl, endpoint) => apiUrl + '/' + endpoint;
5
5
  exports.concatApiPaths = concatApiPaths;
6
6
  const createQueryString = (fetchParams) => {
7
- return fetchParams?.query
8
- ? '?' + new URLSearchParams(fetchParams.query).toString()
9
- : '';
7
+ if (!fetchParams?.query) {
8
+ return '';
9
+ }
10
+ // URLSearchParams stringifies missing values ({ platform: undefined } →
11
+ // "platform=undefined"), so optional params must be dropped before
12
+ // serialization. Falsy-but-real values (0, '', false) are kept.
13
+ const definedEntries = Object.entries(fetchParams.query).filter(([, value]) => value !== undefined && value !== null);
14
+ if (definedEntries.length === 0) {
15
+ return '';
16
+ }
17
+ return ('?' +
18
+ new URLSearchParams(definedEntries.map(([key, value]) => [key, String(value)])).toString());
10
19
  };
11
20
  exports.createQueryString = createQueryString;
12
21
  const extractParams = (path) => {
@@ -23,6 +23,12 @@ export declare class SchedulingAnalyticsKpiDTO {
23
23
  views: number;
24
24
  engagementRate: number;
25
25
  followerGrowth: number;
26
+ /**
27
+ * Range growth percentage derived from Zernio's authoritative growth
28
+ * totals (growth ÷ range-start followers — 050 FR-10 fast-follow).
29
+ * Absent when the range-start base is unknown or zero; never fabricated.
30
+ */
31
+ followerGrowthPercentage?: number;
26
32
  /**
27
33
  * Sources that were unavailable when this response was assembled
28
34
  * (AD2-1045). Absent/empty = all sources healthy. When present, the
@@ -31,6 +31,9 @@ class SchedulingAnalyticsKpiDTO {
31
31
  this.views = data.views;
32
32
  this.engagementRate = data.engagementRate;
33
33
  this.followerGrowth = data.followerGrowth;
34
+ if (data.followerGrowthPercentage !== undefined) {
35
+ this.followerGrowthPercentage = data.followerGrowthPercentage;
36
+ }
34
37
  if (data.failedSources !== undefined) {
35
38
  this.failedSources = data.failedSources;
36
39
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ad2app-lib",
3
- "version": "1.20.0",
3
+ "version": "1.21.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "commonjs",
@@ -47,7 +47,7 @@
47
47
  "prepare": "npm run build"
48
48
  },
49
49
  "keywords": [],
50
- "author": "Maciej G\u00f3rski@ad2.app",
50
+ "author": "Maciej Górski@ad2.app",
51
51
  "license": "ISC",
52
52
  "description": "Package to share types and utils across the ad2app projects",
53
53
  "dependencies": {
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Unit tests for createQueryString. Covers the AD2 serialization bug found
3
+ * 2026-07-26: URLSearchParams stringifies undefined/null values, so callers
4
+ * passing optional params ({ platform: undefined }) produced live requests
5
+ * carrying the literal strings "?platform=undefined" / "null".
6
+ */
7
+ import assert from "node:assert/strict";
8
+ import { test } from "node:test";
9
+
10
+ import { createQueryString } from "./utils";
11
+
12
+ test("serializes defined params", () => {
13
+ assert.equal(
14
+ createQueryString({ query: { platform: "instagram", limit: 20 } }),
15
+ "?platform=instagram&limit=20"
16
+ );
17
+ });
18
+
19
+ test("omits undefined values instead of serializing the string 'undefined'", () => {
20
+ assert.equal(
21
+ createQueryString({
22
+ query: { fromDate: "2026-06-28", platform: undefined, contentType: undefined },
23
+ }),
24
+ "?fromDate=2026-06-28"
25
+ );
26
+ });
27
+
28
+ test("omits null values instead of serializing the string 'null'", () => {
29
+ assert.equal(
30
+ createQueryString({ query: { platform: null, status: "scheduled" } }),
31
+ "?status=scheduled"
32
+ );
33
+ });
34
+
35
+ test("returns empty string when every value is undefined", () => {
36
+ assert.equal(createQueryString({ query: { platform: undefined } }), "");
37
+ });
38
+
39
+ test("returns empty string with no query at all", () => {
40
+ assert.equal(createQueryString(), "");
41
+ assert.equal(createQueryString({}), "");
42
+ });
43
+
44
+ test("keeps falsy-but-real values (0, empty string, false)", () => {
45
+ assert.equal(
46
+ createQueryString({ query: { offset: 0, q: "", includeRead: false } }),
47
+ "?offset=0&q=&includeRead=false"
48
+ );
49
+ });
package/src/api/utils.ts CHANGED
@@ -4,9 +4,27 @@ export const concatApiPaths = (apiUrl: string, endpoint: string) =>
4
4
  apiUrl + '/' + endpoint;
5
5
 
6
6
  export const createQueryString = <T>(fetchParams?: FetchParams<T>) => {
7
- return fetchParams?.query
8
- ? '?' + new URLSearchParams(fetchParams.query as URLSearchParams).toString()
9
- : '';
7
+ if (!fetchParams?.query) {
8
+ return '';
9
+ }
10
+
11
+ // URLSearchParams stringifies missing values ({ platform: undefined } →
12
+ // "platform=undefined"), so optional params must be dropped before
13
+ // serialization. Falsy-but-real values (0, '', false) are kept.
14
+ const definedEntries = Object.entries(
15
+ fetchParams.query as Record<string, unknown>
16
+ ).filter(([, value]) => value !== undefined && value !== null);
17
+
18
+ if (definedEntries.length === 0) {
19
+ return '';
20
+ }
21
+
22
+ return (
23
+ '?' +
24
+ new URLSearchParams(
25
+ definedEntries.map(([key, value]) => [key, String(value)])
26
+ ).toString()
27
+ );
10
28
  };
11
29
 
12
30
  export const extractParams = (path: string): string[] => {
@@ -35,6 +35,12 @@ export class SchedulingAnalyticsKpiDTO {
35
35
  views: number;
36
36
  engagementRate: number;
37
37
  followerGrowth: number;
38
+ /**
39
+ * Range growth percentage derived from Zernio's authoritative growth
40
+ * totals (growth ÷ range-start followers — 050 FR-10 fast-follow).
41
+ * Absent when the range-start base is unknown or zero; never fabricated.
42
+ */
43
+ followerGrowthPercentage?: number;
38
44
  /**
39
45
  * Sources that were unavailable when this response was assembled
40
46
  * (AD2-1045). Absent/empty = all sources healthy. When present, the
@@ -64,6 +70,9 @@ export class SchedulingAnalyticsKpiDTO {
64
70
  this.views = data.views;
65
71
  this.engagementRate = data.engagementRate;
66
72
  this.followerGrowth = data.followerGrowth;
73
+ if (data.followerGrowthPercentage !== undefined) {
74
+ this.followerGrowthPercentage = data.followerGrowthPercentage;
75
+ }
67
76
  if (data.failedSources !== undefined) {
68
77
  this.failedSources = data.failedSources;
69
78
  }