@solomonai/stripe-sync-graphql-sdk 0.0.1
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 +71 -0
- package/dist/index.d.mts +69 -0
- package/dist/index.d.ts +69 -0
- package/dist/index.js +52 -0
- package/dist/index.mjs +26 -0
- package/package.json +51 -0
- package/src/__tests__/sdk.test.ts +435 -0
- package/src/generated/gql.ts +28 -0
- package/src/generated/graphql.ts +744 -0
- package/src/generated/index.ts +1 -0
- package/src/index.ts +78 -0
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { createClient, ClientOptions } from '../index'
|
|
3
|
+
import {
|
|
4
|
+
// Enums (runtime values)
|
|
5
|
+
SubscriptionStatus,
|
|
6
|
+
InvoiceStatus,
|
|
7
|
+
ChargeStatus,
|
|
8
|
+
EntityType,
|
|
9
|
+
Period,
|
|
10
|
+
PricingPlan,
|
|
11
|
+
PricingType,
|
|
12
|
+
PricingTiers,
|
|
13
|
+
} from '../generated/graphql'
|
|
14
|
+
import type {
|
|
15
|
+
Customer,
|
|
16
|
+
Subscription,
|
|
17
|
+
Invoice,
|
|
18
|
+
Charge,
|
|
19
|
+
Product,
|
|
20
|
+
Price,
|
|
21
|
+
SubscriptionItem,
|
|
22
|
+
MrrMetrics,
|
|
23
|
+
ChurnMetrics,
|
|
24
|
+
TenantUsage,
|
|
25
|
+
SyncResult,
|
|
26
|
+
Query,
|
|
27
|
+
Mutation,
|
|
28
|
+
PageInfo,
|
|
29
|
+
Scalars,
|
|
30
|
+
} from '../generated/graphql'
|
|
31
|
+
|
|
32
|
+
describe('GraphQL SDK', () => {
|
|
33
|
+
describe('createClient', () => {
|
|
34
|
+
it('should create a client with endpoint only', () => {
|
|
35
|
+
const client = createClient({
|
|
36
|
+
endpoint: 'https://api.example.com/graphql',
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
expect(client).toBeDefined()
|
|
40
|
+
expect(typeof client.request).toBe('function')
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('should create a client with headers', () => {
|
|
44
|
+
const client = createClient({
|
|
45
|
+
endpoint: 'https://api.example.com/graphql',
|
|
46
|
+
headers: {
|
|
47
|
+
'x-tenant-id': 'tenant-123',
|
|
48
|
+
'x-api-key': 'api-key-456',
|
|
49
|
+
},
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
expect(client).toBeDefined()
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('should create a client with timeout', () => {
|
|
56
|
+
const client = createClient({
|
|
57
|
+
endpoint: 'https://api.example.com/graphql',
|
|
58
|
+
timeout: 5000,
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
expect(client).toBeDefined()
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('should create a client with all options', () => {
|
|
65
|
+
const options: ClientOptions = {
|
|
66
|
+
endpoint: 'https://api.example.com/graphql',
|
|
67
|
+
headers: {
|
|
68
|
+
Authorization: 'Bearer token',
|
|
69
|
+
'x-tenant-id': 'tenant-123',
|
|
70
|
+
},
|
|
71
|
+
timeout: 10000,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const client = createClient(options)
|
|
75
|
+
expect(client).toBeDefined()
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
describe('Generated Types', () => {
|
|
80
|
+
describe('Scalars', () => {
|
|
81
|
+
it('should have correct scalar type definitions', () => {
|
|
82
|
+
// Type-level tests - these validate at compile time
|
|
83
|
+
const id: Scalars['ID']['output'] = 'test-id'
|
|
84
|
+
const str: Scalars['String']['output'] = 'test-string'
|
|
85
|
+
const bool: Scalars['Boolean']['output'] = true
|
|
86
|
+
const int: Scalars['Int']['output'] = 42
|
|
87
|
+
const float: Scalars['Float']['output'] = 3.14
|
|
88
|
+
const bigInt: Scalars['BigInt']['output'] = '9007199254740991'
|
|
89
|
+
const dateTime: Scalars['DateTime']['output'] = '2024-01-01T00:00:00Z'
|
|
90
|
+
const json: Scalars['JSON']['output'] = { key: 'value' }
|
|
91
|
+
|
|
92
|
+
expect(id).toBe('test-id')
|
|
93
|
+
expect(str).toBe('test-string')
|
|
94
|
+
expect(bool).toBe(true)
|
|
95
|
+
expect(int).toBe(42)
|
|
96
|
+
expect(float).toBe(3.14)
|
|
97
|
+
expect(bigInt).toBe('9007199254740991')
|
|
98
|
+
expect(dateTime).toBe('2024-01-01T00:00:00Z')
|
|
99
|
+
expect(json).toEqual({ key: 'value' })
|
|
100
|
+
})
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
describe('Customer type', () => {
|
|
104
|
+
it('should have correct shape', () => {
|
|
105
|
+
const customer: Customer = {
|
|
106
|
+
__typename: 'Customer',
|
|
107
|
+
id: 'cus_123',
|
|
108
|
+
email: 'test@example.com',
|
|
109
|
+
name: 'Test Customer',
|
|
110
|
+
balance: 0,
|
|
111
|
+
created: 1234567890,
|
|
112
|
+
currency: 'usd',
|
|
113
|
+
delinquent: false,
|
|
114
|
+
description: 'A test customer',
|
|
115
|
+
invoicePrefix: 'INV',
|
|
116
|
+
metadata: { key: 'value' },
|
|
117
|
+
phone: '+1234567890',
|
|
118
|
+
taxExempt: 'none',
|
|
119
|
+
lifetimeValue: '100000',
|
|
120
|
+
totalSpend: '50000',
|
|
121
|
+
churnRisk: 0.15,
|
|
122
|
+
address: null,
|
|
123
|
+
invoiceSettings: null,
|
|
124
|
+
invoices: [],
|
|
125
|
+
shipping: null,
|
|
126
|
+
subscriptions: [],
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
expect(customer.id).toBe('cus_123')
|
|
130
|
+
expect(customer.email).toBe('test@example.com')
|
|
131
|
+
expect(customer.__typename).toBe('Customer')
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('should allow nullable fields', () => {
|
|
135
|
+
const customer: Customer = {
|
|
136
|
+
__typename: 'Customer',
|
|
137
|
+
id: 'cus_123',
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
expect(customer.email).toBeUndefined()
|
|
141
|
+
expect(customer.name).toBeUndefined()
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
describe('Subscription type', () => {
|
|
146
|
+
it('should have correct shape', () => {
|
|
147
|
+
const subscription: Subscription = {
|
|
148
|
+
__typename: 'Subscription',
|
|
149
|
+
id: 'sub_123',
|
|
150
|
+
status: SubscriptionStatus.Active,
|
|
151
|
+
currentPeriodStart: 1234567890,
|
|
152
|
+
currentPeriodEnd: 1237246290,
|
|
153
|
+
cancelAtPeriodEnd: false,
|
|
154
|
+
created: 1234567890,
|
|
155
|
+
mrr: '2999',
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
expect(subscription.id).toBe('sub_123')
|
|
159
|
+
expect(subscription.status).toBe('active')
|
|
160
|
+
expect(subscription.mrr).toBe('2999')
|
|
161
|
+
})
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
describe('Invoice type', () => {
|
|
165
|
+
it('should have correct shape', () => {
|
|
166
|
+
const invoice: Invoice = {
|
|
167
|
+
__typename: 'Invoice',
|
|
168
|
+
id: 'in_123',
|
|
169
|
+
status: InvoiceStatus.Paid,
|
|
170
|
+
amountDue: '2999',
|
|
171
|
+
amountPaid: '2999',
|
|
172
|
+
amountRemaining: '0',
|
|
173
|
+
currency: 'usd',
|
|
174
|
+
paid: true,
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
expect(invoice.id).toBe('in_123')
|
|
178
|
+
expect(invoice.status).toBe('paid')
|
|
179
|
+
expect(invoice.paid).toBe(true)
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
describe('Charge type', () => {
|
|
184
|
+
it('should have correct shape', () => {
|
|
185
|
+
const charge: Charge = {
|
|
186
|
+
__typename: 'Charge',
|
|
187
|
+
id: 'ch_123',
|
|
188
|
+
amount: '2999',
|
|
189
|
+
currency: 'usd',
|
|
190
|
+
paid: true,
|
|
191
|
+
status: 'succeeded',
|
|
192
|
+
captured: true,
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
expect(charge.id).toBe('ch_123')
|
|
196
|
+
expect(charge.amount).toBe('2999')
|
|
197
|
+
expect(charge.paid).toBe(true)
|
|
198
|
+
})
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
describe('Product type', () => {
|
|
202
|
+
it('should have correct shape', () => {
|
|
203
|
+
const product: Product = {
|
|
204
|
+
__typename: 'Product',
|
|
205
|
+
id: 'prod_123',
|
|
206
|
+
name: 'Test Product',
|
|
207
|
+
active: true,
|
|
208
|
+
description: 'A test product',
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
expect(product.id).toBe('prod_123')
|
|
212
|
+
expect(product.name).toBe('Test Product')
|
|
213
|
+
})
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
describe('Price type', () => {
|
|
217
|
+
it('should have correct shape', () => {
|
|
218
|
+
const price: Price = {
|
|
219
|
+
__typename: 'Price',
|
|
220
|
+
id: 'price_123',
|
|
221
|
+
active: true,
|
|
222
|
+
currency: 'usd',
|
|
223
|
+
unitAmount: 2999,
|
|
224
|
+
type: PricingType.Recurring,
|
|
225
|
+
billingScheme: 'per_unit',
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
expect(price.id).toBe('price_123')
|
|
229
|
+
expect(price.unitAmount).toBe(2999)
|
|
230
|
+
expect(price.type).toBe('recurring')
|
|
231
|
+
})
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
describe('SubscriptionItem type', () => {
|
|
235
|
+
it('should have correct shape', () => {
|
|
236
|
+
const item: SubscriptionItem = {
|
|
237
|
+
__typename: 'SubscriptionItem',
|
|
238
|
+
id: 'si_123',
|
|
239
|
+
quantity: 5,
|
|
240
|
+
created: 1234567890,
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
expect(item.id).toBe('si_123')
|
|
244
|
+
expect(item.quantity).toBe(5)
|
|
245
|
+
})
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
describe('MRRMetrics type', () => {
|
|
249
|
+
it('should have correct shape', () => {
|
|
250
|
+
const metrics: MrrMetrics = {
|
|
251
|
+
__typename: 'MRRMetrics',
|
|
252
|
+
current: '100000',
|
|
253
|
+
previous: '90000',
|
|
254
|
+
growth: '10000',
|
|
255
|
+
growthRate: 11.11,
|
|
256
|
+
newMrr: '15000',
|
|
257
|
+
expansionMrr: '5000',
|
|
258
|
+
contractionMrr: '2000',
|
|
259
|
+
churnedMrr: '8000',
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
expect(metrics.current).toBe('100000')
|
|
263
|
+
expect(metrics.growthRate).toBe(11.11)
|
|
264
|
+
})
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
describe('ChurnMetrics type', () => {
|
|
268
|
+
it('should have correct shape', () => {
|
|
269
|
+
const metrics: ChurnMetrics = {
|
|
270
|
+
__typename: 'ChurnMetrics',
|
|
271
|
+
count: 5,
|
|
272
|
+
rate: 2.5,
|
|
273
|
+
period: 'MONTH',
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
expect(metrics.count).toBe(5)
|
|
277
|
+
expect(metrics.rate).toBe(2.5)
|
|
278
|
+
})
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
describe('TenantUsage type', () => {
|
|
282
|
+
it('should have correct shape', () => {
|
|
283
|
+
const usage: TenantUsage = {
|
|
284
|
+
__typename: 'TenantUsage',
|
|
285
|
+
webhookEventsThisPeriod: '1000',
|
|
286
|
+
apiCallsThisPeriod: '5000',
|
|
287
|
+
periodStart: '2024-01-01T00:00:00Z',
|
|
288
|
+
periodEnd: '2024-02-01T00:00:00Z',
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
expect(usage.webhookEventsThisPeriod).toBe('1000')
|
|
292
|
+
expect(usage.apiCallsThisPeriod).toBe('5000')
|
|
293
|
+
})
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
describe('SyncResult type', () => {
|
|
297
|
+
it('should have correct shape', () => {
|
|
298
|
+
const result: SyncResult = {
|
|
299
|
+
__typename: 'SyncResult',
|
|
300
|
+
success: true,
|
|
301
|
+
jobId: 'job_123',
|
|
302
|
+
message: 'Sync queued successfully',
|
|
303
|
+
entitiesQueued: 100,
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
expect(result.success).toBe(true)
|
|
307
|
+
expect(result.jobId).toBe('job_123')
|
|
308
|
+
})
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
describe('PageInfo type', () => {
|
|
312
|
+
it('should have correct shape', () => {
|
|
313
|
+
const pageInfo: PageInfo = {
|
|
314
|
+
__typename: 'PageInfo',
|
|
315
|
+
hasNextPage: true,
|
|
316
|
+
hasPreviousPage: false,
|
|
317
|
+
startCursor: 'cursor_start',
|
|
318
|
+
endCursor: 'cursor_end',
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
expect(pageInfo.hasNextPage).toBe(true)
|
|
322
|
+
expect(pageInfo.startCursor).toBe('cursor_start')
|
|
323
|
+
})
|
|
324
|
+
})
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
describe('Enums', () => {
|
|
328
|
+
it('should have SubscriptionStatus values', () => {
|
|
329
|
+
expect(SubscriptionStatus.Active).toBe('active')
|
|
330
|
+
expect(SubscriptionStatus.Canceled).toBe('canceled')
|
|
331
|
+
expect(SubscriptionStatus.Incomplete).toBe('incomplete')
|
|
332
|
+
expect(SubscriptionStatus.IncompleteExpired).toBe('incomplete_expired')
|
|
333
|
+
expect(SubscriptionStatus.PastDue).toBe('past_due')
|
|
334
|
+
expect(SubscriptionStatus.Trialing).toBe('trialing')
|
|
335
|
+
expect(SubscriptionStatus.Unpaid).toBe('unpaid')
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
it('should have InvoiceStatus values', () => {
|
|
339
|
+
expect(InvoiceStatus.Draft).toBe('draft')
|
|
340
|
+
expect(InvoiceStatus.Open).toBe('open')
|
|
341
|
+
expect(InvoiceStatus.Paid).toBe('paid')
|
|
342
|
+
expect(InvoiceStatus.Uncollectible).toBe('uncollectible')
|
|
343
|
+
expect(InvoiceStatus.Void).toBe('void')
|
|
344
|
+
expect(InvoiceStatus.Deleted).toBe('deleted')
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
it('should have ChargeStatus values', () => {
|
|
348
|
+
expect(ChargeStatus.Succeeded).toBe('succeeded')
|
|
349
|
+
expect(ChargeStatus.Pending).toBe('pending')
|
|
350
|
+
expect(ChargeStatus.Failed).toBe('failed')
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
it('should have EntityType values', () => {
|
|
354
|
+
expect(EntityType.Customer).toBe('CUSTOMER')
|
|
355
|
+
expect(EntityType.Subscription).toBe('SUBSCRIPTION')
|
|
356
|
+
expect(EntityType.Invoice).toBe('INVOICE')
|
|
357
|
+
expect(EntityType.Charge).toBe('CHARGE')
|
|
358
|
+
expect(EntityType.Product).toBe('PRODUCT')
|
|
359
|
+
expect(EntityType.Price).toBe('PRICE')
|
|
360
|
+
expect(EntityType.PaymentIntent).toBe('PAYMENT_INTENT')
|
|
361
|
+
expect(EntityType.PaymentMethod).toBe('PAYMENT_METHOD')
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
it('should have Period values', () => {
|
|
365
|
+
expect(Period.Day).toBe('DAY')
|
|
366
|
+
expect(Period.Week).toBe('WEEK')
|
|
367
|
+
expect(Period.Month).toBe('MONTH')
|
|
368
|
+
expect(Period.Quarter).toBe('QUARTER')
|
|
369
|
+
expect(Period.Year).toBe('YEAR')
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
it('should have PricingPlan values', () => {
|
|
373
|
+
expect(PricingPlan.Free).toBe('FREE')
|
|
374
|
+
expect(PricingPlan.Pro).toBe('PRO')
|
|
375
|
+
expect(PricingPlan.Enterprise).toBe('ENTERPRISE')
|
|
376
|
+
})
|
|
377
|
+
|
|
378
|
+
it('should have PricingType values', () => {
|
|
379
|
+
expect(PricingType.OneTime).toBe('one_time')
|
|
380
|
+
expect(PricingType.Recurring).toBe('recurring')
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
it('should have PricingTiers values', () => {
|
|
384
|
+
expect(PricingTiers.Graduated).toBe('graduated')
|
|
385
|
+
expect(PricingTiers.Volume).toBe('volume')
|
|
386
|
+
})
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
describe('Query type structure', () => {
|
|
390
|
+
it('should define all expected query fields', () => {
|
|
391
|
+
// Type-level validation - ensure Query has expected fields
|
|
392
|
+
type QueryFields = keyof Query
|
|
393
|
+
const expectedFields: QueryFields[] = [
|
|
394
|
+
'__typename',
|
|
395
|
+
'_version',
|
|
396
|
+
'customer',
|
|
397
|
+
'customers',
|
|
398
|
+
'subscription',
|
|
399
|
+
'subscriptions',
|
|
400
|
+
'invoice',
|
|
401
|
+
'invoices',
|
|
402
|
+
'charge',
|
|
403
|
+
'charges',
|
|
404
|
+
'product',
|
|
405
|
+
'products',
|
|
406
|
+
'price',
|
|
407
|
+
'prices',
|
|
408
|
+
'mrrMetrics',
|
|
409
|
+
'churnRate',
|
|
410
|
+
'tenantUsage',
|
|
411
|
+
]
|
|
412
|
+
|
|
413
|
+
// This validates the type structure at compile time
|
|
414
|
+
expectedFields.forEach((field) => {
|
|
415
|
+
expect(typeof field).toBe('string')
|
|
416
|
+
})
|
|
417
|
+
})
|
|
418
|
+
})
|
|
419
|
+
|
|
420
|
+
describe('Mutation type structure', () => {
|
|
421
|
+
it('should define all expected mutation fields', () => {
|
|
422
|
+
type MutationFields = keyof Mutation
|
|
423
|
+
const expectedFields: MutationFields[] = [
|
|
424
|
+
'__typename',
|
|
425
|
+
'_noop',
|
|
426
|
+
'triggerSync',
|
|
427
|
+
'syncSingleEntity',
|
|
428
|
+
]
|
|
429
|
+
|
|
430
|
+
expectedFields.forEach((field) => {
|
|
431
|
+
expect(typeof field).toBe('string')
|
|
432
|
+
})
|
|
433
|
+
})
|
|
434
|
+
})
|
|
435
|
+
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
import * as types from './graphql'
|
|
3
|
+
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core'
|
|
4
|
+
|
|
5
|
+
const documents: DocumentNode<any, any>[] = []
|
|
6
|
+
/**
|
|
7
|
+
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
|
8
|
+
*
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* const query = graphql(`query GetUser($id: ID!) { user(id: $id) { name } }`);
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* The query argument is unknown!
|
|
16
|
+
* Please regenerate the types.
|
|
17
|
+
*/
|
|
18
|
+
export function graphql(source: string): unknown
|
|
19
|
+
|
|
20
|
+
export function graphql(source: string): DocumentNode<any, any> {
|
|
21
|
+
return (
|
|
22
|
+
(documents as unknown as Record<string, DocumentNode<any, any>>)[source] ??
|
|
23
|
+
({} as DocumentNode<any, any>)
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type DocumentType<TDocumentNode extends DocumentNode<any, any>> =
|
|
28
|
+
TDocumentNode extends DocumentNode<infer TType, any> ? TType : never
|