ad2app-lib 1.39.0 → 1.41.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.
@@ -2,8 +2,11 @@
2
2
  * Canonical publish-limits source of truth for ad2app (AD2-1140 Wave 1).
3
3
  *
4
4
  * Single source of truth for (a) the per-platform compose/publish limits
5
- * (character count, image/video counts, max video duration) and (b) the
6
- * universal media-upload byte cap ad2app itself enforces.
5
+ * (character count, image/video counts, max video duration), (b) the
6
+ * universal media-upload byte cap ad2app itself enforces, and (c) which
7
+ * platforms a PUBLISHED post can be deleted from through our vendor
8
+ * (UNPUBLISHABLE_PLATFORMS / canUnpublish, ad2app-backend#303), kept here
9
+ * because it is the same kind of per-platform publishing capability.
7
10
  *
8
11
  * Provenance:
9
12
  * - Per-platform values are the CORRECTED figures from
@@ -62,3 +65,16 @@ export declare const MAX_UPLOAD_BYTES: number;
62
65
  export declare const PUBLISH_LIMITS: Record<PublishPlatform, PlatformPublishLimits>;
63
66
  /** All publish platforms, canonical order (mirrors PUBLISH_LIMITS key order). */
64
67
  export declare const ALL_PUBLISH_PLATFORMS: PublishPlatform[];
68
+ /**
69
+ * Platforms where an already-PUBLISHED post can be deleted through our vendor
70
+ * (ad2app-backend#303). Mirrors the `platform` enum of Zernio's
71
+ * POST /v1/posts/{postId}/unpublish (openapi 1.0.4), intersected with ours.
72
+ * Instagram and TikTok are absent because the vendor does not support them:
73
+ * a post published there stays live until the creator deletes it in that app.
74
+ *
75
+ * An allowlist on purpose: a platform nobody has checked reads as "stays live",
76
+ * which is the claim that cannot mislead a creator into thinking a post is gone.
77
+ */
78
+ export declare const UNPUBLISHABLE_PLATFORMS: readonly PublishPlatform[];
79
+ /** Whether a published post on `platform` can be deleted from the platform by ad2app. */
80
+ export declare function canUnpublish(platform: string): boolean;
@@ -3,8 +3,11 @@
3
3
  * Canonical publish-limits source of truth for ad2app (AD2-1140 Wave 1).
4
4
  *
5
5
  * Single source of truth for (a) the per-platform compose/publish limits
6
- * (character count, image/video counts, max video duration) and (b) the
7
- * universal media-upload byte cap ad2app itself enforces.
6
+ * (character count, image/video counts, max video duration), (b) the
7
+ * universal media-upload byte cap ad2app itself enforces, and (c) which
8
+ * platforms a PUBLISHED post can be deleted from through our vendor
9
+ * (UNPUBLISHABLE_PLATFORMS / canUnpublish, ad2app-backend#303), kept here
10
+ * because it is the same kind of per-platform publishing capability.
8
11
  *
9
12
  * Provenance:
10
13
  * - Per-platform values are the CORRECTED figures from
@@ -51,7 +54,8 @@
51
54
  * cap — MAX_UPLOAD_BYTES is).
52
55
  */
53
56
  Object.defineProperty(exports, "__esModule", { value: true });
54
- exports.ALL_PUBLISH_PLATFORMS = exports.PUBLISH_LIMITS = exports.MAX_UPLOAD_BYTES = void 0;
57
+ exports.UNPUBLISHABLE_PLATFORMS = exports.ALL_PUBLISH_PLATFORMS = exports.PUBLISH_LIMITS = exports.MAX_UPLOAD_BYTES = void 0;
58
+ exports.canUnpublish = canUnpublish;
55
59
  /**
56
60
  * The universal media-upload byte cap ad2app enforces on every publish,
57
61
  * regardless of destination platform. This is ad2app's own operational
@@ -146,3 +150,27 @@ exports.PUBLISH_LIMITS = {
146
150
  };
147
151
  /** All publish platforms, canonical order (mirrors PUBLISH_LIMITS key order). */
148
152
  exports.ALL_PUBLISH_PLATFORMS = Object.keys(exports.PUBLISH_LIMITS);
153
+ /**
154
+ * Platforms where an already-PUBLISHED post can be deleted through our vendor
155
+ * (ad2app-backend#303). Mirrors the `platform` enum of Zernio's
156
+ * POST /v1/posts/{postId}/unpublish (openapi 1.0.4), intersected with ours.
157
+ * Instagram and TikTok are absent because the vendor does not support them:
158
+ * a post published there stays live until the creator deletes it in that app.
159
+ *
160
+ * An allowlist on purpose: a platform nobody has checked reads as "stays live",
161
+ * which is the claim that cannot mislead a creator into thinking a post is gone.
162
+ */
163
+ exports.UNPUBLISHABLE_PLATFORMS = [
164
+ 'twitter',
165
+ 'linkedin',
166
+ 'facebook',
167
+ 'youtube',
168
+ 'threads',
169
+ 'reddit',
170
+ 'pinterest',
171
+ 'bluesky',
172
+ ];
173
+ /** Whether a published post on `platform` can be deleted from the platform by ad2app. */
174
+ function canUnpublish(platform) {
175
+ return exports.UNPUBLISHABLE_PLATFORMS.includes(platform);
176
+ }
@@ -6,6 +6,17 @@
6
6
  */
7
7
  export type SchedulingSubscriptionTier = 'free' | 'starter' | 'pro';
8
8
  export type SchedulingSubscriptionStatus = 'active' | 'canceled' | 'expired';
9
+ /**
10
+ * Constructor input. Takes `maxAccounts`, or the deprecated `maxPlatforms` so
11
+ * code written against 1.40.0 keeps compiling; either way both fields are set.
12
+ */
13
+ export type SchedulingSubscriptionInfoInput = Pick<SchedulingSubscriptionInfoDTO, 'tier' | 'status' | 'expiresAt'> & ({
14
+ maxAccounts: number;
15
+ maxPlatforms?: number;
16
+ } | {
17
+ /** @deprecated Pass `maxAccounts` (ad2app-backend#301). */
18
+ maxPlatforms: number;
19
+ });
9
20
  /**
10
21
  * Current subscription state for the authenticated user.
11
22
  * Returned by GET /social/subscription.
@@ -14,8 +25,14 @@ export declare class SchedulingSubscriptionInfoDTO {
14
25
  tier: SchedulingSubscriptionTier;
15
26
  status: SchedulingSubscriptionStatus;
16
27
  expiresAt: string | null;
28
+ /** How many social accounts the plan lets the user connect (free 0, starter 4, pro 10). */
29
+ maxAccounts: number;
30
+ /**
31
+ * @deprecated The cap counts accounts, not platforms (ad2app-backend#301).
32
+ * Read `maxAccounts`. Always equal to it; removed in a later release.
33
+ */
17
34
  maxPlatforms: number;
18
- constructor(data: SchedulingSubscriptionInfoDTO);
35
+ constructor(data: SchedulingSubscriptionInfoInput);
19
36
  }
