Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,69 @@
# [5.0.0](https://github.com/Salable/node-sdk/compare/v4.11.0...v5.0.0) (2025-09-23)


### Bug Fixes

* created more global stripe and salable data to stop the test overwrtting each other ([aba2bfe](https://github.com/Salable/node-sdk/commit/aba2bfe1b3c80f2840761290f504523685e67fef))
* fixed imports ([1b354ba](https://github.com/Salable/node-sdk/commit/1b354ba060d7e5b470d9ef8963c1cad1a8adcbe6))
* moved v2 schemas into a single file ([b6ff867](https://github.com/Salable/node-sdk/commit/b6ff867ab42105f81937f1c6725bfb27c23f00db))
* removed clean up from tests ([e8d5288](https://github.com/Salable/node-sdk/commit/e8d528883860f96f9669c72bef668190ab6df02f))
* updated EntitlementsCheck return type ([273b14d](https://github.com/Salable/node-sdk/commit/273b14dcb3269fe4ad987499804065f2253e0051))


### Features

* cursor pagination on products endpoint ([7e9af6f](https://github.com/Salable/node-sdk/commit/7e9af6f81721705882336e1b91226beed26f4c07))
* entitlements method ([887e8da](https://github.com/Salable/node-sdk/commit/887e8da7cd3b38909b2f7b14fda56f7ae69cf4f5))
* paginate features and plans endpoints ([37e3357](https://github.com/Salable/node-sdk/commit/37e335710ba5f4e21c472b3b1000bfa742642334))
* v3 of salable api added ([9c050b5](https://github.com/Salable/node-sdk/commit/9c050b53253ff5d053482264270108195ae44e0a))
* wip v3 removed Salable class for initSalable function ([a72d4e1](https://github.com/Salable/node-sdk/commit/a72d4e1c5dc3098e4b9246375b32afed3f5f38f1))


### Tests

* updated feature schema to include featureEnumOptions ([2b143a0](https://github.com/Salable/node-sdk/commit/2b143a030bd9f87e0c93dba8ceeb60f661a474f1))


### BREAKING CHANGES

* methods will use v3 of the API. New initSalable function to replace Salable class.

# [5.0.0-beta.2](https://github.com/Salable/node-sdk/compare/v5.0.0-beta.1...v5.0.0-beta.2) (2025-09-18)


### Bug Fixes

* updated EntitlementsCheck return type ([273b14d](https://github.com/Salable/node-sdk/commit/273b14dcb3269fe4ad987499804065f2253e0051))

# [5.0.0-beta.1](https://github.com/Salable/node-sdk/compare/v4.11.0...v5.0.0-beta.1) (2025-09-18)


### Bug Fixes

* created more global stripe and salable data to stop the test overwrtting each other ([aba2bfe](https://github.com/Salable/node-sdk/commit/aba2bfe1b3c80f2840761290f504523685e67fef))
* fixed imports ([1b354ba](https://github.com/Salable/node-sdk/commit/1b354ba060d7e5b470d9ef8963c1cad1a8adcbe6))
* moved v2 schemas into a single file ([b6ff867](https://github.com/Salable/node-sdk/commit/b6ff867ab42105f81937f1c6725bfb27c23f00db))
* removed clean up from tests ([e8d5288](https://github.com/Salable/node-sdk/commit/e8d528883860f96f9669c72bef668190ab6df02f))


### Features

* cursor pagination on products endpoint ([7e9af6f](https://github.com/Salable/node-sdk/commit/7e9af6f81721705882336e1b91226beed26f4c07))
* entitlements method ([887e8da](https://github.com/Salable/node-sdk/commit/887e8da7cd3b38909b2f7b14fda56f7ae69cf4f5))
* paginate features and plans endpoints ([37e3357](https://github.com/Salable/node-sdk/commit/37e335710ba5f4e21c472b3b1000bfa742642334))
* v3 of salable api added ([9c050b5](https://github.com/Salable/node-sdk/commit/9c050b53253ff5d053482264270108195ae44e0a))
* wip v3 removed Salable class for initSalable function ([a72d4e1](https://github.com/Salable/node-sdk/commit/a72d4e1c5dc3098e4b9246375b32afed3f5f38f1))


### Tests

* updated feature schema to include featureEnumOptions ([2b143a0](https://github.com/Salable/node-sdk/commit/2b143a030bd9f87e0c93dba8ceeb60f661a474f1))


### BREAKING CHANGES

* methods will use v3 of the API. New initSalable function to replace Salable class.

# [4.11.0](https://github.com/Salable/node-sdk/compare/v4.10.0...v4.11.0) (2025-07-08)


Expand Down
23 changes: 11 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,33 @@ Let’s walk through setting up a project that uses the Salable API Class from t
1. Create a new Node.js project.
2. Inside of the project, run: `npm install @salable/node-sdk`. Adding packages results in update in lock file, [yarn.lock](https://yarnpkg.com/getting-started/qa/#should-lockfiles-be-committed-to-the-repository) or [package-lock.json](https://docs.npmjs.com/configuring-npm/package-lock-json). You **should** commit your lock file along with your code to avoid potential breaking changes.

## v4.0.0 Update
## v5.0.0 Update

The SDK now supports Salable API version selection and developers can choose which version of the Salable API they want to interact with via the SDK
As such, the Salable API version is now a required argument when instantiating the SDK
As such, the Salable API version is now a required argument when instantiating the SDK

```typescript
import { Salable } from '@salable/node-sdk';
import { initSalable } from '@salable/node-sdk';

const salable = new Salable('your_api_key', 'v2');
const salable = initSalable('your_api_key', 'v3');
```
> **_NOTE:_** Support for `v1` of the Salable API has been deprecated, `v2` is currently the only supported version

### General Changes

#### Salable API versioning and Types
- Types and method documentation are dynamic and automatically adjust to the version selected

```typescript
import { Salable } from '@salable/node-sdk';
import { initSalable } from '@salable/node-sdk';

const salableV1 = new Salable('your_api_key', 'v1'); // NOTE: 'v1' is not supported and used for example purposes
const salableV2 = new Salable('your_api_key', 'v2');
const salableV2 = initSalable('your_api_key', 'v2');
const salableV3 = initSalable('your_api_key', 'v3');

// The "licenses.getUsage" method is supported in this version and will work
await salableV1.licenses.getUsage():
// "licenses.check" method is supported in this version and will work
await salableV2.licenses.check();

// This will error as "licenses.getUsage" has been deprecated in 'v2'
await salableV2.licenses.getUsage(): // Will error with: "Property 'getUsage' does not exist ..."
// This will error as all "licenses" methods has been deprecated in 'v3'
await salableV3.licenses.check(); // Will error with: "Property 'licenses' does not exist ..."
```
#### Pagination
- All methods are now scope authorized and your API Key must contain the appropriate scopes to user certain methods
Expand Down
6 changes: 3 additions & 3 deletions __tests__/_setup/setup-test-envs.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import createStripeData from '../../test-utils/stripe/create-stripe-test-data';
import createTestData from '../../test-utils/scripts/create-test-data';
import createStripeData from '../../test-utils/scripts/create-stripe-test-data';
import createSalableTestData from '../../test-utils/scripts/create-salable-test-data';
import { config } from 'dotenv';
import { exec } from 'child_process';
import { promisify } from 'util';
Expand All @@ -20,7 +20,7 @@ const globalSetup = async () => {
console.log('\n STRIPE ACCOUNT DATA CREATED');
process.env.stripEnvs = JSON.stringify(obj);

await createTestData(obj);
await createSalableTestData(obj);
console.log('\n TEST DATA CREATED');
};

Expand Down
8 changes: 8 additions & 0 deletions __tests__/test-mock-data/capabilities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import objectBuilder from './object-builder';

// deprecated
export const mockCapability = objectBuilder({
name: 'test_capability',
status: 'ACTIVE',
description: 'Capability description',
});
17 changes: 17 additions & 0 deletions __tests__/test-mock-data/coupons.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { CouponDuration, CouponStatus, DiscountType } from '@prisma/client';
import objectBuilder from './object-builder';

export const mockCoupon = objectBuilder({
status: 'ACTIVE' as CouponStatus,
createdAt: new Date(),
updatedAt: new Date(),
name: 'Percentage Coupon',
duration: 'ONCE' as CouponDuration,
discountType: 'PERCENTAGE' as DiscountType,
paymentIntegrationCouponId: 'test-payment-integration-id',
percentOff: 10,
expiresAt: null,
maxRedemptions: null,
isTest: false,
durationInMonths: 1,
});
16 changes: 16 additions & 0 deletions __tests__/test-mock-data/currencies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import objectBuilder from './object-builder';

export const mockCurrency = objectBuilder({
shortName: 'XXX',
longName: 'Mock Currency',
symbol: '@',
});

export const mockProductCurrency = objectBuilder({
defaultCurrency: true,
});

export const mockPlanCurrency = objectBuilder({
price: 500,
paymentIntegrationPlanId: 'test-payment-integration-id',
});
11 changes: 11 additions & 0 deletions __tests__/test-mock-data/event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import objectBuilder from './object-builder';
import { EventStatus } from '@prisma/client';
import { EventType } from '../lib/constants';

export const mockSalableEvent = objectBuilder({
type: EventType.CreateSeats,
organisation: 'xxxxx',
status: EventStatus.pending as EventStatus,
isTest: false,
retries: 0,
});
23 changes: 23 additions & 0 deletions __tests__/test-mock-data/features.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import objectBuilder from './object-builder';

export const mockFeature = objectBuilder({
name: 'Boolean Feature Name',
description: 'Feature description',
displayName: 'Boolean Feature Display Name',
variableName: 'boolean_feature',
status: 'ACTIVE',
visibility: 'public',
valueType: 'boolean',
defaultValue: 'false',
showUnlimited: false,
sortOrder: 0,
});

export const mockPlanFeature = objectBuilder({
value: 'xxxxx',
isUnlimited: undefined as boolean | undefined,
isUsage: undefined as boolean | undefined, // deprecated
pricePerUnit: 10, // deprecated
minUsage: 1, // deprecated
maxUsage: 100, // deprecated
});
38 changes: 38 additions & 0 deletions __tests__/test-mock-data/licenses.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { LicensesUsageRecordType, Prisma } from '@prisma/client';
import objectBuilder from './object-builder';

// deprecated
export const mockLicenseCapability = objectBuilder({
name: 'Export',
uuid: '38e63e2a-1269-4e9d-b712-28cfbf087285',
status: 'ACTIVE',
updatedAt: '2022-10-17T11:41:11.626Z',
description: null as string | null,
productUuid: 'c32f26e4-21d9-4456-a1f0-7e76039af518',
});

export const mockLicenseUsageRecord = objectBuilder({
unitCount: 0,
type: 'current' as LicensesUsageRecordType,
resetAt: null as Date | null,
recordedAt: null as Date | null,
});

export const mockLicense = objectBuilder({
name: null as string | null,
email: null as string | null,
status: 'ACTIVE',
granteeId: '123456' as string | null,
paymentService: 'ad-hoc',
purchaser: 'tester@testing.com',
type: 'licensed',
metadata: undefined as undefined | { member: string; granteeId: string },
capabilities: [
mockLicenseCapability({ name: 'CapabilityOne' }),
mockLicenseCapability({ name: 'CapabilityTwo' }),
] as Prisma.InputJsonObject[], // deprecated
startTime: undefined as undefined | Date,
endTime: new Date(),
cancelAtPeriodEnd: false,
isTest: false,
});
32 changes: 32 additions & 0 deletions __tests__/test-mock-data/object-builder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
type DeepPartial<T> = T extends object ? { [P in keyof T]?: DeepPartial<T[P]> } : T;

function isObject(obj?: unknown): obj is object {
return Boolean(obj) && (obj as object)?.constructor === Object;
}

function merge(target: Record<string, unknown>, source?: Record<string, unknown>) {
const clone = { ...target } as Record<string, unknown>;
if (!source) return clone;
for (const key of Object.keys(source)) {
if (isObject(source[key])) {
clone[key] = merge(
clone[key] as Record<string, unknown>,
source[key] as Record<string, unknown>
);
} else {
clone[key] = source[key];
}
}

return clone;
}

export default function objectBuilder<T>(defaultParameters: T) {
return (overrideParameters?: DeepPartial<T> | null): T => {
if (!overrideParameters) overrideParameters = {} as DeepPartial<T>;
return merge(
defaultParameters as Record<string, unknown>,
overrideParameters as Record<string, unknown>
) as T;
};
}
1 change: 1 addition & 0 deletions __tests__/test-mock-data/optional-type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
27 changes: 27 additions & 0 deletions __tests__/test-mock-data/plans.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import objectBuilder from './object-builder';

export const mockPlan = objectBuilder({
name: 'Free Plan Name', // deprecated
description: 'Free Plan description',
displayName: 'Free Plan Display Name',
slug: 'example-slug',
status: 'ACTIVE',
trialDays: 0, // deprecated
evaluation: false, // deprecated
evalDays: 0,
organisation: 'xxxxx',
visibility: 'public',
licenseType: 'licensed',
interval: 'month',
perSeatAmount: 1,
maxSeatAmount: -1,
length: 1,
active: true, // deprecated
planType: 'Standard', // deprecated
pricingType: 'free',
environment: 'dev', // deprecated
paddlePlanId: null, // deprecated
isTest: false,
hasAcceptedTransaction: false,
archivedAt: null as Date | null,
});
8 changes: 8 additions & 0 deletions __tests__/test-mock-data/pricing-table.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import objectBuilder from './object-builder';

export const mockPricingTable = objectBuilder({
name: 'Sample Pricing Table',
status: 'ACTIVE',
productUuid: 'xxxxxx',
featuredPlanUuid: 'xxxxxx',
});
29 changes: 29 additions & 0 deletions __tests__/test-mock-data/products.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import objectBuilder from './object-builder';
import { PaymentIntegration, PaymentIntegrationStatus } from '@prisma/client';

export const mockProduct = objectBuilder({
name: 'Sample Product',
description: 'This is a sample product for testing purposes',
logoUrl: 'https://example.com/logo.png',
displayName: 'Sample Product',
organisation: 'xxxxx',
slug: 'example-slug',
status: 'ACTIVE',
paid: false,
appType: 'CUSTOM', // deprecated
isTest: false,
archivedAt: null as null | Date,
});

export const mockOrganisationPaymentIntegration = objectBuilder({
organisation: 'xxxxx',
integrationName: 'stripe_existing' as PaymentIntegration,
accountData: { // deprecated
key: 'xxxxx',
encryptedData: 'xoxox',
},
isTest: false,
accountName: 'Account Name',
accountId: 'acc_1234',
status: PaymentIntegrationStatus.active,
});
10 changes: 10 additions & 0 deletions __tests__/test-mock-data/sessions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import objectBuilder from './object-builder';

export const mockSession = objectBuilder({
organisationId: 'xxxxx',
expiresAt: new Date(Date.now() + 10800000),
value: 'xxxx',
scope: '',
isTest: true,
metadata: {},
});
19 changes: 19 additions & 0 deletions __tests__/test-mock-data/subscriptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import objectBuilder from './object-builder';
import { randomUUID } from 'crypto';
import { PaymentIntegration } from '@prisma/client';

export const mockSubscription = objectBuilder({
type: 'salable' as PaymentIntegration,
paymentIntegrationSubscriptionId: randomUUID(),
email: null as string | null,
owner: 'xxxxx',
organisation: 'xxxxx',
status: 'ACTIVE',
createdAt: new Date(),
updatedAt: new Date(),
expiryDate: new Date(Date.now() + 31536000000),
lineItemIds: ['xxxxx'] as string[] | undefined,
isTest: false,
quantity: 1,
cancelAtPeriodEnd: false,
});
7 changes: 6 additions & 1 deletion commitlint.config.js
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
module.exports = { extends: ['@commitlint/config-conventional'] };
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
"body-max-line-length": [0]
}
};
Loading