@adobe/spacecat-shared-tier-client 1.0.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/.mocha-multi.json +6 -0
- package/.releaserc.cjs +17 -0
- package/CHANGELOG.md +21 -0
- package/CODE_OF_CONDUCT.md +74 -0
- package/CONTRIBUTING.md +74 -0
- package/LICENSE.txt +263 -0
- package/README.md +221 -0
- package/package.json +51 -0
- package/src/index.d.ts +51 -0
- package/src/index.js +16 -0
- package/src/tier-client.js +228 -0
- package/test/setup-env.js +14 -0
- package/test/tier-client.test.js +424 -0
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@adobe/spacecat-shared-tier-client",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Shared modules of the Spacecat Services - Tier Client",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=22.0.0 <23.0.0",
|
|
8
|
+
"npm": ">=10.9.0 <12.0.0"
|
|
9
|
+
},
|
|
10
|
+
"main": "src/index.js",
|
|
11
|
+
"types": "src/index.d.ts",
|
|
12
|
+
"scripts": {
|
|
13
|
+
"test": "c8 mocha",
|
|
14
|
+
"lint": "eslint .",
|
|
15
|
+
"lint:fix": "eslint --fix .",
|
|
16
|
+
"clean": "rm -rf package-lock.json node_modules"
|
|
17
|
+
},
|
|
18
|
+
"mocha": {
|
|
19
|
+
"require": "test/setup-env.js",
|
|
20
|
+
"reporter": "mocha-multi-reporters",
|
|
21
|
+
"reporter-options": "configFile=.mocha-multi.json",
|
|
22
|
+
"spec": "test/**/*.test.js"
|
|
23
|
+
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://github.com/adobe/spacecat-shared.git"
|
|
27
|
+
},
|
|
28
|
+
"author": "",
|
|
29
|
+
"license": "Apache-2.0",
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/adobe/spacecat-shared/issues"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://github.com/adobe/spacecat-shared/packages/spacecat-shared-tier-client/#readme",
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@adobe/spacecat-shared-utils": "1.26.4",
|
|
39
|
+
"@adobe/spacecat-shared-data-access": "^2.61.3"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"c8": "10.1.3",
|
|
43
|
+
"chai": "5.3.3",
|
|
44
|
+
"chai-as-promised": "8.0.2",
|
|
45
|
+
"mocha": "11.7.2",
|
|
46
|
+
"mocha-multi-reporters": "1.5.1",
|
|
47
|
+
"sinon": "20.0.0",
|
|
48
|
+
"sinon-chai": "4.0.1",
|
|
49
|
+
"typescript": "5.9.2"
|
|
50
|
+
}
|
|
51
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export interface TierClientContext {
|
|
14
|
+
dataAccess: {
|
|
15
|
+
Entitlement: any;
|
|
16
|
+
SiteEnrollment: any;
|
|
17
|
+
Organization: any;
|
|
18
|
+
Site: any;
|
|
19
|
+
OrganizationIdentityProvider: any;
|
|
20
|
+
};
|
|
21
|
+
log: {
|
|
22
|
+
info: (message: string) => void;
|
|
23
|
+
error: (message: string) => void;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface TierClientResult {
|
|
28
|
+
entitlement?: any;
|
|
29
|
+
siteEnrollment?: any;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface Organization {
|
|
33
|
+
getId(): string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface Site {
|
|
37
|
+
getId(): string;
|
|
38
|
+
getOrganizationId?(): string;
|
|
39
|
+
getOrganization?(): Organization;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export declare class TierClient {
|
|
43
|
+
constructor(context: TierClientContext, organization: Organization, site: Site | null | undefined, productCode: string);
|
|
44
|
+
|
|
45
|
+
checkValidEntitlement(): Promise<TierClientResult>;
|
|
46
|
+
createEntitlement(tier: string): Promise<TierClientResult>;
|
|
47
|
+
|
|
48
|
+
static createForOrg(context: TierClientContext, organization: Organization, productCode: string): TierClient;
|
|
49
|
+
static createForSite(context: TierClientContext, site: Site, productCode: string): TierClient;
|
|
50
|
+
}
|
|
51
|
+
export { TierClient as default };
|
package/src/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import TierClient from './tier-client.js';
|
|
14
|
+
|
|
15
|
+
export { TierClient };
|
|
16
|
+
export default TierClient;
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { isNonEmptyObject, hasText } from '@adobe/spacecat-shared-utils';
|
|
14
|
+
import {
|
|
15
|
+
Site,
|
|
16
|
+
Entitlement as EntitlementModel,
|
|
17
|
+
Organization,
|
|
18
|
+
} from '@adobe/spacecat-shared-data-access';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* TierClient provides methods to manage entitlements and site enrollments.
|
|
22
|
+
*/
|
|
23
|
+
class TierClient {
|
|
24
|
+
/**
|
|
25
|
+
* Creates a TierClient for organization-only operations.
|
|
26
|
+
* @param {object} context - Context of the request.
|
|
27
|
+
* @param {object} organization - Organization object with getId() method.
|
|
28
|
+
* @param {string} productCode - Product code.
|
|
29
|
+
* @returns {TierClient} TierClient instance for organization operations.
|
|
30
|
+
*/
|
|
31
|
+
static createForOrg(context, organization, productCode) {
|
|
32
|
+
if (!(organization instanceof Organization)) {
|
|
33
|
+
throw new Error('Entity must be an instance of Organization');
|
|
34
|
+
}
|
|
35
|
+
if (!hasText(productCode)) {
|
|
36
|
+
throw new Error('Product code is required');
|
|
37
|
+
}
|
|
38
|
+
if (!isNonEmptyObject(context)) {
|
|
39
|
+
throw new Error('Context is required');
|
|
40
|
+
}
|
|
41
|
+
return new TierClient(context, organization, null, productCode);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Creates a TierClient for site-specific operations.
|
|
46
|
+
* @param {object} context - Context of the request.
|
|
47
|
+
* @param {object} site - Site object with getId() method.
|
|
48
|
+
* @param {string} productCode - Product code.
|
|
49
|
+
* @returns {TierClient} TierClient instance for site operations.
|
|
50
|
+
*/
|
|
51
|
+
static async createForSite(context, site, productCode) {
|
|
52
|
+
if (!(site instanceof Site)) {
|
|
53
|
+
throw new Error('Entity must be an instance of Site');
|
|
54
|
+
}
|
|
55
|
+
if (!hasText(productCode)) {
|
|
56
|
+
throw new Error('Product code is required');
|
|
57
|
+
}
|
|
58
|
+
if (!isNonEmptyObject(context)) {
|
|
59
|
+
throw new Error('Context is required');
|
|
60
|
+
}
|
|
61
|
+
const organization = await site.getOrganization();
|
|
62
|
+
return new TierClient(context, organization, site, productCode);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Creates a new TierClient instance.
|
|
67
|
+
* @param {object} context - Context of the request.
|
|
68
|
+
* @param {object} organization - Organization object with getId() method.
|
|
69
|
+
* @param {object} site - Site object with getId() method (optional for org-only operations).
|
|
70
|
+
* @param {string} productCode - Product code.
|
|
71
|
+
*/
|
|
72
|
+
constructor(context, organization, site, productCode) {
|
|
73
|
+
// Basic validation is now handled in static factory methods
|
|
74
|
+
const { dataAccess } = context;
|
|
75
|
+
|
|
76
|
+
const {
|
|
77
|
+
Entitlement: EntitlementCollection,
|
|
78
|
+
SiteEnrollment: SiteEnrollmentCollection,
|
|
79
|
+
Organization: OrganizationCollection,
|
|
80
|
+
Site: SiteCollection,
|
|
81
|
+
} = dataAccess;
|
|
82
|
+
|
|
83
|
+
const { log } = context;
|
|
84
|
+
|
|
85
|
+
// Store instance properties
|
|
86
|
+
this.context = context;
|
|
87
|
+
this.organization = organization;
|
|
88
|
+
this.site = site;
|
|
89
|
+
this.productCode = productCode;
|
|
90
|
+
this.log = log;
|
|
91
|
+
|
|
92
|
+
// Store dataAccess properties directly
|
|
93
|
+
this.Entitlement = EntitlementCollection;
|
|
94
|
+
this.SiteEnrollment = SiteEnrollmentCollection;
|
|
95
|
+
this.Organization = OrganizationCollection;
|
|
96
|
+
this.Site = SiteCollection;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Checks for valid entitlement on organization and valid site enrollment on site.
|
|
101
|
+
* @returns {Promise<object>} Object with entitlement and/or siteEnrollment based on what exists.
|
|
102
|
+
*/
|
|
103
|
+
async checkValidEntitlement() {
|
|
104
|
+
try {
|
|
105
|
+
const orgId = this.organization.getId();
|
|
106
|
+
this.log.info(`Checking for valid entitlement for org ${orgId} and product ${this.productCode}`);
|
|
107
|
+
|
|
108
|
+
const entitlement = await this.Entitlement
|
|
109
|
+
.findByOrganizationIdAndProductCode(orgId, this.productCode);
|
|
110
|
+
|
|
111
|
+
if (!entitlement) {
|
|
112
|
+
this.log.info(`No valid entitlement found for org ${orgId} and product ${this.productCode}`);
|
|
113
|
+
return {};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
this.log.info(`Found valid entitlement: ${entitlement.getId()}`);
|
|
117
|
+
|
|
118
|
+
// Only check for site enrollment if site is provided
|
|
119
|
+
if (this.site) {
|
|
120
|
+
const siteId = this.site.getId();
|
|
121
|
+
this.log.info(`Checking for valid site enrollment for site ${siteId} and entitlement ${entitlement.getId()}`);
|
|
122
|
+
|
|
123
|
+
const siteEnrollments = await this.SiteEnrollment.allBySiteId(siteId);
|
|
124
|
+
const validSiteEnrollment = siteEnrollments.find(
|
|
125
|
+
(se) => se.getEntitlementId() === entitlement.getId(),
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
if (!validSiteEnrollment) {
|
|
129
|
+
this.log.info(`No valid site enrollment found for site ${siteId} and entitlement ${entitlement.getId()}`);
|
|
130
|
+
return { entitlement };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
this.log.info(`Found valid site enrollment: ${validSiteEnrollment.getId()}`);
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
entitlement,
|
|
137
|
+
siteEnrollment: validSiteEnrollment,
|
|
138
|
+
};
|
|
139
|
+
} else {
|
|
140
|
+
this.log.info(`No site provided, returning entitlement only for org ${orgId}`);
|
|
141
|
+
return { entitlement };
|
|
142
|
+
}
|
|
143
|
+
} catch (error) {
|
|
144
|
+
this.log.error(`Error checking valid entitlement and site enrollment: ${error.message}`);
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Creates entitlement for organization and site enrollment for site.
|
|
151
|
+
* First validates that org and site don't already have an entitlement for this product.
|
|
152
|
+
* @param {string} tier - Entitlement tier.
|
|
153
|
+
* @returns {Promise<object>} Object with created entitlement and siteEnrollment.
|
|
154
|
+
*/
|
|
155
|
+
async createEntitlement(tier) {
|
|
156
|
+
try {
|
|
157
|
+
if (!Object.values(EntitlementModel.TIERS).includes(tier)) {
|
|
158
|
+
throw new Error(`Invalid tier: ${tier}. Valid tiers: ${Object.values(EntitlementModel.TIERS).join(', ')}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (!this.site) {
|
|
162
|
+
throw new Error('Site required for creating entitlements');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const orgId = this.organization.getId();
|
|
166
|
+
const siteId = this.site.getId();
|
|
167
|
+
this.log.info(`Creating entitlement for org ${orgId}, site ${siteId}, product ${this.productCode}, tier ${tier}`);
|
|
168
|
+
|
|
169
|
+
// Check what already exists
|
|
170
|
+
const existing = await this.checkValidEntitlement();
|
|
171
|
+
|
|
172
|
+
// If both entitlement and site enrollment exist, return them
|
|
173
|
+
if (existing.entitlement && existing.siteEnrollment) {
|
|
174
|
+
this.log.info(`Entitlement and site enrollment already exist for org ${orgId}, site ${siteId} and product ${this.productCode}`);
|
|
175
|
+
return existing;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// If only entitlement exists, we need to create site enrollment
|
|
179
|
+
if (existing.entitlement && !existing.siteEnrollment) {
|
|
180
|
+
this.log.info(`Entitlement exists but site enrollment missing for org ${orgId}, site ${siteId} and product ${this.productCode}`);
|
|
181
|
+
|
|
182
|
+
// Create site enrollment for existing entitlement
|
|
183
|
+
const siteEnrollment = await this.SiteEnrollment.create({
|
|
184
|
+
siteId,
|
|
185
|
+
entitlementId: existing.entitlement.getId(),
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
this.log.info(`Created site enrollment: ${siteEnrollment.getId()}`);
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
entitlement: existing.entitlement,
|
|
192
|
+
siteEnrollment,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Create entitlement
|
|
197
|
+
const entitlement = await this.Entitlement.create({
|
|
198
|
+
organizationId: orgId,
|
|
199
|
+
productCode: this.productCode,
|
|
200
|
+
tier,
|
|
201
|
+
quotas: {
|
|
202
|
+
llmo_trial_prompts: 200,
|
|
203
|
+
llmo_trial_prompts_consumed: 0,
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
this.log.info(`Created entitlement: ${entitlement.getId()}`);
|
|
208
|
+
|
|
209
|
+
// Create site enrollment
|
|
210
|
+
const siteEnrollment = await this.SiteEnrollment.create({
|
|
211
|
+
siteId,
|
|
212
|
+
entitlementId: entitlement.getId(),
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
this.log.info(`Created site enrollment: ${siteEnrollment.getId()}`);
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
entitlement,
|
|
219
|
+
siteEnrollment,
|
|
220
|
+
};
|
|
221
|
+
} catch (error) {
|
|
222
|
+
this.log.error(`Error creating entitlement and site enrollment: ${error.message}`);
|
|
223
|
+
throw error;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export default TierClient;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// Test setup file
|
|
14
|
+
export default {};
|