20
37
  /**
21
38
  * Response DTO for POST /social/subscription/checkout.
@@ -38,6 +55,16 @@ export interface SchedulingPlanPriceDTO {
38
55
  amount: number;
39
56
  currency: string;
40
57
  interval: string;
58
+ /**
59
+ * How many social accounts the plan lets the user connect. Optional only so
60
+ * 1.40.0-era literals still compile and because a backend older than this
61
+ * release omits it; read `maxAccounts ?? maxPlatforms`.
62
+ */
63
+ maxAccounts?: number;
64
+ /**
65
+ * @deprecated The cap counts accounts, not platforms (ad2app-backend#301).
66
+ * Read `maxAccounts`. Always equal to it; removed in a later release.
67
+ */
41
68
  maxPlatforms: number;
42
69
  }
43
70
  export declare class SchedulingProductPricesDTO {
@@ -7,17 +7,18 @@
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.SchedulingProUpgradeDTO = exports.SchedulingProductPricesDTO = exports.SchedulingPortalSessionResultDTO = exports.SchedulingCheckoutSessionResultDTO = exports.SchedulingSubscriptionInfoDTO = void 0;
10
- // ── SchedulingSubscriptionInfoDTO ────────────────────────────────────────────
11
10
  /**
12
11
  * Current subscription state for the authenticated user.
13
12
  * Returned by GET /social/subscription.
14
13
  */
15
14
  class SchedulingSubscriptionInfoDTO {
16
15
  constructor(data) {
16
+ const maxAccounts = 'maxAccounts' in data ? data.maxAccounts : data.maxPlatforms;
17
17
  this.tier = data.tier;
18
18
  this.status = data.status;
19
19
  this.expiresAt = data.expiresAt;
20
- this.maxPlatforms = data.maxPlatforms;
20
+ this.maxAccounts = maxAccounts;
21
+ this.maxPlatforms = maxAccounts;
21
22
  }
22
23
  }
23
24
  exports.SchedulingSubscriptionInfoDTO = SchedulingSubscriptionInfoDTO;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ad2app-lib",
3
- "version": "1.39.0",
3
+ "version": "1.41.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "commonjs",
@@ -9,7 +9,13 @@
9
9
  import assert from 'node:assert/strict';
10
10
  import { test } from 'node:test';
11
11
 
12
- import { ALL_PUBLISH_PLATFORMS, MAX_UPLOAD_BYTES, PUBLISH_LIMITS } from './index';
12
+ import {
13
+ ALL_PUBLISH_PLATFORMS,
14
+ canUnpublish,
15
+ MAX_UPLOAD_BYTES,
16
+ PUBLISH_LIMITS,
17
+ UNPUBLISHABLE_PLATFORMS,
18
+ } from './index';
13
19
  import type { PublishPlatform } from './types';
14
20
 
15
21
  const EXPECTED_PLATFORMS: PublishPlatform[] = [
@@ -114,3 +120,22 @@ test('PlatformPublishLimits contract does not carry per-platform byte-size field
114
120
  ].sort(),
115
121
  );
116
122
  });
