@hiclimba/client 0.0.2

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/README.md ADDED
@@ -0,0 +1,106 @@
1
+ # @hiclimba/client
2
+
3
+ Toasty API SDK for product backends and customer-scoped integrations.
4
+
5
+ ```bash
6
+ npm install @hiclimba/client
7
+ ```
8
+
9
+ ## Server Mode
10
+
11
+ Server mode uses Toasty app HMAC credentials. Keep it on the product backend.
12
+
13
+ ```ts
14
+ import { ToastyClient } from '@hiclimba/client';
15
+
16
+ const toasty = new ToastyClient({
17
+ appId: process.env.TOASTY_APP_ID!,
18
+ keyId: process.env.TOASTY_KEY_ID!,
19
+ secret: process.env.TOASTY_SECRET!,
20
+ apiUrl: process.env.TOASTY_API_URL!,
21
+ });
22
+
23
+ const identified = await toasty.identify({
24
+ shop: 'merchant.myshopify.com',
25
+ shopifyShopGid: 'gid://shopify/Shop/123',
26
+ });
27
+ ```
28
+
29
+ `identify()` returns `customerApiToken`. It is a random Toasty app-installation
30
+ lookup token. It is not signed, encrypted, expiring, rotatable, privileged app
31
+ auth, or any kind of Shopify token.
32
+
33
+ ## Customer Mode
34
+
35
+ Customer mode uses the Toasty app ID plus the `customerApiToken` returned by
36
+ `identify()`.
37
+
38
+ ```ts
39
+ const toasty = new ToastyClient({
40
+ appId: '<toasty-app-id>',
41
+ customerApiToken: '<customer-api-token>',
42
+ apiUrl: 'https://api.toasty.dev',
43
+ });
44
+
45
+ const customer = await toasty.getCustomer();
46
+ const enabled = await toasty.isFeatureEnabled('api_access');
47
+ await toasty.sendUsageEvent({
48
+ eventName: 'order_processed',
49
+ idempotencyKey: 'order_123',
50
+ });
51
+ ```
52
+
53
+ Customer mode sends:
54
+
55
+ ```http
56
+ X-Toasty-App-Id: app_...
57
+ Authorization: Bearer custok_...
58
+ ```
59
+
60
+ ## Top-Level Methods
61
+
62
+ - `identify`
63
+ - `getCustomer`
64
+ - `evaluateFeature`
65
+ - `isFeatureEnabled`
66
+ - `limitForFeature`
67
+ - `subscribe`
68
+ - `cancelSubscription`
69
+ - `updateSubscription`
70
+ - `createOneTimeCharge`
71
+ - `sendUsageEvent`
72
+ - `sendUsageEvents`
73
+ - `getUsageMetricReport`
74
+
75
+ The SDK intentionally has no Mantle-style nested namespaces such as
76
+ `client.usage.*`, `client.subscriptions.*`, or `client.customers.*`.
77
+
78
+ ## Building
79
+
80
+ Run `npm exec -- nx run toasty-client:build` to build the library.
81
+
82
+ Run `npm exec -- nx run toasty-client:build:production` to build the local npm
83
+ publish artifact in `packages/toasty/client/dist`.
84
+
85
+ Run `npm exec -- nx run toasty-client:verify-publish-artifact` before release.
86
+
87
+ ## Running unit tests
88
+
89
+ Run `npm exec -- nx run toasty-client:test` to execute the unit tests via Jest.
90
+
91
+ ## Publishing
92
+
93
+ The source package remains private. Npm publishing uses only
94
+ `packages/toasty/client/dist` as the package root.
95
+
96
+ Dry-run the publish artifact:
97
+
98
+ ```bash
99
+ npm exec -- nx release publish --projects=toasty-client --dry-run --access public
100
+ ```
101
+
102
+ Publish after versioning and verification:
103
+
104
+ ```bash
105
+ npm exec -- nx release publish --projects=toasty-client --access public
106
+ ```
package/index.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './lib/toasty-client.js';
2
+ export type * from './lib/toasty-client.types.js';
package/index.js ADDED
@@ -0,0 +1 @@
1
+ export * from './lib/toasty-client.js';
@@ -0,0 +1,90 @@
1
+ import type { ICancelSubscriptionInput, ICancelSubscriptionResponse, ICreateOneTimeChargeInput, ICreateOneTimeChargeResponse, ICreateSubscriptionInput, ICreateSubscriptionResponse, IGetCustomerResponse, IGetUsageMetricReportInput, IGetUsageMetricReportResponse, IIdentifyInput, IIdentifyResponse, IPublicCustomerDto, IPublicCustomerFeatureDto, IPublicCustomerPlanDto, IPublicCustomerSubscriptionDto, IPublicCustomerUsageMetricDto, ITrackUsageEventBatchInput, ITrackUsageEventBatchResponse, ITrackUsageEventInput, ITrackUsageEventResponse, IUpdateSubscriptionInput, IUpdateSubscriptionResponse, IPlanDiscount } from './toasty-client.types.js';
2
+ export type Customer = IPublicCustomerDto;
3
+ export type Plan = IPublicCustomerPlanDto;
4
+ export type Subscription = IPublicCustomerSubscriptionDto;
5
+ export type Feature = IPublicCustomerFeatureDto;
6
+ export type UsageMetric = IPublicCustomerUsageMetricDto;
7
+ export type UsageCharge = ICreateOneTimeChargeResponse;
8
+ export type Discount = IPlanDiscount;
9
+ export type AppliedDiscount = IPlanDiscount;
10
+ export type UsageEvent = ITrackUsageEventResponse;
11
+ export type SubscribeParams = ICreateSubscriptionInput;
12
+ export interface IToastyServerClientConfig {
13
+ appId: string;
14
+ keyId: string;
15
+ secret: string;
16
+ apiUrl: string;
17
+ fetch?: typeof fetch;
18
+ allowBrowserServerCredentials?: boolean;
19
+ }
20
+ export interface IToastyCustomerClientConfig {
21
+ appId: string;
22
+ customerApiToken: string;
23
+ apiUrl: string;
24
+ fetch?: typeof fetch;
25
+ }
26
+ export type IToastyClientConfig = IToastyServerClientConfig | IToastyCustomerClientConfig;
27
+ export interface ICreateToastyHmacHeadersInput {
28
+ appId: string;
29
+ keyId: string;
30
+ secret: string;
31
+ method: string;
32
+ pathWithQuery: string;
33
+ timestamp?: string;
34
+ rawBody?: Buffer;
35
+ }
36
+ export interface ICreateToastyHmacHeadersResult {
37
+ canonicalString: string;
38
+ headers: Record<string, string>;
39
+ }
40
+ export interface IToastyError {
41
+ name: 'ToastyError';
42
+ status: number;
43
+ code: string;
44
+ message: string;
45
+ responseBody?: unknown;
46
+ }
47
+ export type ToastyError = IToastyError;
48
+ type ToastyResult<T> = Promise<T | ToastyError>;
49
+ export declare function sha256HexRawBody(rawBody?: Buffer): string;
50
+ export declare function buildToastyHmacCanonicalString(input: {
51
+ method: string;
52
+ pathWithQuery: string;
53
+ timestamp: string;
54
+ rawBody?: Buffer;
55
+ }): string;
56
+ export declare function deriveToastyCredentialSigningKey(input: {
57
+ rawSecret: string;
58
+ appPublicId: string;
59
+ keyId: string;
60
+ }): Buffer;
61
+ export declare function signToastyHmacCanonicalString(canonicalString: string, signingKey: Buffer): string;
62
+ export declare function createToastyHmacHeaders(input: ICreateToastyHmacHeadersInput): ICreateToastyHmacHeadersResult;
63
+ export declare function getPathWithQuery(url: URL): string;
64
+ export declare function isToastyError(value: unknown): value is ToastyError;
65
+ export declare class ToastyClient {
66
+ private readonly config;
67
+ private readonly apiUrl;
68
+ private readonly fetchImpl;
69
+ constructor(config: IToastyClientConfig);
70
+ identify(input: IIdentifyInput): ToastyResult<IIdentifyResponse>;
71
+ getCustomer(input?: {
72
+ customerId?: string;
73
+ }): ToastyResult<IGetCustomerResponse>;
74
+ evaluateFeature(featureKey: string): ToastyResult<Feature | null>;
75
+ isFeatureEnabled(featureKey: string): ToastyResult<boolean>;
76
+ limitForFeature(featureKey: string): ToastyResult<number | null>;
77
+ subscribe(input: ICreateSubscriptionInput): ToastyResult<ICreateSubscriptionResponse>;
78
+ cancelSubscription(input: ICancelSubscriptionInput): ToastyResult<ICancelSubscriptionResponse>;
79
+ updateSubscription(input: IUpdateSubscriptionInput): ToastyResult<IUpdateSubscriptionResponse>;
80
+ createOneTimeCharge(input: ICreateOneTimeChargeInput): ToastyResult<ICreateOneTimeChargeResponse>;
81
+ sendUsageEvent(input: ITrackUsageEventInput): ToastyResult<ITrackUsageEventResponse>;
82
+ sendUsageEvents(input: ITrackUsageEventBatchInput): ToastyResult<ITrackUsageEventBatchResponse>;
83
+ getUsageMetricReport(input: IGetUsageMetricReportInput): ToastyResult<IGetUsageMetricReportResponse>;
84
+ private get;
85
+ private post;
86
+ private requestJson;
87
+ private createAuthHeaders;
88
+ private assertServerMode;
89
+ }
90
+ export {};
@@ -0,0 +1,285 @@
1
+ import { createHash, createHmac, hkdfSync } from 'node:crypto';
2
+ const HMAC_SIGNATURE_PREFIX = 'v1=';
3
+ const SIGNING_KEY_INFO = 'toasty-hmac-signing-key-v1';
4
+ export function sha256HexRawBody(rawBody) {
5
+ return createHash('sha256')
6
+ .update(rawBody ?? Buffer.alloc(0))
7
+ .digest('hex');
8
+ }
9
+ export function buildToastyHmacCanonicalString(input) {
10
+ return [
11
+ input.method.toUpperCase(),
12
+ input.pathWithQuery,
13
+ input.timestamp,
14
+ sha256HexRawBody(input.rawBody),
15
+ ].join('\n');
16
+ }
17
+ export function deriveToastyCredentialSigningKey(input) {
18
+ return Buffer.from(hkdfSync('sha256', Buffer.from(input.rawSecret, 'utf8'), Buffer.from(`${input.appPublicId}:${input.keyId}`, 'utf8'), Buffer.from(SIGNING_KEY_INFO, 'utf8'), 32));
19
+ }
20
+ export function signToastyHmacCanonicalString(canonicalString, signingKey) {
21
+ const digest = createHmac('sha256', signingKey)
22
+ .update(canonicalString)
23
+ .digest('hex');
24
+ return `${HMAC_SIGNATURE_PREFIX}${digest}`;
25
+ }
26
+ export function createToastyHmacHeaders(input) {
27
+ const timestamp = input.timestamp ?? new Date().toISOString();
28
+ const canonicalString = buildToastyHmacCanonicalString({
29
+ method: input.method,
30
+ pathWithQuery: input.pathWithQuery,
31
+ timestamp,
32
+ ...(input.rawBody ? { rawBody: input.rawBody } : {}),
33
+ });
34
+ const signingKey = deriveToastyCredentialSigningKey({
35
+ rawSecret: input.secret,
36
+ appPublicId: input.appId,
37
+ keyId: input.keyId,
38
+ });
39
+ return {
40
+ canonicalString,
41
+ headers: {
42
+ 'x-toasty-app-id': input.appId,
43
+ 'x-toasty-key-id': input.keyId,
44
+ 'x-toasty-timestamp': timestamp,
45
+ 'x-toasty-signature': signToastyHmacCanonicalString(canonicalString, signingKey),
46
+ },
47
+ };
48
+ }
49
+ export function getPathWithQuery(url) {
50
+ return `${url.pathname}${url.search}`;
51
+ }
52
+ export function isToastyError(value) {
53
+ return (typeof value === 'object' &&
54
+ value !== null &&
55
+ value.name === 'ToastyError');
56
+ }
57
+ export class ToastyClient {
58
+ config;
59
+ apiUrl;
60
+ fetchImpl;
61
+ constructor(config) {
62
+ assertConfigValue('appId', config.appId);
63
+ assertConfigValue('apiUrl', config.apiUrl);
64
+ if (isServerConfig(config)) {
65
+ assertConfigValue('keyId', config.keyId);
66
+ assertConfigValue('secret', config.secret);
67
+ if (isBrowserRuntime() && !config.allowBrowserServerCredentials) {
68
+ throw new TypeError('Server credential mode cannot be used in browser runtimes');
69
+ }
70
+ }
71
+ else {
72
+ assertConfigValue('customerApiToken', config.customerApiToken);
73
+ }
74
+ this.config = config;
75
+ this.apiUrl = normalizeApiUrl(config.apiUrl);
76
+ this.fetchImpl = config.fetch ?? fetch;
77
+ }
78
+ identify(input) {
79
+ this.assertServerMode('identify');
80
+ assertNoShopifyTokenFields(input);
81
+ return this.post('/v1/identify', input);
82
+ }
83
+ getCustomer(input = {}) {
84
+ if (isServerConfig(this.config)) {
85
+ if (!input.customerId) {
86
+ throw new TypeError('customerId is required in server mode');
87
+ }
88
+ return this.get(`/v1/customers/${encodeURIComponent(input.customerId)}`);
89
+ }
90
+ return this.get('/v1/customer');
91
+ }
92
+ async evaluateFeature(featureKey) {
93
+ assertConfigValue('featureKey', featureKey);
94
+ const response = await this.getCustomer();
95
+ if (isToastyError(response)) {
96
+ return response;
97
+ }
98
+ return response.customer.features[featureKey] ?? null;
99
+ }
100
+ async isFeatureEnabled(featureKey) {
101
+ const feature = await this.evaluateFeature(featureKey);
102
+ if (isToastyError(feature)) {
103
+ return feature;
104
+ }
105
+ if (!feature) {
106
+ return false;
107
+ }
108
+ return featureValueEnabled(feature.value);
109
+ }
110
+ async limitForFeature(featureKey) {
111
+ const feature = await this.evaluateFeature(featureKey);
112
+ if (isToastyError(feature)) {
113
+ return feature;
114
+ }
115
+ if (!feature) {
116
+ return null;
117
+ }
118
+ return featureLimit(feature.value);
119
+ }
120
+ subscribe(input) {
121
+ return this.post('/v1/subscriptions', input);
122
+ }
123
+ cancelSubscription(input) {
124
+ return this.post('/v1/subscriptions/cancel', input);
125
+ }
126
+ updateSubscription(input) {
127
+ return this.post('/v1/subscriptions/update', input);
128
+ }
129
+ createOneTimeCharge(input) {
130
+ return this.post('/v1/one-time-charges', input);
131
+ }
132
+ sendUsageEvent(input) {
133
+ return this.post('/v1/usage-events', input);
134
+ }
135
+ sendUsageEvents(input) {
136
+ return this.post('/v1/usage-events/batch', input);
137
+ }
138
+ getUsageMetricReport(input) {
139
+ return this.get('/v1/usage-metrics/report', input);
140
+ }
141
+ get(path, query) {
142
+ return this.requestJson('GET', path, query ? { query } : {});
143
+ }
144
+ post(path, input) {
145
+ return this.requestJson('POST', path, {
146
+ body: JSON.stringify(input),
147
+ });
148
+ }
149
+ async requestJson(method, path, options = {}) {
150
+ const url = createRequestUrl(this.apiUrl, path, options.query);
151
+ const rawBody = Buffer.from(options.body ?? '', 'utf8');
152
+ const headers = {
153
+ accept: 'application/json',
154
+ ...this.createAuthHeaders(method, getPathWithQuery(url), rawBody),
155
+ };
156
+ if (options.body !== undefined) {
157
+ headers['content-type'] = 'application/json';
158
+ }
159
+ const response = await this.fetchImpl(url.toString(), {
160
+ method,
161
+ headers,
162
+ ...(options.body !== undefined ? { body: options.body } : {}),
163
+ });
164
+ const responseBody = await readResponseBody(response);
165
+ if (!response.ok) {
166
+ return toToastyError(response.status, responseBody);
167
+ }
168
+ return responseBody;
169
+ }
170
+ createAuthHeaders(method, pathWithQuery, rawBody) {
171
+ if (isServerConfig(this.config)) {
172
+ return createToastyHmacHeaders({
173
+ appId: this.config.appId,
174
+ keyId: this.config.keyId,
175
+ secret: this.config.secret,
176
+ method,
177
+ pathWithQuery,
178
+ rawBody,
179
+ }).headers;
180
+ }
181
+ return {
182
+ 'x-toasty-app-id': this.config.appId,
183
+ authorization: `Bearer ${this.config.customerApiToken}`,
184
+ };
185
+ }
186
+ assertServerMode(methodName) {
187
+ if (!isServerConfig(this.config)) {
188
+ throw new TypeError(`${methodName} requires server credential mode`);
189
+ }
190
+ }
191
+ }
192
+ function isServerConfig(config) {
193
+ return 'keyId' in config || 'secret' in config;
194
+ }
195
+ function assertConfigValue(name, value) {
196
+ if (!value.trim()) {
197
+ throw new TypeError(`${name} is required`);
198
+ }
199
+ }
200
+ function normalizeApiUrl(value) {
201
+ const url = new URL(value);
202
+ url.search = '';
203
+ url.hash = '';
204
+ url.pathname = url.pathname.replace(/\/+$/g, '');
205
+ return url.toString();
206
+ }
207
+ function createRequestUrl(apiUrl, path, query) {
208
+ const url = new URL(apiUrl);
209
+ const basePath = url.pathname.replace(/\/+$/g, '');
210
+ url.pathname = `${basePath}${path}`;
211
+ url.search = '';
212
+ if (query) {
213
+ for (const [key, value] of Object.entries(query)) {
214
+ if (value !== undefined) {
215
+ url.searchParams.append(key, String(value));
216
+ }
217
+ }
218
+ }
219
+ return url;
220
+ }
221
+ async function readResponseBody(response) {
222
+ const text = await response.text();
223
+ if (!text) {
224
+ return undefined;
225
+ }
226
+ try {
227
+ return JSON.parse(text);
228
+ }
229
+ catch {
230
+ return text;
231
+ }
232
+ }
233
+ function toToastyError(status, responseBody) {
234
+ const body = typeof responseBody === 'object' && responseBody !== null
235
+ ? responseBody
236
+ : {};
237
+ const code = typeof body.error === 'string'
238
+ ? body.error
239
+ : typeof body.code === 'string'
240
+ ? body.code
241
+ : 'toasty_api_error';
242
+ return {
243
+ name: 'ToastyError',
244
+ status,
245
+ code,
246
+ message: code,
247
+ ...(responseBody !== undefined ? { responseBody } : {}),
248
+ };
249
+ }
250
+ function isBrowserRuntime() {
251
+ return (typeof globalThis === 'object' &&
252
+ 'window' in globalThis &&
253
+ 'document' in globalThis);
254
+ }
255
+ function assertNoShopifyTokenFields(input) {
256
+ const value = input;
257
+ for (const key of [
258
+ 'accessToken',
259
+ 'refreshToken',
260
+ 'accessTokenExpiresAt',
261
+ 'offlineAccessToken',
262
+ 'shopifyOfflineAccessToken',
263
+ 'shopifyRefreshToken',
264
+ ]) {
265
+ if (key in value) {
266
+ throw new TypeError(`${key} is not accepted by identify`);
267
+ }
268
+ }
269
+ }
270
+ function featureValueEnabled(value) {
271
+ if (value.type === 'boolean') {
272
+ return value.value;
273
+ }
274
+ if (value.type === 'string') {
275
+ return Boolean(value.value);
276
+ }
277
+ return value.value !== 0;
278
+ }
279
+ function featureLimit(value) {
280
+ if (value.type === 'limit' ||
281
+ value.type === 'limit_with_overage') {
282
+ return value.value;
283
+ }
284
+ return null;
285
+ }
@@ -0,0 +1,294 @@
1
+ export type CurrencyCode = 'USD' | 'EUR' | 'GBP' | 'CAD' | 'AUD';
2
+ export type LocaleCode = string;
3
+ export interface ILocalizedText {
4
+ fallbackLocale: LocaleCode;
5
+ values: Partial<Record<LocaleCode, string>>;
6
+ }
7
+ export type BillingProvider = 'shopify_billing_api' | 'shopify_app_pricing';
8
+ export type SubscriptionStatus = 'pending_approval' | 'trialing' | 'active' | 'frozen' | 'cancelled' | 'declined' | 'expired' | 'failed';
9
+ export type AppInstallationStatus = 'identified' | 'installed' | 'uninstalled' | 'deactivated' | 'reactivated' | 'frozen' | 'unknown';
10
+ export type FeatureType = 'boolean' | 'limit' | 'limit_with_overage' | 'string';
11
+ export type FeatureValue = {
12
+ type: 'boolean';
13
+ value: boolean;
14
+ } | {
15
+ type: 'limit';
16
+ value: number;
17
+ } | {
18
+ type: 'limit_with_overage';
19
+ value: number;
20
+ linkedUsageMetricId?: string;
21
+ } | {
22
+ type: 'string';
23
+ value: string;
24
+ };
25
+ export type UsageMetricAggregation = 'count_events' | 'sum_event_property';
26
+ export type UsageMetricStatus = 'active' | 'archived';
27
+ export type PlanVisibility = 'public' | 'hidden' | 'custom';
28
+ export type BillingInterval = 'every_30_days' | 'annual';
29
+ export type DiscountType = 'percentage' | 'fixed_amount';
30
+ export type PlanCustomFieldValue = boolean | string | number | Record<string, unknown> | unknown[] | null;
31
+ export type PlanUsagePricingType = 'per_unit' | 'per_unit_with_limits' | 'package' | 'percentage';
32
+ export type UsageRollupFrequency = 'immediate' | 'daily' | 'weekly' | 'monthly';
33
+ export type UsageLimitsPeriod = 'billing_period' | 'calendar_month';
34
+ export type PackagePricingRounding = 'ceil' | 'floor' | 'pro_rata';
35
+ export interface IPlanUsagePricingTier {
36
+ fromQuantity: number;
37
+ toQuantity?: number;
38
+ unitPrice: number;
39
+ }
40
+ export interface IPlanDiscount {
41
+ type: DiscountType;
42
+ value: number;
43
+ duration: {
44
+ type: 'forever';
45
+ } | {
46
+ type: 'once';
47
+ } | {
48
+ type: 'repeating';
49
+ cycleCount: number;
50
+ };
51
+ reason?: string;
52
+ }
53
+ export interface IRecurringPricing {
54
+ amount: number;
55
+ currency: CurrencyCode;
56
+ interval: BillingInterval;
57
+ trialDays?: number;
58
+ discount?: IPlanDiscount;
59
+ }
60
+ export type PlanUsageBillingPricing = {
61
+ type: 'per_unit';
62
+ includedQuantity?: number;
63
+ unitPrice: number;
64
+ } | {
65
+ type: 'per_unit_with_limits';
66
+ tiers?: IPlanUsagePricingTier[];
67
+ unitPrice?: number;
68
+ minimumBillableQuantity?: number;
69
+ maximumBillableQuantity?: number;
70
+ } | {
71
+ type: 'package';
72
+ includedQuantity?: number;
73
+ packageSize: number;
74
+ packagePrice: number;
75
+ rounding: PackagePricingRounding;
76
+ } | {
77
+ type: 'percentage';
78
+ percentageRate: number;
79
+ amountPropertyName: string;
80
+ includedAmount?: number;
81
+ };
82
+ export interface IUsageBillingConfig {
83
+ handle: string;
84
+ usageMetricId: string;
85
+ pricingType: PlanUsagePricingType;
86
+ terms?: ILocalizedText;
87
+ currency: CurrencyCode;
88
+ rollupFrequency: UsageRollupFrequency;
89
+ usageLimitsPeriod: UsageLimitsPeriod;
90
+ rollOverPendingUsage: boolean;
91
+ pricing: PlanUsageBillingPricing;
92
+ linkedFeatureKey?: string;
93
+ }
94
+ export interface IUsageBillingCap {
95
+ cappedAmount: number;
96
+ }
97
+ export type PublicPlanUsageBillingConfig = IUsageBillingConfig;
98
+ export type ToastyBillingStatus = 'none' | 'pending_approval' | 'trialing' | 'active' | 'inactive';
99
+ export interface IPublicCustomerFeatureDto {
100
+ id: string;
101
+ key: string;
102
+ name: string;
103
+ description?: string;
104
+ type: FeatureType;
105
+ value: FeatureValue;
106
+ visibleToCustomers: boolean;
107
+ linkedUsageMetricId?: string;
108
+ }
109
+ export interface IPublicCustomerUsageMetricDto {
110
+ id: string;
111
+ name: string;
112
+ description?: string;
113
+ eventName: string;
114
+ aggregationMode: UsageMetricAggregation;
115
+ propertyName?: string;
116
+ status: UsageMetricStatus;
117
+ }
118
+ export interface IPublicCustomerUsageByFeatureDto {
119
+ featureKey: string;
120
+ usageMetricId: string;
121
+ eventName: string;
122
+ limit: number;
123
+ used: number;
124
+ remaining: number | null;
125
+ }
126
+ export interface IPublicCustomerPlanDto {
127
+ id: string;
128
+ handle: string;
129
+ name: string;
130
+ description?: string;
131
+ terms?: string;
132
+ visibility: PlanVisibility;
133
+ pricing: IRecurringPricing;
134
+ features: IPublicCustomerFeatureDto[];
135
+ usageBilling: PublicPlanUsageBillingConfig[];
136
+ usageBillingCap?: IUsageBillingCap;
137
+ customFields: Record<string, PlanCustomFieldValue>;
138
+ }
139
+ export interface IPublicCustomerSubscriptionDto {
140
+ id: string;
141
+ status: SubscriptionStatus;
142
+ provider: BillingProvider;
143
+ planId: string;
144
+ planHandle: string;
145
+ currentPeriodStart?: string;
146
+ currentPeriodEnd?: string;
147
+ trialEndsAt?: string;
148
+ cancelAtPeriodEnd?: boolean;
149
+ }
150
+ export interface IPublicCustomerDto {
151
+ id: string;
152
+ installationId: string;
153
+ myshopifyDomain: string;
154
+ displayName?: string;
155
+ shopDomain?: string;
156
+ billingStatus: ToastyBillingStatus;
157
+ plans: IPublicCustomerPlanDto[];
158
+ subscription: IPublicCustomerSubscriptionDto | null;
159
+ features: Record<string, IPublicCustomerFeatureDto>;
160
+ usageMetrics: IPublicCustomerUsageMetricDto[];
161
+ usageMetricsByEventName: Record<string, IPublicCustomerUsageMetricDto>;
162
+ usageByFeatureKey: Record<string, IPublicCustomerUsageByFeatureDto>;
163
+ customFields: Record<string, unknown>;
164
+ }
165
+ export interface IGetCustomerResponse {
166
+ customer: IPublicCustomerDto;
167
+ }
168
+ export interface IIdentifyInput {
169
+ shop: string;
170
+ shopifyShopGid?: string;
171
+ shopDomain?: string;
172
+ shopName?: string;
173
+ email?: string;
174
+ countryCode?: string;
175
+ currencyCode?: CurrencyCode;
176
+ shopifyPlanName?: string;
177
+ shopifyPlanDisplayName?: string;
178
+ }
179
+ export interface IIdentifyResponse {
180
+ customerId: string;
181
+ installationId: string;
182
+ installationStatus: AppInstallationStatus;
183
+ customerApiToken: string;
184
+ customer: IPublicCustomerDto;
185
+ }
186
+ export interface ICreateSubscriptionInput {
187
+ shop?: string;
188
+ returnUrl: string;
189
+ planId?: string;
190
+ planHandle?: string;
191
+ test?: boolean;
192
+ }
193
+ export interface ICreateSubscriptionResponse {
194
+ subscriptionId: string;
195
+ status: SubscriptionStatus;
196
+ provider: BillingProvider;
197
+ confirmationUrl: string;
198
+ planId: string;
199
+ }
200
+ export interface ICancelSubscriptionInput {
201
+ shop?: string;
202
+ subscriptionId: string;
203
+ prorate?: boolean;
204
+ }
205
+ export interface ICancelSubscriptionResponse {
206
+ subscriptionId: string;
207
+ status: SubscriptionStatus;
208
+ provider: BillingProvider;
209
+ planId: string;
210
+ }
211
+ export interface IUpdateSubscriptionInput {
212
+ shop?: string;
213
+ subscriptionId: string;
214
+ lineItemId: string;
215
+ cappedAmount: number;
216
+ }
217
+ export interface IUpdateSubscriptionResponse {
218
+ subscriptionId: string;
219
+ status: SubscriptionStatus;
220
+ provider: BillingProvider;
221
+ planId: string;
222
+ confirmationUrl?: string;
223
+ }
224
+ export interface ICreateOneTimeChargeInput {
225
+ shop?: string;
226
+ name: string;
227
+ amount: number;
228
+ currency: string;
229
+ returnUrl: string;
230
+ test?: boolean;
231
+ }
232
+ export interface ICreateOneTimeChargeResponse {
233
+ chargeId: string;
234
+ status: string;
235
+ confirmationUrl: string;
236
+ provider: BillingProvider;
237
+ }
238
+ export interface ITrackUsageEventInput {
239
+ shop?: string;
240
+ eventName: string;
241
+ quantity?: number;
242
+ properties?: Record<string, unknown>;
243
+ idempotencyKey: string;
244
+ sourceId?: string;
245
+ occurredAt?: string;
246
+ }
247
+ export interface ITrackUsageLedgerEntryDto {
248
+ id: string;
249
+ status: 'included' | 'pending_charge';
250
+ usageBillingHandle: string;
251
+ usageMetricHandle: string;
252
+ quantity: number;
253
+ amount: number;
254
+ currency: CurrencyCode;
255
+ }
256
+ export interface ITrackUsageEventResponse {
257
+ eventId: string;
258
+ processingStatus: 'processed' | 'failed';
259
+ reason?: string;
260
+ ledgerEntries: ITrackUsageLedgerEntryDto[];
261
+ }
262
+ export interface ITrackUsageEventBatchInput {
263
+ events: ITrackUsageEventInput[];
264
+ }
265
+ export interface ITrackUsageEventBatchItemError {
266
+ code: string;
267
+ message?: string;
268
+ }
269
+ export interface ITrackUsageEventBatchItemResponse {
270
+ index: number;
271
+ ok: boolean;
272
+ result?: ITrackUsageEventResponse;
273
+ error?: ITrackUsageEventBatchItemError;
274
+ }
275
+ export interface ITrackUsageEventBatchResponse {
276
+ items: ITrackUsageEventBatchItemResponse[];
277
+ }
278
+ export interface IGetUsageMetricReportInput {
279
+ shop?: string;
280
+ usageMetricId?: string;
281
+ eventName?: string;
282
+ featureKey?: string;
283
+ from?: string;
284
+ to?: string;
285
+ }
286
+ export interface IGetUsageMetricReportResponse {
287
+ usageMetricId: string;
288
+ eventName: string;
289
+ featureKey?: string;
290
+ from?: string;
291
+ to?: string;
292
+ total: number;
293
+ count: number;
294
+ }
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@hiclimba/client",
3
+ "version": "0.0.2",
4
+ "description": "Node SDK for Toasty product backends and customer-scoped API access.",
5
+ "license": "MIT",
6
+ "private": false,
7
+ "type": "module",
8
+ "main": "./index.js",
9
+ "module": "./index.js",
10
+ "types": "./index.d.ts",
11
+ "exports": {
12
+ "./package.json": "./package.json",
13
+ ".": {
14
+ "types": "./index.d.ts",
15
+ "import": "./index.js",
16
+ "default": "./index.js"
17
+ }
18
+ },
19
+ "sideEffects": false,
20
+ "files": [
21
+ "index.js",
22
+ "index.d.ts",
23
+ "lib/**/*.js",
24
+ "lib/**/*.d.ts",
25
+ "README.md",
26
+ "package.json"
27
+ ],
28
+ "dependencies": {
29
+ "tslib": "^2.3.0"
30
+ },
31
+ "engines": {
32
+ "node": ">=20"
33
+ },
34
+ "publishConfig": {
35
+ "access": "public"
36
+ }
37
+ }