@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/README.md +735 -0
- package/dist/index.d.mts +616 -0
- package/dist/index.d.ts +616 -0
- package/dist/index.js +640 -0
- package/dist/index.mjs +604 -0
- package/package.json +66 -0
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
|
+
});
|