@cynco/sdk 0.1.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/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2026-03-22
9
+
10
+ ### Added
11
+
12
+ - Initial release of the Cynco TypeScript SDK.
13
+ - Resource clients: invoices, customers, vendors, bills, items, accounts, journal entries, bank accounts, reports, webhooks.
14
+ - Auto-pagination with `for await...of` support.
15
+ - Automatic retry with exponential backoff for rate limits and server errors.
16
+ - Idempotency key support for safe mutation retries.
17
+ - Webhook signature verification with HMAC-SHA256.
18
+ - Full TypeScript types for all API resources and operations.
19
+ - ESM and CommonJS dual-package output.
20
+ - Configurable base URL, timeout, max retries, and custom fetch.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Cynco Sdn Bhd
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,410 @@
1
+ # cynco
2
+
3
+ The official TypeScript SDK for the [Cynco](https://cynco.io) REST API.
4
+
5
+ Cynco is an AI-native business platform for accounting, invoicing, payments, and operations. This SDK provides full typed access to the Cynco API from any Node.js or TypeScript environment.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install cynco
11
+ ```
12
+
13
+ ```bash
14
+ pnpm add cynco
15
+ ```
16
+
17
+ ```bash
18
+ yarn add cynco
19
+ ```
20
+
21
+ **Requirements:** Node.js 18 or later.
22
+
23
+ ## Quick Start
24
+
25
+ ```typescript
26
+ import Cynco from 'cynco';
27
+
28
+ const cynco = new Cynco('cak_your_api_key');
29
+
30
+ // List invoices
31
+ const page = await cynco.invoices.list({ status: 'paid', limit: 20 });
32
+ console.log(page.data); // Invoice[]
33
+
34
+ // Create a customer
35
+ const customer = await cynco.customers.create({
36
+ name: 'Acme Corp',
37
+ email: 'billing@acme.com',
38
+ type: 'company',
39
+ currency: 'MYR',
40
+ });
41
+
42
+ // Retrieve a single resource
43
+ const invoice = await cynco.invoices.retrieve('inv_abc123');
44
+ ```
45
+
46
+ ## Configuration
47
+
48
+ ```typescript
49
+ const cynco = new Cynco('cak_your_api_key', {
50
+ baseUrl: 'https://app.cynco.io/api/v1', // Default
51
+ timeout: 30_000, // 30 seconds (default)
52
+ maxRetries: 3, // Automatic retries (default)
53
+ });
54
+ ```
55
+
56
+ | Option | Type | Default | Description |
57
+ |--------|------|---------|-------------|
58
+ | `baseUrl` | `string` | `https://app.cynco.io/api/v1` | API base URL |
59
+ | `timeout` | `number` | `30000` | Request timeout in milliseconds |
60
+ | `maxRetries` | `number` | `3` | Max retries on transient failures |
61
+ | `fetch` | `typeof fetch` | `globalThis.fetch` | Custom fetch implementation |
62
+
63
+ ## Resources
64
+
65
+ All resources follow a consistent pattern:
66
+
67
+ ```typescript
68
+ cynco.{resource}.list(params?) // Paginated list (also async iterable)
69
+ cynco.{resource}.retrieve(id) // Get by ID
70
+ cynco.{resource}.create(data, opts) // Create (where applicable)
71
+ cynco.{resource}.update(id, data) // Update (where applicable)
72
+ cynco.{resource}.delete(id) // Delete (where applicable)
73
+ ```
74
+
75
+ ### Invoices
76
+
77
+ ```typescript
78
+ // List with filters
79
+ const page = await cynco.invoices.list({
80
+ status: 'overdue',
81
+ customerId: 'cust_abc',
82
+ dateFrom: '2026-01-01',
83
+ limit: 50,
84
+ });
85
+
86
+ // Create
87
+ const invoice = await cynco.invoices.create({
88
+ customerId: 'cust_abc',
89
+ issueDate: '2026-03-22',
90
+ dueDate: '2026-04-22',
91
+ lineItems: [
92
+ { description: 'Consulting', quantity: 10, unitPrice: 500 },
93
+ ],
94
+ });
95
+
96
+ // Actions
97
+ await cynco.invoices.send('inv_123');
98
+ await cynco.invoices.markPaid('inv_123', { paidDate: '2026-03-25' });
99
+ await cynco.invoices.void('inv_123');
100
+
101
+ // PDF
102
+ const { url } = await cynco.invoices.getPdf('inv_123');
103
+ ```
104
+
105
+ ### Customers
106
+
107
+ ```typescript
108
+ const page = await cynco.customers.list({ search: 'acme', type: 'company' });
109
+ const customer = await cynco.customers.create({ name: 'Acme Corp' });
110
+ const updated = await cynco.customers.update('cust_123', { name: 'Acme Inc' });
111
+ await cynco.customers.delete('cust_123');
112
+ ```
113
+
114
+ ### Vendors
115
+
116
+ ```typescript
117
+ const page = await cynco.vendors.list({ search: 'supplier' });
118
+ const vendor = await cynco.vendors.create({
119
+ name: 'Office Supplies Co',
120
+ email: 'accounts@supplier.com',
121
+ });
122
+ ```
123
+
124
+ ### Bills
125
+
126
+ ```typescript
127
+ const page = await cynco.bills.list({ status: 'received', vendorId: 'vnd_123' });
128
+ const bill = await cynco.bills.create({
129
+ vendorId: 'vnd_123',
130
+ issueDate: '2026-03-20',
131
+ dueDate: '2026-04-20',
132
+ lineItems: [{ description: 'Paper supplies', quantity: 100, unitPrice: 5 }],
133
+ });
134
+
135
+ await cynco.bills.markPaid('bill_123');
136
+ await cynco.bills.void('bill_123');
137
+ ```
138
+
139
+ ### Items
140
+
141
+ ```typescript
142
+ const page = await cynco.items.list({ type: 'service' });
143
+ const item = await cynco.items.create({
144
+ name: 'Consulting Hour',
145
+ type: 'service',
146
+ unitPrice: 500,
147
+ });
148
+ ```
149
+
150
+ ### Chart of Accounts
151
+
152
+ ```typescript
153
+ const page = await cynco.accounts.list({ type: 'revenue' });
154
+ const account = await cynco.accounts.create({
155
+ name: 'Sales Revenue',
156
+ code: '4000',
157
+ type: 'revenue',
158
+ });
159
+ ```
160
+
161
+ ### Journal Entries
162
+
163
+ ```typescript
164
+ const page = await cynco.journalEntries.list({ status: 'posted' });
165
+ const entry = await cynco.journalEntries.create({
166
+ date: '2026-03-22',
167
+ description: 'Monthly depreciation',
168
+ lines: [
169
+ { accountId: 'acc_dep_exp', debit: 1000, credit: 0 },
170
+ { accountId: 'acc_accum_dep', debit: 0, credit: 1000 },
171
+ ],
172
+ });
173
+
174
+ await cynco.journalEntries.post('je_123');
175
+ await cynco.journalEntries.void('je_123');
176
+ ```
177
+
178
+ ### Bank Accounts
179
+
180
+ ```typescript
181
+ const page = await cynco.bankAccounts.list();
182
+ const account = await cynco.bankAccounts.create({
183
+ name: 'Operating Account',
184
+ accountNumber: '1234567890',
185
+ bankName: 'Maybank',
186
+ currency: 'MYR',
187
+ });
188
+
189
+ // List transactions for a bank account
190
+ const txns = await cynco.bankAccounts.listTransactions('ba_123', {
191
+ status: 'cleared',
192
+ dateFrom: '2026-03-01',
193
+ });
194
+ ```
195
+
196
+ ### Reports
197
+
198
+ ```typescript
199
+ const bs = await cynco.reports.balanceSheet({
200
+ endDate: '2026-03-31',
201
+ currency: 'MYR',
202
+ });
203
+ console.log(bs.totalAssets, bs.totalLiabilities, bs.totalEquity);
204
+
205
+ const pnl = await cynco.reports.profitAndLoss({
206
+ startDate: '2026-01-01',
207
+ endDate: '2026-03-31',
208
+ });
209
+ console.log(pnl.netIncome);
210
+
211
+ const tb = await cynco.reports.trialBalance({ endDate: '2026-03-31' });
212
+ console.log(tb.totalDebits, tb.totalCredits);
213
+ ```
214
+
215
+ ### Webhooks
216
+
217
+ ```typescript
218
+ const page = await cynco.webhooks.list();
219
+ const webhook = await cynco.webhooks.create({
220
+ url: 'https://example.com/webhooks/cynco',
221
+ events: ['invoice.paid', 'customer.created'],
222
+ });
223
+
224
+ await cynco.webhooks.update('wh_123', { isActive: false });
225
+ const { secret } = await cynco.webhooks.rotateSecret('wh_123');
226
+ ```
227
+
228
+ ## Pagination
229
+
230
+ ### Standard (single page)
231
+
232
+ ```typescript
233
+ const page = await cynco.invoices.list({ limit: 20, offset: 40 });
234
+
235
+ console.log(page.data); // Invoice[]
236
+ console.log(page.pagination.total); // Total count
237
+ console.log(page.pagination.hasMore); // More pages?
238
+
239
+ // Manual pagination
240
+ const nextPage = await page.nextPage(); // Page<Invoice> | null
241
+ ```
242
+
243
+ ### Auto-pagination
244
+
245
+ Iterate over all items across all pages automatically:
246
+
247
+ ```typescript
248
+ for await (const invoice of cynco.invoices.list({ limit: 50 })) {
249
+ console.log(invoice.id);
250
+ }
251
+ ```
252
+
253
+ ### Collect all items
254
+
255
+ ```typescript
256
+ const allCustomers: Customer[] = [];
257
+ for await (const customer of cynco.customers.list({ limit: 100 })) {
258
+ allCustomers.push(customer);
259
+ }
260
+ ```
261
+
262
+ ## Error Handling
263
+
264
+ All API errors are instances of `CyncoError` with specific subclasses:
265
+
266
+ ```typescript
267
+ import Cynco, {
268
+ CyncoError,
269
+ AuthenticationError,
270
+ ValidationError,
271
+ NotFoundError,
272
+ RateLimitError,
273
+ } from 'cynco';
274
+
275
+ try {
276
+ await cynco.customers.create({ name: '' });
277
+ } catch (error) {
278
+ if (error instanceof ValidationError) {
279
+ console.log(error.status); // 422
280
+ console.log(error.code); // "validation_error"
281
+ console.log(error.message); // "Validation failed"
282
+ console.log(error.details); // [{ field: "name", message: "is required" }]
283
+ console.log(error.requestId); // UUID for support
284
+ }
285
+ }
286
+ ```
287
+
288
+ | Error Class | Status | When |
289
+ |-------------|--------|------|
290
+ | `AuthenticationError` | 401 | Invalid or missing API key |
291
+ | `PermissionError` | 403 | Insufficient permissions |
292
+ | `NotFoundError` | 404 | Resource does not exist |
293
+ | `ConflictError` | 409 | Conflicting request |
294
+ | `ValidationError` | 422 | Invalid request body |
295
+ | `RateLimitError` | 429 | Rate limit exceeded (after retries) |
296
+ | `InternalError` | 5xx | Server error (after retries) |
297
+ | `TimeoutError` | - | Request timed out |
298
+ | `ConnectionError` | - | Network unreachable |
299
+
300
+ ## Retries
301
+
302
+ The SDK automatically retries on rate limits (429) and server errors (5xx) with exponential backoff and jitter.
303
+
304
+ - **GET, HEAD, DELETE** requests are always retried.
305
+ - **POST, PATCH, PUT** requests are only retried if an idempotency key is provided (except for 429, which is always safe to retry since the request was never processed).
306
+ - Retries respect the `Retry-After` header.
307
+ - Maximum retry count is configurable via `maxRetries` (default: 3).
308
+
309
+ ## Idempotency
310
+
311
+ For safe retries on mutations, pass an idempotency key:
312
+
313
+ ```typescript
314
+ const customer = await cynco.customers.create(
315
+ { name: 'Acme Corp' },
316
+ { idempotencyKey: 'create-acme-20260322' },
317
+ );
318
+ ```
319
+
320
+ If a request with the same idempotency key was already processed, the API returns the original response instead of creating a duplicate.
321
+
322
+ ## Webhook Verification
323
+
324
+ Verify incoming webhook signatures using the static `Cynco.webhooks` utility. No client instantiation needed.
325
+
326
+ ```typescript
327
+ import Cynco from 'cynco';
328
+
329
+ // In your webhook handler (e.g. Express)
330
+ app.post('/webhooks/cynco', (req, res) => {
331
+ const payload = req.body; // Raw string body
332
+ const signature = req.headers['x-webhook-signature'];
333
+ const timestamp = req.headers['x-webhook-timestamp'];
334
+
335
+ const isValid = Cynco.webhooks.verify(
336
+ payload,
337
+ signature,
338
+ timestamp,
339
+ process.env.CYNCO_WEBHOOK_SECRET,
340
+ );
341
+
342
+ if (!isValid) {
343
+ return res.status(401).send('Invalid signature');
344
+ }
345
+
346
+ const event = JSON.parse(payload);
347
+ // Handle the event...
348
+
349
+ res.sendStatus(200);
350
+ });
351
+ ```
352
+
353
+ ### Verify or throw
354
+
355
+ ```typescript
356
+ try {
357
+ Cynco.webhooks.verifyOrThrow(payload, signature, timestamp, secret);
358
+ } catch (error) {
359
+ // "Invalid webhook signature" or "Webhook timestamp is too old"
360
+ }
361
+ ```
362
+
363
+ ### Custom tolerance
364
+
365
+ By default, timestamps older than 5 minutes are rejected. You can customise this:
366
+
367
+ ```typescript
368
+ Cynco.webhooks.verify(payload, signature, timestamp, secret, {
369
+ tolerance: 600, // 10 minutes
370
+ });
371
+ ```
372
+
373
+ ### Testing webhooks
374
+
375
+ Generate test signatures for development:
376
+
377
+ ```typescript
378
+ const { signature, timestamp } = Cynco.webhooks.sign(
379
+ JSON.stringify({ event: 'invoice.paid', data: {} }),
380
+ 'whsec_your_test_secret',
381
+ );
382
+ ```
383
+
384
+ ## TypeScript
385
+
386
+ Every resource, parameter, and response is fully typed. Import individual types as needed:
387
+
388
+ ```typescript
389
+ import type {
390
+ Invoice,
391
+ Customer,
392
+ InvoiceCreateInput,
393
+ CustomerListParams,
394
+ PaginatedResponse,
395
+ WebhookEvent,
396
+ } from 'cynco';
397
+ ```
398
+
399
+ ## CommonJS
400
+
401
+ The SDK ships as a dual ESM/CJS package:
402
+
403
+ ```javascript
404
+ const { default: Cynco } = require('cynco');
405
+ const cynco = new Cynco('cak_your_api_key');
406
+ ```
407
+
408
+ ## License
409
+
410
+ MIT - Cynco Sdn Bhd