@canveletedotcom/sdk 2.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 +143 -0
- package/dist/client.d.ts +46 -0
- package/dist/client.js +132 -0
- package/dist/errors.d.ts +27 -0
- package/dist/errors.js +48 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +8 -0
- package/dist/resources/apiKeys.d.ts +22 -0
- package/dist/resources/apiKeys.js +15 -0
- package/dist/resources/assets.d.ts +101 -0
- package/dist/resources/assets.js +93 -0
- package/dist/resources/billing.d.ts +96 -0
- package/dist/resources/billing.js +64 -0
- package/dist/resources/canvas.d.ts +36 -0
- package/dist/resources/canvas.js +26 -0
- package/dist/resources/designs.d.ts +42 -0
- package/dist/resources/designs.js +46 -0
- package/dist/resources/index.d.ts +15 -0
- package/dist/resources/index.js +9 -0
- package/dist/resources/render.d.ts +90 -0
- package/dist/resources/render.js +171 -0
- package/dist/resources/templates.d.ts +53 -0
- package/dist/resources/templates.js +76 -0
- package/dist/resources/usage.d.ts +76 -0
- package/dist/resources/usage.js +52 -0
- package/dist/types/index.d.ts +197 -0
- package/dist/types/index.js +4 -0
- package/dist/utils/index.d.ts +9 -0
- package/dist/utils/index.js +6 -0
- package/dist/utils/retry.d.ts +22 -0
- package/dist/utils/retry.js +70 -0
- package/dist/utils/validation.d.ts +69 -0
- package/dist/utils/validation.js +126 -0
- package/dist/utils/webhooks.d.ts +31 -0
- package/dist/utils/webhooks.js +70 -0
- package/package.json +57 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Templates resource for managing design templates
|
|
3
|
+
*/
|
|
4
|
+
export class TemplatesResource {
|
|
5
|
+
constructor(client) {
|
|
6
|
+
this.client = client;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* List available templates
|
|
10
|
+
*/
|
|
11
|
+
async list(options = {}) {
|
|
12
|
+
const params = {
|
|
13
|
+
page: String(options.page || 1),
|
|
14
|
+
limit: String(options.limit || 20),
|
|
15
|
+
};
|
|
16
|
+
if (options.myOnly)
|
|
17
|
+
params.myOnly = 'true';
|
|
18
|
+
if (options.search)
|
|
19
|
+
params.search = options.search;
|
|
20
|
+
if (options.category)
|
|
21
|
+
params.category = options.category;
|
|
22
|
+
return await this.client.request('GET', '/api/automation/templates', { params });
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Iterate through all templates with automatic pagination
|
|
26
|
+
*/
|
|
27
|
+
async *iterateAll(options = {}) {
|
|
28
|
+
let page = 1;
|
|
29
|
+
const limit = options.limit || 50;
|
|
30
|
+
while (true) {
|
|
31
|
+
const response = await this.list({ ...options, page, limit });
|
|
32
|
+
const templates = response.data || [];
|
|
33
|
+
if (templates.length === 0)
|
|
34
|
+
break;
|
|
35
|
+
for (const template of templates) {
|
|
36
|
+
yield template;
|
|
37
|
+
}
|
|
38
|
+
if (page >= (response.pagination?.totalPages || 1))
|
|
39
|
+
break;
|
|
40
|
+
page++;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Get a specific template by ID
|
|
45
|
+
*/
|
|
46
|
+
async get(id) {
|
|
47
|
+
return await this.client.request('GET', `/api/automation/designs/${id}`);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Apply a template to a design
|
|
51
|
+
*/
|
|
52
|
+
async apply(options) {
|
|
53
|
+
const payload = {
|
|
54
|
+
templateId: options.templateId,
|
|
55
|
+
};
|
|
56
|
+
if (options.dynamicData) {
|
|
57
|
+
payload.dynamicData = options.dynamicData;
|
|
58
|
+
}
|
|
59
|
+
return await this.client.request('POST', `/api/automation/designs/${options.designId}/apply-template`, { json: payload });
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Create a template from an existing design
|
|
63
|
+
*/
|
|
64
|
+
async create(options) {
|
|
65
|
+
const payload = {
|
|
66
|
+
name: options.name,
|
|
67
|
+
};
|
|
68
|
+
if (options.description) {
|
|
69
|
+
payload.description = options.description;
|
|
70
|
+
}
|
|
71
|
+
if (options.category) {
|
|
72
|
+
payload.category = options.category;
|
|
73
|
+
}
|
|
74
|
+
return await this.client.request('POST', `/api/automation/designs/${options.designId}/save-as-template`, { json: payload });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Usage tracking resource for monitoring API usage and credits
|
|
3
|
+
*/
|
|
4
|
+
import type { CanveleteClient } from '../client';
|
|
5
|
+
import type { PaginatedResponse } from '../types';
|
|
6
|
+
export interface UsageStats {
|
|
7
|
+
creditsUsed: number;
|
|
8
|
+
creditLimit: number;
|
|
9
|
+
creditsRemaining: number;
|
|
10
|
+
apiCalls: number;
|
|
11
|
+
apiCallLimit: number;
|
|
12
|
+
renders: number;
|
|
13
|
+
storageUsed: number;
|
|
14
|
+
}
|
|
15
|
+
export interface UsageEvent {
|
|
16
|
+
type: string;
|
|
17
|
+
creditsUsed: number;
|
|
18
|
+
timestamp: string;
|
|
19
|
+
details?: Record<string, any>;
|
|
20
|
+
}
|
|
21
|
+
export interface ApiStats {
|
|
22
|
+
endpoints: Record<string, number>;
|
|
23
|
+
totalCalls: number;
|
|
24
|
+
period: string;
|
|
25
|
+
}
|
|
26
|
+
export interface Activity {
|
|
27
|
+
action: string;
|
|
28
|
+
timestamp: string;
|
|
29
|
+
details?: Record<string, any>;
|
|
30
|
+
}
|
|
31
|
+
export interface Analytics {
|
|
32
|
+
totalRenders: number;
|
|
33
|
+
averagePerDay: number;
|
|
34
|
+
peakDay: string;
|
|
35
|
+
trend: 'increasing' | 'decreasing' | 'stable';
|
|
36
|
+
breakdown?: Record<string, number>;
|
|
37
|
+
}
|
|
38
|
+
export interface UsageHistoryOptions {
|
|
39
|
+
page?: number;
|
|
40
|
+
limit?: number;
|
|
41
|
+
startDate?: Date;
|
|
42
|
+
endDate?: Date;
|
|
43
|
+
}
|
|
44
|
+
export declare class UsageResource {
|
|
45
|
+
private client;
|
|
46
|
+
constructor(client: CanveleteClient);
|
|
47
|
+
/**
|
|
48
|
+
* Get current usage statistics
|
|
49
|
+
*/
|
|
50
|
+
getStats(): Promise<{
|
|
51
|
+
data: UsageStats;
|
|
52
|
+
}>;
|
|
53
|
+
/**
|
|
54
|
+
* Get usage history
|
|
55
|
+
*/
|
|
56
|
+
getHistory(options?: UsageHistoryOptions): Promise<PaginatedResponse<UsageEvent>>;
|
|
57
|
+
/**
|
|
58
|
+
* Get API usage statistics by endpoint
|
|
59
|
+
*/
|
|
60
|
+
getApiStats(): Promise<{
|
|
61
|
+
data: ApiStats;
|
|
62
|
+
}>;
|
|
63
|
+
/**
|
|
64
|
+
* Get recent activities
|
|
65
|
+
*/
|
|
66
|
+
getActivities(options?: {
|
|
67
|
+
page?: number;
|
|
68
|
+
limit?: number;
|
|
69
|
+
}): Promise<PaginatedResponse<Activity>>;
|
|
70
|
+
/**
|
|
71
|
+
* Get usage analytics
|
|
72
|
+
*/
|
|
73
|
+
getAnalytics(period?: 'day' | 'week' | 'month' | 'year'): Promise<{
|
|
74
|
+
data: Analytics;
|
|
75
|
+
}>;
|
|
76
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Usage tracking resource for monitoring API usage and credits
|
|
3
|
+
*/
|
|
4
|
+
export class UsageResource {
|
|
5
|
+
constructor(client) {
|
|
6
|
+
this.client = client;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Get current usage statistics
|
|
10
|
+
*/
|
|
11
|
+
async getStats() {
|
|
12
|
+
return await this.client.request('GET', '/api/v1/usage/stats');
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Get usage history
|
|
16
|
+
*/
|
|
17
|
+
async getHistory(options = {}) {
|
|
18
|
+
const params = {
|
|
19
|
+
page: String(options.page || 1),
|
|
20
|
+
limit: String(options.limit || 20),
|
|
21
|
+
};
|
|
22
|
+
if (options.startDate) {
|
|
23
|
+
params.startDate = options.startDate.toISOString();
|
|
24
|
+
}
|
|
25
|
+
if (options.endDate) {
|
|
26
|
+
params.endDate = options.endDate.toISOString();
|
|
27
|
+
}
|
|
28
|
+
return await this.client.request('GET', '/api/v1/usage/history', { params });
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Get API usage statistics by endpoint
|
|
32
|
+
*/
|
|
33
|
+
async getApiStats() {
|
|
34
|
+
return await this.client.request('GET', '/api/v1/usage/api-stats');
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Get recent activities
|
|
38
|
+
*/
|
|
39
|
+
async getActivities(options = {}) {
|
|
40
|
+
const params = {
|
|
41
|
+
page: String(options.page || 1),
|
|
42
|
+
limit: String(options.limit || 20),
|
|
43
|
+
};
|
|
44
|
+
return await this.client.request('GET', '/api/usage/activities', { params });
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get usage analytics
|
|
48
|
+
*/
|
|
49
|
+
async getAnalytics(period = 'month') {
|
|
50
|
+
return await this.client.request('GET', '/api/usage/analytics', { params: { period } });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for Canvelete API
|
|
3
|
+
*/
|
|
4
|
+
export interface ElementBase {
|
|
5
|
+
id?: string;
|
|
6
|
+
type: string;
|
|
7
|
+
x: number;
|
|
8
|
+
y: number;
|
|
9
|
+
width?: number;
|
|
10
|
+
height?: number;
|
|
11
|
+
rotation?: number;
|
|
12
|
+
opacity?: number;
|
|
13
|
+
visible?: boolean;
|
|
14
|
+
locked?: boolean;
|
|
15
|
+
name?: string;
|
|
16
|
+
groupId?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface RectangleElement extends ElementBase {
|
|
19
|
+
type: 'rectangle';
|
|
20
|
+
fill?: string;
|
|
21
|
+
stroke?: string;
|
|
22
|
+
strokeWidth?: number;
|
|
23
|
+
borderRadius?: number;
|
|
24
|
+
borderRadiusTopLeft?: number;
|
|
25
|
+
borderRadiusTopRight?: number;
|
|
26
|
+
borderRadiusBottomLeft?: number;
|
|
27
|
+
borderRadiusBottomRight?: number;
|
|
28
|
+
boxShadow?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface CircleElement extends ElementBase {
|
|
31
|
+
type: 'circle';
|
|
32
|
+
fill?: string;
|
|
33
|
+
stroke?: string;
|
|
34
|
+
strokeWidth?: number;
|
|
35
|
+
}
|
|
36
|
+
export interface TextElement extends ElementBase {
|
|
37
|
+
type: 'text';
|
|
38
|
+
text: string;
|
|
39
|
+
fontSize?: number;
|
|
40
|
+
fontFamily?: string;
|
|
41
|
+
fontWeight?: string;
|
|
42
|
+
fontStyle?: 'normal' | 'italic' | 'oblique';
|
|
43
|
+
fill?: string;
|
|
44
|
+
color?: string;
|
|
45
|
+
textAlign?: 'left' | 'center' | 'right' | 'justify';
|
|
46
|
+
textDecoration?: 'none' | 'underline' | 'line-through' | 'overline';
|
|
47
|
+
textTransform?: 'none' | 'uppercase' | 'lowercase' | 'capitalize';
|
|
48
|
+
lineHeight?: number;
|
|
49
|
+
letterSpacing?: number;
|
|
50
|
+
textStrokeColor?: string;
|
|
51
|
+
textStrokeWidth?: number;
|
|
52
|
+
textShadowColor?: string;
|
|
53
|
+
textShadowX?: number;
|
|
54
|
+
textShadowY?: number;
|
|
55
|
+
textShadowBlur?: number;
|
|
56
|
+
isDynamic?: boolean;
|
|
57
|
+
}
|
|
58
|
+
export interface ImageElement extends ElementBase {
|
|
59
|
+
type: 'image';
|
|
60
|
+
src: string;
|
|
61
|
+
objectFit?: 'fill' | 'contain' | 'cover' | 'none' | 'scale-down';
|
|
62
|
+
objectPosition?: string;
|
|
63
|
+
brightness?: number;
|
|
64
|
+
contrast?: number;
|
|
65
|
+
saturate?: number;
|
|
66
|
+
hueRotate?: number;
|
|
67
|
+
blur?: number;
|
|
68
|
+
}
|
|
69
|
+
export interface LineElement extends ElementBase {
|
|
70
|
+
type: 'line';
|
|
71
|
+
linePoints?: Array<{
|
|
72
|
+
x: number;
|
|
73
|
+
y: number;
|
|
74
|
+
}>;
|
|
75
|
+
stroke?: string;
|
|
76
|
+
strokeWidth?: number;
|
|
77
|
+
lineCap?: 'butt' | 'round' | 'square';
|
|
78
|
+
lineDash?: string;
|
|
79
|
+
}
|
|
80
|
+
export interface PolygonElement extends ElementBase {
|
|
81
|
+
type: 'polygon';
|
|
82
|
+
polygonPoints?: Array<{
|
|
83
|
+
x: number;
|
|
84
|
+
y: number;
|
|
85
|
+
}>;
|
|
86
|
+
polygonSides?: number;
|
|
87
|
+
fill?: string;
|
|
88
|
+
stroke?: string;
|
|
89
|
+
strokeWidth?: number;
|
|
90
|
+
svgPath?: string;
|
|
91
|
+
svgViewBox?: string;
|
|
92
|
+
}
|
|
93
|
+
export interface StarElement extends ElementBase {
|
|
94
|
+
type: 'star';
|
|
95
|
+
starPoints?: number;
|
|
96
|
+
starInnerRadius?: number;
|
|
97
|
+
fill?: string;
|
|
98
|
+
stroke?: string;
|
|
99
|
+
strokeWidth?: number;
|
|
100
|
+
}
|
|
101
|
+
export interface SvgElement extends ElementBase {
|
|
102
|
+
type: 'svg';
|
|
103
|
+
src?: string;
|
|
104
|
+
svgPath?: string;
|
|
105
|
+
svgViewBox?: string;
|
|
106
|
+
fill?: string;
|
|
107
|
+
stroke?: string;
|
|
108
|
+
}
|
|
109
|
+
export interface QrElement extends ElementBase {
|
|
110
|
+
type: 'qr';
|
|
111
|
+
qrValue: string;
|
|
112
|
+
qrColor?: string;
|
|
113
|
+
qrBgColor?: string;
|
|
114
|
+
qrErrorLevel?: 'L' | 'M' | 'Q' | 'H';
|
|
115
|
+
qrMargin?: number;
|
|
116
|
+
}
|
|
117
|
+
export interface BarcodeElement extends ElementBase {
|
|
118
|
+
type: 'barcode';
|
|
119
|
+
barcodeValue: string;
|
|
120
|
+
barcodeFormat?: 'CODE128' | 'CODE39' | 'EAN13' | 'EAN8' | 'UPC' | 'UPCE' | 'ITF14' | 'MSI' | 'pharmacode' | 'codabar';
|
|
121
|
+
barcodeLineColor?: string;
|
|
122
|
+
barcodeBackground?: string;
|
|
123
|
+
barcodeShowText?: boolean;
|
|
124
|
+
barcodeFontSize?: number;
|
|
125
|
+
barcodeTextAlign?: 'left' | 'center' | 'right';
|
|
126
|
+
barcodeTextMargin?: number;
|
|
127
|
+
}
|
|
128
|
+
export interface TableElement extends ElementBase {
|
|
129
|
+
type: 'table';
|
|
130
|
+
tableRows?: number;
|
|
131
|
+
tableColumns?: number;
|
|
132
|
+
tableCellData?: Array<Record<string, any>>;
|
|
133
|
+
tableHeaderData?: Array<Record<string, any>>;
|
|
134
|
+
tableHasHeader?: boolean;
|
|
135
|
+
tableHasVerticalHeader?: boolean;
|
|
136
|
+
tableBorderWidth?: number;
|
|
137
|
+
tableBorderColor?: string;
|
|
138
|
+
tableCellPadding?: number;
|
|
139
|
+
tableHeaderBackground?: string;
|
|
140
|
+
tableHeaderTextColor?: string;
|
|
141
|
+
tableAlternateRowColor?: string;
|
|
142
|
+
tableCellTextColor?: string;
|
|
143
|
+
tableCellFontSize?: number;
|
|
144
|
+
tableCellAlignment?: 'left' | 'center' | 'right';
|
|
145
|
+
tableHeaderAlignment?: 'left' | 'center' | 'right';
|
|
146
|
+
tableColumnWidths?: number[];
|
|
147
|
+
tableRowHeights?: number[];
|
|
148
|
+
}
|
|
149
|
+
export type CanvasElement = RectangleElement | CircleElement | TextElement | ImageElement | LineElement | PolygonElement | StarElement | SvgElement | QrElement | BarcodeElement | TableElement | ElementBase;
|
|
150
|
+
export interface CanvasData {
|
|
151
|
+
elements: CanvasElement[];
|
|
152
|
+
background?: string;
|
|
153
|
+
[key: string]: any;
|
|
154
|
+
}
|
|
155
|
+
export interface Design {
|
|
156
|
+
id: string;
|
|
157
|
+
name: string;
|
|
158
|
+
description?: string;
|
|
159
|
+
canvasData: CanvasData;
|
|
160
|
+
width: number;
|
|
161
|
+
height: number;
|
|
162
|
+
status: 'DRAFT' | 'PUBLISHED' | 'ARCHIVED';
|
|
163
|
+
visibility: 'PRIVATE' | 'PUBLIC' | 'TEAM';
|
|
164
|
+
isTemplate: boolean;
|
|
165
|
+
thumbnailUrl?: string;
|
|
166
|
+
createdAt: string;
|
|
167
|
+
updatedAt: string;
|
|
168
|
+
}
|
|
169
|
+
export interface Template extends Design {
|
|
170
|
+
isTemplate: true;
|
|
171
|
+
dynamicFields?: string[];
|
|
172
|
+
category?: string;
|
|
173
|
+
}
|
|
174
|
+
export interface PaginatedResponse<T> {
|
|
175
|
+
data: T[];
|
|
176
|
+
pagination: {
|
|
177
|
+
page: number;
|
|
178
|
+
limit: number;
|
|
179
|
+
total: number;
|
|
180
|
+
totalPages: number;
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
export interface APIKey {
|
|
184
|
+
id: string;
|
|
185
|
+
name: string;
|
|
186
|
+
keyPrefix: string;
|
|
187
|
+
status: 'ACTIVE' | 'REVOKED';
|
|
188
|
+
scopes: string[];
|
|
189
|
+
createdAt: string;
|
|
190
|
+
lastUsedAt?: string;
|
|
191
|
+
expiresAt?: string;
|
|
192
|
+
}
|
|
193
|
+
export interface ClientOptions {
|
|
194
|
+
apiKey?: string;
|
|
195
|
+
baseUrl?: string;
|
|
196
|
+
timeout?: number;
|
|
197
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility exports
|
|
3
|
+
*/
|
|
4
|
+
export { withRetry, retryOnRateLimit, createRetryWrapper } from './retry';
|
|
5
|
+
export type { RetryConfig } from './retry';
|
|
6
|
+
export { verifyWebhookSignature, parseWebhookPayload, constructWebhookEvent, generateWebhookSignature, } from './webhooks';
|
|
7
|
+
export type { WebhookEvent, WebhookVerifyOptions } from './webhooks';
|
|
8
|
+
export { validateElement, validateCanvasDimensions, validateRenderOptions, validateColor, } from './validation';
|
|
9
|
+
export type { ElementBase, RectangleElement, CircleElement, TextElement, ImageElement, LineElement, CanvasElement, } from './validation';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility exports
|
|
3
|
+
*/
|
|
4
|
+
export { withRetry, retryOnRateLimit, createRetryWrapper } from './retry';
|
|
5
|
+
export { verifyWebhookSignature, parseWebhookPayload, constructWebhookEvent, generateWebhookSignature, } from './webhooks';
|
|
6
|
+
export { validateElement, validateCanvasDimensions, validateRenderOptions, validateColor, } from './validation';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retry logic with exponential backoff
|
|
3
|
+
*/
|
|
4
|
+
export interface RetryConfig {
|
|
5
|
+
maxAttempts: number;
|
|
6
|
+
backoffFactor: number;
|
|
7
|
+
initialDelay: number;
|
|
8
|
+
maxDelay: number;
|
|
9
|
+
retryOn: Array<new (...args: any[]) => Error>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Execute a function with retry logic and exponential backoff
|
|
13
|
+
*/
|
|
14
|
+
export declare function withRetry<T>(fn: () => Promise<T>, config?: Partial<RetryConfig>): Promise<T>;
|
|
15
|
+
/**
|
|
16
|
+
* Create a retry wrapper for rate limit errors
|
|
17
|
+
*/
|
|
18
|
+
export declare function retryOnRateLimit<T>(fn: () => Promise<T>): Promise<T>;
|
|
19
|
+
/**
|
|
20
|
+
* Decorator-style retry wrapper
|
|
21
|
+
*/
|
|
22
|
+
export declare function createRetryWrapper(config?: Partial<RetryConfig>): <T>(fn: () => Promise<T>) => Promise<T>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retry logic with exponential backoff
|
|
3
|
+
*/
|
|
4
|
+
import { RateLimitError, ServerError, CanveleteError } from '../errors';
|
|
5
|
+
const defaultConfig = {
|
|
6
|
+
maxAttempts: 3,
|
|
7
|
+
backoffFactor: 2.0,
|
|
8
|
+
initialDelay: 1000,
|
|
9
|
+
maxDelay: 60000,
|
|
10
|
+
retryOn: [RateLimitError, ServerError],
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Sleep for a specified duration
|
|
14
|
+
*/
|
|
15
|
+
function sleep(ms) {
|
|
16
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Execute a function with retry logic and exponential backoff
|
|
20
|
+
*/
|
|
21
|
+
export async function withRetry(fn, config = {}) {
|
|
22
|
+
const cfg = { ...defaultConfig, ...config };
|
|
23
|
+
let attempt = 0;
|
|
24
|
+
let delay = cfg.initialDelay;
|
|
25
|
+
while (attempt < cfg.maxAttempts) {
|
|
26
|
+
try {
|
|
27
|
+
return await fn();
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
attempt++;
|
|
31
|
+
// Check if we should retry this error
|
|
32
|
+
const shouldRetry = cfg.retryOn.some(ErrorClass => error instanceof ErrorClass);
|
|
33
|
+
if (!shouldRetry || attempt >= cfg.maxAttempts) {
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
// Check for rate limit with retry-after header
|
|
37
|
+
if (error instanceof RateLimitError && error.retryAfter) {
|
|
38
|
+
delay = error.retryAfter * 1000;
|
|
39
|
+
console.warn(`Rate limited. Retrying after ${delay}ms (attempt ${attempt}/${cfg.maxAttempts})`);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
console.warn(`Attempt ${attempt}/${cfg.maxAttempts} failed: ${error.message}. ` +
|
|
43
|
+
`Retrying in ${delay}ms...`);
|
|
44
|
+
}
|
|
45
|
+
await sleep(delay);
|
|
46
|
+
// Exponential backoff
|
|
47
|
+
delay = Math.min(delay * cfg.backoffFactor, cfg.maxDelay);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// This should never be reached, but TypeScript needs it
|
|
51
|
+
throw new CanveleteError('Max retry attempts reached');
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Create a retry wrapper for rate limit errors
|
|
55
|
+
*/
|
|
56
|
+
export function retryOnRateLimit(fn) {
|
|
57
|
+
return withRetry(fn, {
|
|
58
|
+
maxAttempts: 5,
|
|
59
|
+
backoffFactor: 2,
|
|
60
|
+
retryOn: [RateLimitError, ServerError],
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Decorator-style retry wrapper
|
|
65
|
+
*/
|
|
66
|
+
export function createRetryWrapper(config = {}) {
|
|
67
|
+
return function (fn) {
|
|
68
|
+
return withRetry(fn, config);
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Element validation utilities
|
|
3
|
+
*/
|
|
4
|
+
export interface ElementBase {
|
|
5
|
+
type: string;
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
width?: number;
|
|
9
|
+
height?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface RectangleElement extends ElementBase {
|
|
12
|
+
type: 'rectangle';
|
|
13
|
+
fill?: string;
|
|
14
|
+
stroke?: string;
|
|
15
|
+
strokeWidth?: number;
|
|
16
|
+
borderRadius?: number;
|
|
17
|
+
opacity?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface CircleElement extends ElementBase {
|
|
20
|
+
type: 'circle';
|
|
21
|
+
fill?: string;
|
|
22
|
+
stroke?: string;
|
|
23
|
+
strokeWidth?: number;
|
|
24
|
+
opacity?: number;
|
|
25
|
+
}
|
|
26
|
+
export interface TextElement extends ElementBase {
|
|
27
|
+
type: 'text';
|
|
28
|
+
text: string;
|
|
29
|
+
fontSize?: number;
|
|
30
|
+
fontFamily?: string;
|
|
31
|
+
fontWeight?: string;
|
|
32
|
+
fontStyle?: string;
|
|
33
|
+
fill?: string;
|
|
34
|
+
textAlign?: 'left' | 'center' | 'right' | 'justify';
|
|
35
|
+
lineHeight?: number;
|
|
36
|
+
}
|
|
37
|
+
export interface ImageElement extends ElementBase {
|
|
38
|
+
type: 'image';
|
|
39
|
+
src: string;
|
|
40
|
+
objectFit?: 'fill' | 'contain' | 'cover' | 'none' | 'scale-down';
|
|
41
|
+
opacity?: number;
|
|
42
|
+
}
|
|
43
|
+
export interface LineElement extends ElementBase {
|
|
44
|
+
type: 'line';
|
|
45
|
+
linePoints?: Array<{
|
|
46
|
+
x: number;
|
|
47
|
+
y: number;
|
|
48
|
+
}>;
|
|
49
|
+
stroke?: string;
|
|
50
|
+
strokeWidth?: number;
|
|
51
|
+
lineCap?: 'butt' | 'round' | 'square';
|
|
52
|
+
}
|
|
53
|
+
export type CanvasElement = RectangleElement | CircleElement | TextElement | ImageElement | LineElement | ElementBase;
|
|
54
|
+
/**
|
|
55
|
+
* Validate an element's basic properties
|
|
56
|
+
*/
|
|
57
|
+
export declare function validateElement(element: Record<string, any>): void;
|
|
58
|
+
/**
|
|
59
|
+
* Validate canvas dimensions
|
|
60
|
+
*/
|
|
61
|
+
export declare function validateCanvasDimensions(width: number, height: number): void;
|
|
62
|
+
/**
|
|
63
|
+
* Validate render options
|
|
64
|
+
*/
|
|
65
|
+
export declare function validateRenderOptions(options: Record<string, any>): void;
|
|
66
|
+
/**
|
|
67
|
+
* Validate color format
|
|
68
|
+
*/
|
|
69
|
+
export declare function validateColor(color: string): boolean;
|