123
+
124
+ test('canUnpublish: Instagram and TikTok published posts cannot be deleted by ad2app (backend#303)', () => {
125
+ assert.equal(canUnpublish('instagram'), false);
126
+ assert.equal(canUnpublish('tiktok'), false);
127
+ for (const platform of ALL_PUBLISH_PLATFORMS.filter((p) => p !== 'instagram' && p !== 'tiktok')) {
128
+ assert.equal(canUnpublish(platform), true, `${platform} should be unpublishable`);
129
+ }
130
+ });
131
+
132
+ test('canUnpublish: an unknown platform reads as not unpublishable', () => {
133
+ assert.equal(canUnpublish('snapchat'), false);
134
+ assert.equal(canUnpublish(''), false);
135
+ });
136
+
137
+ test('UNPUBLISHABLE_PLATFORMS only names platforms ad2app publishes to', () => {
138
+ for (const platform of UNPUBLISHABLE_PLATFORMS) {
139
+ assert.ok(platform in PUBLISH_LIMITS, `${platform} is not a publish platform`);
140
+ }
141
+ });
@@ -2,8 +2,11 @@
2
2
  * Canonical publish-limits source of truth for ad2app (AD2-1140 Wave 1).
3
3
  *
4
4
  * Single source of truth for (a) the per-platform compose/publish limits
