@rynko/sdk 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/dist/index.js ADDED
@@ -0,0 +1,640 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ DocumentsResource: () => DocumentsResource,
24
+ Rynko: () => Rynko,
25
+ RynkoError: () => RynkoError,
26
+ TemplatesResource: () => TemplatesResource,
27
+ WebhookSignatureError: () => WebhookSignatureError,
28
+ WebhooksResource: () => WebhooksResource,
29
+ computeSignature: () => computeSignature,
30
+ createClient: () => createClient,
31
+ parseSignatureHeader: () => parseSignatureHeader,
32
+ verifyWebhookSignature: () => verifyWebhookSignature
33
+ });
34
+ module.exports = __toCommonJS(index_exports);
35
+
36
+ // src/utils/http.ts
37
+ var DEFAULT_RETRY_CONFIG = {
38
+ maxAttempts: 5,
39
+ initialDelayMs: 1e3,
40
+ maxDelayMs: 3e4,
41
+ maxJitterMs: 1e3,
42
+ retryableStatuses: [429, 503, 504]
43
+ };
44
+ var HttpClient = class {
45
+ constructor(config) {
46
+ this.config = config;
47
+ if (config.retry === false) {
48
+ this.retryConfig = null;
49
+ } else {
50
+ this.retryConfig = {
51
+ ...DEFAULT_RETRY_CONFIG,
52
+ ...config.retry
53
+ };
54
+ }
55
+ }
56
+ /**
57
+ * Calculate delay for exponential backoff with jitter
58
+ */
59
+ calculateDelay(attempt, retryAfterMs) {
60
+ if (!this.retryConfig) return 0;
61
+ if (retryAfterMs !== void 0) {
62
+ const jitter2 = Math.random() * this.retryConfig.maxJitterMs;
63
+ return Math.min(retryAfterMs + jitter2, this.retryConfig.maxDelayMs);
64
+ }
65
+ const exponentialDelay = this.retryConfig.initialDelayMs * Math.pow(2, attempt);
66
+ const jitter = Math.random() * this.retryConfig.maxJitterMs;
67
+ return Math.min(exponentialDelay + jitter, this.retryConfig.maxDelayMs);
68
+ }
69
+ /**
70
+ * Parse Retry-After header value to milliseconds
71
+ */
72
+ parseRetryAfter(retryAfter) {
73
+ if (!retryAfter) return void 0;
74
+ const seconds = parseInt(retryAfter, 10);
75
+ if (!isNaN(seconds)) {
76
+ return seconds * 1e3;
77
+ }
78
+ const date = new Date(retryAfter);
79
+ if (!isNaN(date.getTime())) {
80
+ const delayMs = date.getTime() - Date.now();
81
+ return delayMs > 0 ? delayMs : void 0;
82
+ }
83
+ return void 0;
84
+ }
85
+ /**
86
+ * Check if the status code should trigger a retry
87
+ */
88
+ shouldRetry(statusCode) {
89
+ if (!this.retryConfig) return false;
90
+ return this.retryConfig.retryableStatuses.includes(statusCode);
91
+ }
92
+ /**
93
+ * Sleep for the specified duration
94
+ */
95
+ sleep(ms) {
96
+ return new Promise((resolve) => setTimeout(resolve, ms));
97
+ }
98
+ async request(method, path, options = {}) {
99
+ const url = new URL(path, this.config.baseUrl);
100
+ if (options.query) {
101
+ Object.entries(options.query).forEach(([key, value]) => {
102
+ if (value !== void 0 && value !== null) {
103
+ if (value instanceof Date) {
104
+ url.searchParams.append(key, value.toISOString());
105
+ } else {
106
+ url.searchParams.append(key, String(value));
107
+ }
108
+ }
109
+ });
110
+ }
111
+ const headers = {
112
+ "Content-Type": "application/json",
113
+ Authorization: `Bearer ${this.config.apiKey}`,
114
+ "User-Agent": "@rynko/sdk/1.0.0",
115
+ ...this.config.headers
116
+ };
117
+ const maxAttempts = this.retryConfig?.maxAttempts ?? 1;
118
+ let lastError = null;
119
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
120
+ const controller = new AbortController();
121
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
122
+ try {
123
+ const response = await fetch(url.toString(), {
124
+ method,
125
+ headers,
126
+ body: options.body ? JSON.stringify(options.body) : void 0,
127
+ signal: controller.signal
128
+ });
129
+ clearTimeout(timeoutId);
130
+ if (!response.ok && this.shouldRetry(response.status)) {
131
+ const retryAfterMs = this.parseRetryAfter(response.headers.get("Retry-After"));
132
+ const delay = this.calculateDelay(attempt, retryAfterMs);
133
+ const data2 = await response.json().catch(() => ({}));
134
+ const error = data2;
135
+ lastError = new RynkoError(
136
+ error.message || `HTTP ${response.status}`,
137
+ error.error || "ApiError",
138
+ response.status
139
+ );
140
+ if (attempt < maxAttempts - 1) {
141
+ await this.sleep(delay);
142
+ continue;
143
+ }
144
+ }
145
+ const data = await response.json();
146
+ if (!response.ok) {
147
+ const error = data;
148
+ throw new RynkoError(
149
+ error.message || `HTTP ${response.status}`,
150
+ error.error || "ApiError",
151
+ response.status
152
+ );
153
+ }
154
+ return data;
155
+ } catch (error) {
156
+ clearTimeout(timeoutId);
157
+ if (error instanceof RynkoError) {
158
+ if (!this.shouldRetry(error.statusCode) || attempt >= maxAttempts - 1) {
159
+ throw error;
160
+ }
161
+ lastError = error;
162
+ const delay = this.calculateDelay(attempt);
163
+ await this.sleep(delay);
164
+ continue;
165
+ }
166
+ if (error instanceof Error) {
167
+ if (error.name === "AbortError") {
168
+ throw new RynkoError("Request timeout", "TimeoutError", 408);
169
+ }
170
+ throw new RynkoError(error.message, "NetworkError", 0);
171
+ }
172
+ throw new RynkoError("Unknown error", "UnknownError", 0);
173
+ }
174
+ }
175
+ if (lastError) {
176
+ throw lastError;
177
+ }
178
+ throw new RynkoError("Request failed after retries", "RetryExhausted", 0);
179
+ }
180
+ async get(path, query) {
181
+ return this.request("GET", path, { query });
182
+ }
183
+ async post(path, body) {
184
+ return this.request("POST", path, { body });
185
+ }
186
+ async put(path, body) {
187
+ return this.request("PUT", path, { body });
188
+ }
189
+ async patch(path, body) {
190
+ return this.request("PATCH", path, { body });
191
+ }
192
+ async delete(path) {
193
+ return this.request("DELETE", path);
194
+ }
195
+ };
196
+ var RynkoError = class extends Error {
197
+ constructor(message, code, statusCode) {
198
+ super(message);
199
+ this.name = "RynkoError";
200
+ this.code = code;
201
+ this.statusCode = statusCode;
202
+ }
203
+ };
204
+
205
+ // src/resources/documents.ts
206
+ var DocumentsResource = class {
207
+ constructor(http) {
208
+ this.http = http;
209
+ }
210
+ /**
211
+ * Generate a document from a template
212
+ *
213
+ * @example
214
+ * ```typescript
215
+ * const result = await rynko.documents.generate({
216
+ * templateId: 'tmpl_abc123',
217
+ * format: 'pdf',
218
+ * variables: {
219
+ * customerName: 'John Doe',
220
+ * invoiceNumber: 'INV-001',
221
+ * amount: 150.00,
222
+ * },
223
+ * });
224
+ * console.log('Job ID:', result.jobId);
225
+ * console.log('Download URL:', result.downloadUrl);
226
+ * ```
227
+ */
228
+ async generate(options) {
229
+ return this.http.post(
230
+ "/api/v1/documents/generate",
231
+ options
232
+ );
233
+ }
234
+ /**
235
+ * Generate a PDF document from a template
236
+ *
237
+ * @example
238
+ * ```typescript
239
+ * const result = await rynko.documents.generatePdf({
240
+ * templateId: 'tmpl_invoice',
241
+ * variables: {
242
+ * invoiceNumber: 'INV-001',
243
+ * total: 99.99,
244
+ * },
245
+ * });
246
+ * ```
247
+ */
248
+ async generatePdf(options) {
249
+ return this.generate({ ...options, format: "pdf" });
250
+ }
251
+ /**
252
+ * Generate an Excel document from a template
253
+ *
254
+ * @example
255
+ * ```typescript
256
+ * const result = await rynko.documents.generateExcel({
257
+ * templateId: 'tmpl_report',
258
+ * variables: {
259
+ * reportDate: '2025-01-15',
260
+ * data: [{ name: 'Item 1', value: 100 }],
261
+ * },
262
+ * });
263
+ * ```
264
+ */
265
+ async generateExcel(options) {
266
+ return this.generate({ ...options, format: "excel" });
267
+ }
268
+ /**
269
+ * Generate multiple documents in a batch
270
+ *
271
+ * @example
272
+ * ```typescript
273
+ * const result = await rynko.documents.generateBatch({
274
+ * templateId: 'tmpl_invoice',
275
+ * format: 'pdf',
276
+ * documents: [
277
+ * { variables: { invoiceNumber: 'INV-001', total: 99.99 } },
278
+ * { variables: { invoiceNumber: 'INV-002', total: 149.99 } },
279
+ * ],
280
+ * });
281
+ * console.log('Batch ID:', result.batchId);
282
+ * ```
283
+ */
284
+ async generateBatch(options) {
285
+ return this.http.post(
286
+ "/api/v1/documents/generate/batch",
287
+ options
288
+ );
289
+ }
290
+ /**
291
+ * Get a document job by ID
292
+ *
293
+ * @example
294
+ * ```typescript
295
+ * const job = await rynko.documents.getJob('job_abc123');
296
+ * console.log('Status:', job.status);
297
+ * if (job.status === 'completed') {
298
+ * console.log('Download:', job.downloadUrl);
299
+ * }
300
+ * ```
301
+ */
302
+ async getJob(jobId) {
303
+ return this.http.get(`/api/v1/documents/jobs/${jobId}`);
304
+ }
305
+ /**
306
+ * List document jobs with optional filters
307
+ *
308
+ * @example
309
+ * ```typescript
310
+ * const { data, meta } = await rynko.documents.listJobs({
311
+ * status: 'completed',
312
+ * limit: 10,
313
+ * });
314
+ * console.log(`Found ${meta.total} jobs`);
315
+ * ```
316
+ */
317
+ async listJobs(options = {}) {
318
+ const response = await this.http.get(
319
+ "/api/v1/documents/jobs",
320
+ {
321
+ status: options.status,
322
+ templateId: options.templateId,
323
+ workspaceId: options.workspaceId,
324
+ limit: options.limit,
325
+ offset: options.offset ?? ((options.page ?? 1) - 1) * (options.limit ?? 20)
326
+ }
327
+ );
328
+ const limit = options.limit ?? 20;
329
+ const page = options.page ?? 1;
330
+ return {
331
+ data: response.jobs,
332
+ meta: {
333
+ total: response.total,
334
+ page,
335
+ limit,
336
+ totalPages: Math.ceil(response.total / limit)
337
+ }
338
+ };
339
+ }
340
+ /**
341
+ * Wait for a document job to complete
342
+ *
343
+ * @example
344
+ * ```typescript
345
+ * const result = await rynko.documents.generate({
346
+ * templateId: 'tmpl_invoice',
347
+ * format: 'pdf',
348
+ * variables: { invoiceNumber: 'INV-001' },
349
+ * });
350
+ *
351
+ * // Wait for completion (polls every 1 second, max 30 seconds)
352
+ * const completedJob = await rynko.documents.waitForCompletion(result.jobId);
353
+ * console.log('Download URL:', completedJob.downloadUrl);
354
+ * ```
355
+ */
356
+ async waitForCompletion(jobId, options = {}) {
357
+ const pollInterval = options.pollInterval || 1e3;
358
+ const timeout = options.timeout || 3e4;
359
+ const startTime = Date.now();
360
+ while (true) {
361
+ const job = await this.getJob(jobId);
362
+ if (job.status === "completed" || job.status === "failed") {
363
+ return job;
364
+ }
365
+ if (Date.now() - startTime > timeout) {
366
+ throw new Error(`Timeout waiting for job ${jobId} to complete`);
367
+ }
368
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
369
+ }
370
+ }
371
+ };
372
+
373
+ // src/resources/templates.ts
374
+ var TemplatesResource = class {
375
+ constructor(http) {
376
+ this.http = http;
377
+ }
378
+ /**
379
+ * Get a template by ID
380
+ *
381
+ * @example
382
+ * ```typescript
383
+ * const template = await rynko.templates.get('tmpl_abc123');
384
+ * console.log('Template:', template.name);
385
+ * console.log('Variables:', template.variables);
386
+ * ```
387
+ */
388
+ async get(id) {
389
+ return this.http.get(`/api/templates/${id}`);
390
+ }
391
+ /**
392
+ * List templates with optional filters
393
+ *
394
+ * @example
395
+ * ```typescript
396
+ * // List all templates
397
+ * const { data } = await rynko.templates.list();
398
+ *
399
+ * // List with pagination
400
+ * const { data, meta } = await rynko.templates.list({ page: 1, limit: 10 });
401
+ * ```
402
+ */
403
+ async list(options = {}) {
404
+ const response = await this.http.get(
405
+ "/api/templates/attachment",
406
+ {
407
+ limit: options.limit,
408
+ page: options.page,
409
+ search: options.search
410
+ }
411
+ );
412
+ return {
413
+ data: response.data,
414
+ meta: {
415
+ total: response.total,
416
+ page: response.page,
417
+ limit: response.limit,
418
+ totalPages: response.totalPages
419
+ }
420
+ };
421
+ }
422
+ /**
423
+ * List only PDF templates
424
+ *
425
+ * Note: Filtering by type is done client-side based on outputFormats.
426
+ *
427
+ * @example
428
+ * ```typescript
429
+ * const { data } = await rynko.templates.listPdf();
430
+ * ```
431
+ */
432
+ async listPdf(options = {}) {
433
+ const result = await this.list(options);
434
+ result.data = result.data.filter(
435
+ (t) => t.outputFormats?.includes("pdf")
436
+ );
437
+ return result;
438
+ }
439
+ /**
440
+ * List only Excel templates
441
+ *
442
+ * Note: Filtering by type is done client-side based on outputFormats.
443
+ *
444
+ * @example
445
+ * ```typescript
446
+ * const { data } = await rynko.templates.listExcel();
447
+ * ```
448
+ */
449
+ async listExcel(options = {}) {
450
+ const result = await this.list(options);
451
+ result.data = result.data.filter(
452
+ (t) => t.outputFormats?.includes("xlsx") || t.outputFormats?.includes("excel")
453
+ );
454
+ return result;
455
+ }
456
+ };
457
+
458
+ // src/resources/webhooks.ts
459
+ var WebhooksResource = class {
460
+ constructor(http) {
461
+ this.http = http;
462
+ }
463
+ /**
464
+ * Get a webhook subscription by ID
465
+ *
466
+ * @example
467
+ * ```typescript
468
+ * const webhook = await rynko.webhooks.get('wh_abc123');
469
+ * console.log('Events:', webhook.events);
470
+ * ```
471
+ */
472
+ async get(id) {
473
+ return this.http.get(
474
+ `/api/v1/webhook-subscriptions/${id}`
475
+ );
476
+ }
477
+ /**
478
+ * List all webhook subscriptions
479
+ *
480
+ * @example
481
+ * ```typescript
482
+ * const { data } = await rynko.webhooks.list();
483
+ * console.log('Active webhooks:', data.filter(w => w.isActive).length);
484
+ * ```
485
+ */
486
+ async list() {
487
+ const response = await this.http.get(
488
+ "/api/v1/webhook-subscriptions"
489
+ );
490
+ return {
491
+ data: response.data,
492
+ meta: {
493
+ total: response.total,
494
+ page: 1,
495
+ limit: response.data.length,
496
+ totalPages: 1
497
+ }
498
+ };
499
+ }
500
+ };
501
+
502
+ // src/client.ts
503
+ var DEFAULT_BASE_URL = "https://api.rynko.dev";
504
+ var DEFAULT_TIMEOUT = 3e4;
505
+ var Rynko = class {
506
+ /**
507
+ * Create a new Rynko client
508
+ *
509
+ * @example
510
+ * ```typescript
511
+ * import { Rynko } from '@rynko/sdk';
512
+ *
513
+ * const rynko = new Rynko({
514
+ * apiKey: process.env.RYNKO_API_KEY!,
515
+ * });
516
+ *
517
+ * // Generate a PDF
518
+ * const result = await rynko.documents.generate({
519
+ * templateId: 'tmpl_invoice',
520
+ * format: 'pdf',
521
+ * variables: { invoiceNumber: 'INV-001' },
522
+ * });
523
+ * ```
524
+ */
525
+ constructor(config) {
526
+ if (!config.apiKey) {
527
+ throw new Error("apiKey is required");
528
+ }
529
+ this.http = new HttpClient({
530
+ baseUrl: config.baseUrl || DEFAULT_BASE_URL,
531
+ apiKey: config.apiKey,
532
+ timeout: config.timeout || DEFAULT_TIMEOUT,
533
+ headers: config.headers,
534
+ retry: config.retry
535
+ });
536
+ this.documents = new DocumentsResource(this.http);
537
+ this.templates = new TemplatesResource(this.http);
538
+ this.webhooks = new WebhooksResource(this.http);
539
+ }
540
+ /**
541
+ * Get the current authenticated user
542
+ *
543
+ * @example
544
+ * ```typescript
545
+ * const user = await rynko.me();
546
+ * console.log('Authenticated as:', user.email);
547
+ * ```
548
+ */
549
+ async me() {
550
+ return this.http.get("/api/auth/verify");
551
+ }
552
+ /**
553
+ * Verify the API key is valid
554
+ *
555
+ * @example
556
+ * ```typescript
557
+ * const isValid = await rynko.verifyApiKey();
558
+ * if (!isValid) {
559
+ * throw new Error('Invalid API key');
560
+ * }
561
+ * ```
562
+ */
563
+ async verifyApiKey() {
564
+ try {
565
+ await this.me();
566
+ return true;
567
+ } catch {
568
+ return false;
569
+ }
570
+ }
571
+ };
572
+ function createClient(config) {
573
+ return new Rynko(config);
574
+ }
575
+
576
+ // src/utils/webhooks.ts
577
+ var import_crypto = require("crypto");
578
+ function parseSignatureHeader(header) {
579
+ const parts = header.split(",");
580
+ const parsed = {};
581
+ for (const part of parts) {
582
+ const [key, value] = part.split("=");
583
+ if (key === "t") {
584
+ parsed.timestamp = parseInt(value, 10);
585
+ } else if (key === "v1") {
586
+ parsed.signature = value;
587
+ }
588
+ }
589
+ if (!parsed.timestamp || !parsed.signature) {
590
+ throw new WebhookSignatureError("Invalid signature header format");
591
+ }
592
+ return parsed;
593
+ }
594
+ function computeSignature(timestamp, payload, secret) {
595
+ const signedPayload = `${timestamp}.${payload}`;
596
+ return (0, import_crypto.createHmac)("sha256", secret).update(signedPayload).digest("hex");
597
+ }
598
+ function verifyWebhookSignature(options) {
599
+ const { payload, signature, secret, tolerance = 300 } = options;
600
+ const { timestamp, signature: expectedSig } = parseSignatureHeader(signature);
601
+ const now = Math.floor(Date.now() / 1e3);
602
+ if (Math.abs(now - timestamp) > tolerance) {
603
+ throw new WebhookSignatureError(
604
+ "Webhook timestamp outside tolerance window"
605
+ );
606
+ }
607
+ const computedSig = computeSignature(timestamp, payload, secret);
608
+ const sigBuffer = Buffer.from(expectedSig, "hex");
609
+ const computedBuffer = Buffer.from(computedSig, "hex");
610
+ if (sigBuffer.length !== computedBuffer.length) {
611
+ throw new WebhookSignatureError("Invalid signature");
612
+ }
613
+ if (!(0, import_crypto.timingSafeEqual)(sigBuffer, computedBuffer)) {
614
+ throw new WebhookSignatureError("Invalid signature");
615
+ }
616
+ try {
617
+ return JSON.parse(payload);
618
+ } catch {
619
+ throw new WebhookSignatureError("Invalid webhook payload");
620
+ }
621
+ }
622
+ var WebhookSignatureError = class extends Error {
623
+ constructor(message) {
624
+ super(message);
625
+ this.name = "WebhookSignatureError";
626
+ }
627
+ };
628
+ // Annotate the CommonJS export names for ESM import in node:
629
+ 0 && (module.exports = {
630
+ DocumentsResource,
631
+ Rynko,
632
+ RynkoError,
633
+ TemplatesResource,
634
+ WebhookSignatureError,
635
+ WebhooksResource,
636
+ computeSignature,
637
+ createClient,
638
+ parseSignatureHeader,
639
+ verifyWebhookSignature
640
+ });