5
- * (character count, image/video counts, max video duration) and (b) the
6
- * universal media-upload byte cap ad2app itself enforces.
5
+ * (character count, image/video counts, max video duration), (b) the
6
+ * universal media-upload byte cap ad2app itself enforces, and (c) which
7
+ * platforms a PUBLISHED post can be deleted from through our vendor
8
+ * (UNPUBLISHABLE_PLATFORMS / canUnpublish, ad2app-backend#303), kept here
9
+ * because it is the same kind of per-platform publishing capability.
7
10
  *
8
11
  * Provenance:
9
12
  * - Per-platform values are the CORRECTED figures from
@@ -152,3 +155,29 @@ export const PUBLISH_LIMITS: Record<PublishPlatform, PlatformPublishLimits> = {
152
155
  export const ALL_PUBLISH_PLATFORMS: PublishPlatform[] = Object.keys(
153
156
  PUBLISH_LIMITS,
154
157
  ) as PublishPlatform[];
158
+
159
+ /**
160
+ * Platforms where an already-PUBLISHED post can be deleted through our vendor
161
+ * (ad2app-backend#303). Mirrors the `platform` enum of Zernio's
162
+ * POST /v1/posts/{postId}/unpublish (openapi 1.0.4), intersected with ours.
163
+ * Instagram and TikTok are absent because the vendor does not support them:
164
+ * a post published there stays live until the creator deletes it in that app.
165
+ *
166
+ * An allowlist on purpose: a platform nobody has checked reads as "stays live",
167
+ * which is the claim that cannot mislead a creator into thinking a post is gone.
168
+ */
169
+ export const UNPUBLISHABLE_PLATFORMS: readonly PublishPlatform[] = [
170
+ 'twitter',
171
+ 'linkedin',
172
+ 'facebook',
173
+ 'youtube',
174
+ 'threads',
175
+ 'reddit',
176
+ 'pinterest',
177
+ 'bluesky',
178
+ ];
179
+
180
+ /** Whether a published post on `platform` can be deleted from the platform by ad2app. */
181
+ export function canUnpublish(platform: string): boolean {
182
+ return (UNPUBLISHABLE_PLATFORMS as readonly string[]).includes(platform);
183
+ }
@@ -15,6 +15,22 @@ export type SchedulingSubscriptionStatus = 'active' | 'canceled' | 'expired';
15
15
 
16
16
  // ── SchedulingSubscriptionInfoDTO ────────────────────────────────────────────
17
17
 
18
+ /**
19
+ * Constructor input. Takes `maxAccounts`, or the deprecated `maxPlatforms` so
20
+ * code written against 1.40.0 keeps compiling; either way both fields are set.
21
+ */
22
+ export type SchedulingSubscriptionInfoInput = Pick<
23
+ SchedulingSubscriptionInfoDTO,
24
+ 'tier' | 'status' | 'expiresAt'
25
+ > &
26
+ (
27
+ | { maxAccounts: number; maxPlatforms?: number }
28
+ | {
29
+ /** @deprecated Pass `maxAccounts` (ad2app-backend#301). */
30
+ maxPlatforms: number;
31
+ }
32
+ );
33
+
18
34
  /**
19
35
  * Current subscription state for the authenticated user.
20
36
  * Returned by GET /social/subscription.
@@ -23,13 +39,21 @@ export class SchedulingSubscriptionInfoDTO {
23
39
  tier: SchedulingSubscriptionTier;
24
40
  status: SchedulingSubscriptionStatus;
25
41
  expiresAt: string | null;
42
+ /** How many social accounts the plan lets the user connect (free 0, starter 4, pro 10). */
43
+ maxAccounts: number;
44
+ /**
45
+ * @deprecated The cap counts accounts, not platforms (ad2app-backend#301).
46
+ * Read `maxAccounts`. Always equal to it; removed in a later release.
47
+ */
26
48
  maxPlatforms: number;
27
49
 
28
- constructor(data: SchedulingSubscriptionInfoDTO) {
50
+ constructor(data: SchedulingSubscriptionInfoInput) {
51
+ const maxAccounts = 'maxAccounts' in data ? data.maxAccounts : data.maxPlatforms;
29
52
  this.tier = data.tier;
30
53
  this.status = data.status;
31
54
  this.expiresAt = data.expiresAt;
32
- this.maxPlatforms = data.maxPlatforms;
55
+ this.maxAccounts = maxAccounts;
56
+ this.maxPlatforms = maxAccounts;
33
57
  }
34
58
  }
35
59
 
@@ -68,6 +92,16 @@ export interface SchedulingPlanPriceDTO {
68
92
  amount: number;
69
93
  currency: string;
70
94
  interval: string;
95
+ /**
96
+ * How many social accounts the plan lets the user connect. Optional only so
97
+ * 1.40.0-era literals still compile and because a backend older than this
98
+ * release omits it; read `maxAccounts ?? maxPlatforms`.
99
+ */
100
+ maxAccounts?: number;
101
+ /**
102
+ * @deprecated The cap counts accounts, not platforms (ad2app-backend#301).
103
+ * Read `maxAccounts`. Always equal to it; removed in a later release.
104
+ */
71
105
  maxPlatforms: number;
72
106
  }
73
107
 
@@ -0,0 +1,75 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+
4
+ import { SchedulingPlanPriceDTO, SchedulingSubscriptionInfoDTO } from './I_SchedulingProUpgrade';
5
+
6
+ /**
7
+ * ad2app-backend#301 — the plan cap counts connected ACCOUNTS. `maxPlatforms`
8
+ * named the wrong unit on a public response, so `maxAccounts` replaces it and
9
+ * `maxPlatforms` stays, deprecated, with the identical value until readers
10
+ * (the MCP connector first) have moved.
11
+ */
12
+
13
+ test('subscription info carries maxAccounts', () => {
14
+ const info = new SchedulingSubscriptionInfoDTO({
15
+ tier: 'starter',
16
+ status: 'active',
17
+ expiresAt: null,
18
+ maxAccounts: 4,
19
+ });
20
+
21
+ assert.equal(info.maxAccounts, 4);
22
+ });
23
+
24
+ test('the deprecated maxPlatforms always equals maxAccounts', () => {
25
+ const info = new SchedulingSubscriptionInfoDTO({
26
+ tier: 'pro',
27
+ status: 'active',
28
+ expiresAt: null,
29
+ maxAccounts: 10,
30
+ });
31
+
32
+ assert.equal(info.maxPlatforms, 10);
33
+ });
34
+
35
+ test('a plan price carries both fields', () => {
36
+ const price: SchedulingPlanPriceDTO = {
37
+ tier: 'starter',
38
+ amount: 1600,
39
+ currency: 'USD',
40
+ interval: 'month',
41
+ maxAccounts: 4,
42
+ maxPlatforms: 4,
43
+ };
44
+
45
+ assert.equal(price.maxAccounts, price.maxPlatforms);
46
+ });
47
+
48
+ /**
49
+ * 1.41.0 is a minor release. Code written against 1.40.0 still passes
50
+ * `maxPlatforms` and builds plan-price literals without `maxAccounts`; both
51
+ * must keep compiling (backend and frontend master do exactly this).
52
+ */
53
+ test('the 1.40.0 constructor call still works and fills both fields', () => {
54
+ const info = new SchedulingSubscriptionInfoDTO({
55
+ tier: 'starter',
56
+ status: 'active',
57
+ expiresAt: null,
58
+ maxPlatforms: 4,
59
+ });
60
+
61
+ assert.equal(info.maxAccounts, 4);
62
+ assert.equal(info.maxPlatforms, 4);
63
+ });
64
+
65
+ test('a 1.40.0 plan-price literal without maxAccounts still type-checks', () => {
66
+ const price: SchedulingPlanPriceDTO = {
67
+ tier: 'pro',
68
+ amount: 3300,
69
+ currency: 'USD',
70
+ interval: 'month',
71
+ maxPlatforms: 10,
72
+ };
73
+
74
+ assert.equal(price.maxAccounts ?? price.maxPlatforms, 10);
75
+ });