@panoptic-it-solutions/zoho-projects-client 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/README.md +231 -0
- package/dist/auth/token-manager.d.ts +59 -0
- package/dist/auth/token-manager.d.ts.map +1 -0
- package/dist/auth/token-manager.js +120 -0
- package/dist/auth/token-manager.js.map +1 -0
- package/dist/client.d.ts +193 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +357 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +61 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +113 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/types/common.d.ts +173 -0
- package/dist/types/common.d.ts.map +1 -0
- package/dist/types/common.js +56 -0
- package/dist/types/common.js.map +1 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +11 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/projects.d.ts +3671 -0
- package/dist/types/projects.d.ts.map +1 -0
- package/dist/types/projects.js +94 -0
- package/dist/types/projects.js.map +1 -0
- package/dist/types/tasks.d.ts +2612 -0
- package/dist/types/tasks.d.ts.map +1 -0
- package/dist/types/tasks.js +127 -0
- package/dist/types/tasks.js.map +1 -0
- package/dist/types/timelogs.d.ts +13623 -0
- package/dist/types/timelogs.d.ts.map +1 -0
- package/dist/types/timelogs.js +115 -0
- package/dist/types/timelogs.js.map +1 -0
- package/dist/types/users.d.ts +695 -0
- package/dist/types/users.d.ts.map +1 -0
- package/dist/types/users.js +65 -0
- package/dist/types/users.js.map +1 -0
- package/dist/utils/pagination.d.ts +59 -0
- package/dist/utils/pagination.d.ts.map +1 -0
- package/dist/utils/pagination.js +84 -0
- package/dist/utils/pagination.js.map +1 -0
- package/dist/utils/rate-limiter.d.ts +33 -0
- package/dist/utils/rate-limiter.d.ts.map +1 -0
- package/dist/utils/rate-limiter.js +92 -0
- package/dist/utils/rate-limiter.js.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../src/types/users.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;GAEG;AACH,eAAO,MAAM,cAAc,aAAa,CAAC;AAEzC,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAEtD;;GAEG;AACH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAwCP,CAAC;AAEjB,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAE9C;;GAEG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAGjC,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE;;GAEG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAE7B,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D;;GAEG;AACH,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAGxC,CAAC;AAEH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ZohoPageInfoSchema } from "./common.js";
|
|
3
|
+
/**
|
|
4
|
+
* User role - Zoho has many role types including custom ones
|
|
5
|
+
*/
|
|
6
|
+
export const UserRoleSchema = z.string();
|
|
7
|
+
/**
|
|
8
|
+
* User from Zoho Projects API
|
|
9
|
+
*/
|
|
10
|
+
export const UserSchema = z.object({
|
|
11
|
+
// Identification
|
|
12
|
+
id: z.string(),
|
|
13
|
+
zpuid: z.string().optional(),
|
|
14
|
+
name: z.string(),
|
|
15
|
+
email: z.string(),
|
|
16
|
+
// Status
|
|
17
|
+
active: z.boolean().optional(),
|
|
18
|
+
// Role & Profile
|
|
19
|
+
role: UserRoleSchema.optional(),
|
|
20
|
+
role_name: z.string().optional(),
|
|
21
|
+
role_id: z.string().optional(),
|
|
22
|
+
profile_id: z.string().optional(),
|
|
23
|
+
profile_name: z.string().optional(),
|
|
24
|
+
profile_type: z.union([z.string(), z.number()]).optional(),
|
|
25
|
+
// Billing & Budget
|
|
26
|
+
rate: z.union([z.string(), z.number()]).nullable().optional(), // Hourly rate
|
|
27
|
+
cost_per_hour: z.union([z.string(), z.number()]).nullable().optional(),
|
|
28
|
+
user_budget: z.union([z.string(), z.number()]).nullable().optional(),
|
|
29
|
+
revenue_budget: z.union([z.string(), z.number()]).nullable().optional(),
|
|
30
|
+
budget_threshold: z.union([z.string(), z.number()]).nullable().optional(),
|
|
31
|
+
invoice: z.string().nullable().optional(),
|
|
32
|
+
currency_code: z.string().optional(),
|
|
33
|
+
// Client-specific fields
|
|
34
|
+
client_company_name: z.string().nullable().optional(),
|
|
35
|
+
client_company_id: z.string().nullable().optional(),
|
|
36
|
+
// Project associations
|
|
37
|
+
associated_projects: z.array(z.object({
|
|
38
|
+
id: z.string(),
|
|
39
|
+
name: z.string(),
|
|
40
|
+
})).optional(),
|
|
41
|
+
// Timestamps
|
|
42
|
+
added_time: z.string().optional(),
|
|
43
|
+
added_time_long: z.number().optional(),
|
|
44
|
+
}).passthrough(); // Allow extra fields we haven't documented
|
|
45
|
+
/**
|
|
46
|
+
* Response wrapper for listing portal users
|
|
47
|
+
*/
|
|
48
|
+
export const UserListResponseSchema = z.object({
|
|
49
|
+
users: z.array(UserSchema),
|
|
50
|
+
page_info: ZohoPageInfoSchema.optional(),
|
|
51
|
+
});
|
|
52
|
+
/**
|
|
53
|
+
* Response wrapper for getting a single user
|
|
54
|
+
*/
|
|
55
|
+
export const UserResponseSchema = z.object({
|
|
56
|
+
users: z.array(UserSchema),
|
|
57
|
+
});
|
|
58
|
+
/**
|
|
59
|
+
* Response wrapper for listing project users
|
|
60
|
+
*/
|
|
61
|
+
export const ProjectUserListResponseSchema = z.object({
|
|
62
|
+
users: z.array(UserSchema),
|
|
63
|
+
page_info: ZohoPageInfoSchema.optional(),
|
|
64
|
+
});
|
|
65
|
+
//# sourceMappingURL=users.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"users.js","sourceRoot":"","sources":["../../src/types/users.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { ZohoPageInfo } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Default page size for Zoho Projects API
|
|
4
|
+
* Maximum allowed is 200
|
|
5
|
+
*/
|
|
6
|
+
export declare const DEFAULT_PAGE_SIZE = 100;
|
|
7
|
+
/**
|
|
8
|
+
* Maximum page size allowed by Zoho
|
|
9
|
+
*/
|
|
10
|
+
export declare const MAX_PAGE_SIZE = 200;
|
|
11
|
+
/**
|
|
12
|
+
* Generic paginated response structure
|
|
13
|
+
*/
|
|
14
|
+
export interface PaginatedResponse<T> {
|
|
15
|
+
data: T[];
|
|
16
|
+
pageInfo?: ZohoPageInfo;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Options for auto-pagination
|
|
20
|
+
*/
|
|
21
|
+
export interface AutoPaginateOptions {
|
|
22
|
+
/** Maximum number of items to fetch (default: unlimited) */
|
|
23
|
+
maxItems?: number;
|
|
24
|
+
/** Number of items per page (default: 100, max: 200) */
|
|
25
|
+
pageSize?: number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Create an async generator that automatically handles pagination
|
|
29
|
+
*
|
|
30
|
+
* Usage:
|
|
31
|
+
* ```ts
|
|
32
|
+
* for await (const project of autoPaginate((index) =>
|
|
33
|
+
* client.projects.list({ index, range: 100 })
|
|
34
|
+
* )) {
|
|
35
|
+
* console.log(project);
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @param fetchPage Function that fetches a page given the index (0-based)
|
|
40
|
+
* @param options Pagination options
|
|
41
|
+
*/
|
|
42
|
+
export declare function autoPaginate<T>(fetchPage: (index: number, range: number) => Promise<PaginatedResponse<T>>, options?: AutoPaginateOptions): AsyncGenerator<T, void, unknown>;
|
|
43
|
+
/**
|
|
44
|
+
* Collect all items from an async generator into an array
|
|
45
|
+
*
|
|
46
|
+
* Usage:
|
|
47
|
+
* ```ts
|
|
48
|
+
* const allProjects = await collectAll(autoPaginate(...));
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export declare function collectAll<T>(generator: AsyncGenerator<T, void, unknown>): Promise<T[]>;
|
|
52
|
+
/**
|
|
53
|
+
* Helper to create pagination params for Zoho API
|
|
54
|
+
*/
|
|
55
|
+
export declare function createPaginationParams(index?: number, range?: number): {
|
|
56
|
+
index: number;
|
|
57
|
+
range: number;
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=pagination.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pagination.d.ts","sourceRoot":"","sources":["../../src/utils/pagination.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD;;;GAGG;AACH,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAErC;;GAEG;AACH,eAAO,MAAM,aAAa,MAAM,CAAC;AAEjC;;GAEG;AACH,MAAM,WAAW,iBAAiB,CAAC,CAAC;IAClC,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,QAAQ,CAAC,EAAE,YAAY,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAuB,YAAY,CAAC,CAAC,EACnC,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,EAC1E,OAAO,GAAE,mBAAwB,GAChC,cAAc,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,CAqClC;AAED;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAAC,CAAC,EAChC,SAAS,EAAE,cAAc,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,GAC1C,OAAO,CAAC,CAAC,EAAE,CAAC,CAMd;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,GAAE,MAAU,EACjB,KAAK,GAAE,MAA0B,GAChC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAKlC"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default page size for Zoho Projects API
|
|
3
|
+
* Maximum allowed is 200
|
|
4
|
+
*/
|
|
5
|
+
export const DEFAULT_PAGE_SIZE = 100;
|
|
6
|
+
/**
|
|
7
|
+
* Maximum page size allowed by Zoho
|
|
8
|
+
*/
|
|
9
|
+
export const MAX_PAGE_SIZE = 200;
|
|
10
|
+
/**
|
|
11
|
+
* Create an async generator that automatically handles pagination
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* ```ts
|
|
15
|
+
* for await (const project of autoPaginate((index) =>
|
|
16
|
+
* client.projects.list({ index, range: 100 })
|
|
17
|
+
* )) {
|
|
18
|
+
* console.log(project);
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @param fetchPage Function that fetches a page given the index (0-based)
|
|
23
|
+
* @param options Pagination options
|
|
24
|
+
*/
|
|
25
|
+
export async function* autoPaginate(fetchPage, options = {}) {
|
|
26
|
+
const pageSize = Math.min(options.pageSize ?? DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE);
|
|
27
|
+
const maxItems = options.maxItems ?? Infinity;
|
|
28
|
+
let index = 0;
|
|
29
|
+
let itemsYielded = 0;
|
|
30
|
+
let hasMore = true;
|
|
31
|
+
while (hasMore && itemsYielded < maxItems) {
|
|
32
|
+
const response = await fetchPage(index, pageSize);
|
|
33
|
+
const items = response.data;
|
|
34
|
+
if (items.length === 0) {
|
|
35
|
+
// No more items
|
|
36
|
+
hasMore = false;
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
for (const item of items) {
|
|
40
|
+
if (itemsYielded >= maxItems) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
yield item;
|
|
44
|
+
itemsYielded++;
|
|
45
|
+
}
|
|
46
|
+
// Determine if there are more pages
|
|
47
|
+
if (response.pageInfo?.has_more_page === false) {
|
|
48
|
+
hasMore = false;
|
|
49
|
+
}
|
|
50
|
+
else if (items.length < pageSize) {
|
|
51
|
+
// Received fewer items than requested, likely last page
|
|
52
|
+
hasMore = false;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
// Move to next page
|
|
56
|
+
index += pageSize;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Collect all items from an async generator into an array
|
|
62
|
+
*
|
|
63
|
+
* Usage:
|
|
64
|
+
* ```ts
|
|
65
|
+
* const allProjects = await collectAll(autoPaginate(...));
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export async function collectAll(generator) {
|
|
69
|
+
const items = [];
|
|
70
|
+
for await (const item of generator) {
|
|
71
|
+
items.push(item);
|
|
72
|
+
}
|
|
73
|
+
return items;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Helper to create pagination params for Zoho API
|
|
77
|
+
*/
|
|
78
|
+
export function createPaginationParams(index = 0, range = DEFAULT_PAGE_SIZE) {
|
|
79
|
+
return {
|
|
80
|
+
index,
|
|
81
|
+
range: Math.min(range, MAX_PAGE_SIZE),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=pagination.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pagination.js","sourceRoot":"","sources":["../../src/utils/pagination.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAErC;;GAEG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,GAAG,CAAC;AAoBjC;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,SAAS,CAAC,CAAC,YAAY,CACjC,SAA0E,EAC1E,UAA+B,EAAE;IAEjC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,IAAI,iBAAiB,EAAE,aAAa,CAAC,CAAC;IAChF,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,QAAQ,CAAC;IAE9C,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,OAAO,GAAG,IAAI,CAAC;IAEnB,OAAO,OAAO,IAAI,YAAY,GAAG,QAAQ,EAAE,CAAC;QAC1C,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;QAClD,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC;QAE5B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,gBAAgB;YAChB,OAAO,GAAG,KAAK,CAAC;YAChB,MAAM;QACR,CAAC;QAED,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,YAAY,IAAI,QAAQ,EAAE,CAAC;gBAC7B,OAAO;YACT,CAAC;YACD,MAAM,IAAI,CAAC;YACX,YAAY,EAAE,CAAC;QACjB,CAAC;QAED,oCAAoC;QACpC,IAAI,QAAQ,CAAC,QAAQ,EAAE,aAAa,KAAK,KAAK,EAAE,CAAC;YAC/C,OAAO,GAAG,KAAK,CAAC;QAClB,CAAC;aAAM,IAAI,KAAK,CAAC,MAAM,GAAG,QAAQ,EAAE,CAAC;YACnC,wDAAwD;YACxD,OAAO,GAAG,KAAK,CAAC;QAClB,CAAC;aAAM,CAAC;YACN,oBAAoB;YACpB,KAAK,IAAI,QAAQ,CAAC;QACpB,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,SAA2C;IAE3C,MAAM,KAAK,GAAQ,EAAE,CAAC;IACtB,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QACnC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,sBAAsB,CACpC,QAAgB,CAAC,EACjB,QAAgB,iBAAiB;IAEjC,OAAO;QACL,KAAK;QACL,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,aAAa,CAAC;KACtC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import Bottleneck from "bottleneck";
|
|
2
|
+
/**
|
|
3
|
+
* Configuration for rate limiting
|
|
4
|
+
*/
|
|
5
|
+
export interface RateLimiterConfig {
|
|
6
|
+
/**
|
|
7
|
+
* Redis configuration for distributed rate limiting
|
|
8
|
+
* If not provided, uses in-memory limiting (single instance only)
|
|
9
|
+
*/
|
|
10
|
+
redis?: {
|
|
11
|
+
url: string;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Create a rate limiter for Zoho Projects API
|
|
16
|
+
*
|
|
17
|
+
* Uses Bottleneck's reservoir pattern to enforce:
|
|
18
|
+
* - Max 90 requests per 2 minutes (with 10 request safety margin)
|
|
19
|
+
* - Min 1.3s between requests
|
|
20
|
+
* - Max 5 concurrent requests
|
|
21
|
+
*
|
|
22
|
+
* Handles 429 responses by pausing for 30-minute lockout
|
|
23
|
+
*/
|
|
24
|
+
export declare function createRateLimiter(config?: RateLimiterConfig): Bottleneck;
|
|
25
|
+
/**
|
|
26
|
+
* Get rate limiter stats for monitoring
|
|
27
|
+
*/
|
|
28
|
+
export declare function getRateLimiterStats(limiter: Bottleneck): {
|
|
29
|
+
running: number;
|
|
30
|
+
queued: number;
|
|
31
|
+
reservoir: number | null;
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=rate-limiter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../src/utils/rate-limiter.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,YAAY,CAAC;AAEpC;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,KAAK,CAAC,EAAE;QACN,GAAG,EAAE,MAAM,CAAC;KACb,CAAC;CACH;AAsBD;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,CAAC,EAAE,iBAAiB,GAAG,UAAU,CAkDxE;AAaD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,UAAU,GAAG;IACxD,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,CAOA"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import Bottleneck from "bottleneck";
|
|
2
|
+
/**
|
|
3
|
+
* Zoho Projects API rate limits:
|
|
4
|
+
* - 100 requests per 2 minutes (per user/org)
|
|
5
|
+
* - 429 response triggers 30-minute lockout
|
|
6
|
+
*/
|
|
7
|
+
const ZOHO_RATE_LIMIT = {
|
|
8
|
+
/** Maximum requests in the time window */
|
|
9
|
+
maxRequests: 100,
|
|
10
|
+
/** Time window in milliseconds (2 minutes) */
|
|
11
|
+
windowMs: 2 * 60 * 1000,
|
|
12
|
+
/** Safety margin - we use 90 requests to avoid hitting the exact limit */
|
|
13
|
+
safeMaxRequests: 90,
|
|
14
|
+
/** Minimum time between requests in milliseconds (~1.3s for safety) */
|
|
15
|
+
minTime: 1300,
|
|
16
|
+
/** Maximum concurrent requests */
|
|
17
|
+
maxConcurrent: 5,
|
|
18
|
+
/** Lockout duration when rate limited (30 minutes) */
|
|
19
|
+
lockoutMs: 30 * 60 * 1000,
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Create a rate limiter for Zoho Projects API
|
|
23
|
+
*
|
|
24
|
+
* Uses Bottleneck's reservoir pattern to enforce:
|
|
25
|
+
* - Max 90 requests per 2 minutes (with 10 request safety margin)
|
|
26
|
+
* - Min 1.3s between requests
|
|
27
|
+
* - Max 5 concurrent requests
|
|
28
|
+
*
|
|
29
|
+
* Handles 429 responses by pausing for 30-minute lockout
|
|
30
|
+
*/
|
|
31
|
+
export function createRateLimiter(config) {
|
|
32
|
+
const limiterOptions = {
|
|
33
|
+
// Reservoir pattern for request budget
|
|
34
|
+
reservoir: ZOHO_RATE_LIMIT.safeMaxRequests,
|
|
35
|
+
reservoirRefreshAmount: ZOHO_RATE_LIMIT.safeMaxRequests,
|
|
36
|
+
reservoirRefreshInterval: ZOHO_RATE_LIMIT.windowMs,
|
|
37
|
+
// Concurrency controls
|
|
38
|
+
maxConcurrent: ZOHO_RATE_LIMIT.maxConcurrent,
|
|
39
|
+
minTime: ZOHO_RATE_LIMIT.minTime,
|
|
40
|
+
};
|
|
41
|
+
// Add Redis clustering if configured
|
|
42
|
+
if (config?.redis) {
|
|
43
|
+
const redisConnection = new Bottleneck.IORedisConnection({
|
|
44
|
+
client: createRedisClient(config.redis.url),
|
|
45
|
+
});
|
|
46
|
+
return new Bottleneck({
|
|
47
|
+
...limiterOptions,
|
|
48
|
+
id: "zoho-projects-rate-limiter",
|
|
49
|
+
datastore: "ioredis",
|
|
50
|
+
clearDatastore: false,
|
|
51
|
+
connection: redisConnection,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
const limiter = new Bottleneck(limiterOptions);
|
|
55
|
+
// Handle rate limit errors with 30-minute backoff
|
|
56
|
+
limiter.on("failed", async (error, jobInfo) => {
|
|
57
|
+
const axiosError = error;
|
|
58
|
+
if (axiosError.response?.status === 429) {
|
|
59
|
+
console.warn(`[ZohoProjectsClient] Rate limit exceeded, pausing for ${ZOHO_RATE_LIMIT.lockoutMs / 1000 / 60} minutes`);
|
|
60
|
+
// Return retry delay in ms - Bottleneck will retry after this delay
|
|
61
|
+
return ZOHO_RATE_LIMIT.lockoutMs;
|
|
62
|
+
}
|
|
63
|
+
// Don't retry other errors
|
|
64
|
+
return undefined;
|
|
65
|
+
});
|
|
66
|
+
limiter.on("retry", (message, jobInfo) => {
|
|
67
|
+
console.info(`[ZohoProjectsClient] Retrying request after rate limit cooldown`);
|
|
68
|
+
});
|
|
69
|
+
return limiter;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Create Redis client for distributed rate limiting
|
|
73
|
+
* Lazy import to avoid requiring ioredis when not using Redis
|
|
74
|
+
*/
|
|
75
|
+
function createRedisClient(url) {
|
|
76
|
+
// Dynamic import to avoid bundling ioredis when not needed
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
78
|
+
const Redis = require("ioredis");
|
|
79
|
+
return new Redis(url);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Get rate limiter stats for monitoring
|
|
83
|
+
*/
|
|
84
|
+
export function getRateLimiterStats(limiter) {
|
|
85
|
+
const counts = limiter.counts();
|
|
86
|
+
return {
|
|
87
|
+
running: counts.RUNNING,
|
|
88
|
+
queued: counts.QUEUED,
|
|
89
|
+
reservoir: null, // Bottleneck doesn't expose current reservoir directly
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=rate-limiter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../src/utils/rate-limiter.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,YAAY,CAAC;AAepC;;;;GAIG;AACH,MAAM,eAAe,GAAG;IACtB,0CAA0C;IAC1C,WAAW,EAAE,GAAG;IAChB,8CAA8C;IAC9C,QAAQ,EAAE,CAAC,GAAG,EAAE,GAAG,IAAI;IACvB,0EAA0E;IAC1E,eAAe,EAAE,EAAE;IACnB,uEAAuE;IACvE,OAAO,EAAE,IAAI;IACb,kCAAkC;IAClC,aAAa,EAAE,CAAC;IAChB,sDAAsD;IACtD,SAAS,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI;CAC1B,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAA0B;IAC1D,MAAM,cAAc,GAAkC;QACpD,uCAAuC;QACvC,SAAS,EAAE,eAAe,CAAC,eAAe;QAC1C,sBAAsB,EAAE,eAAe,CAAC,eAAe;QACvD,wBAAwB,EAAE,eAAe,CAAC,QAAQ;QAElD,uBAAuB;QACvB,aAAa,EAAE,eAAe,CAAC,aAAa;QAC5C,OAAO,EAAE,eAAe,CAAC,OAAO;KACjC,CAAC;IAEF,qCAAqC;IACrC,IAAI,MAAM,EAAE,KAAK,EAAE,CAAC;QAClB,MAAM,eAAe,GAAG,IAAI,UAAU,CAAC,iBAAiB,CAAC;YACvD,MAAM,EAAE,iBAAiB,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC;SAC5C,CAAC,CAAC;QAEH,OAAO,IAAI,UAAU,CAAC;YACpB,GAAG,cAAc;YACjB,EAAE,EAAE,4BAA4B;YAChC,SAAS,EAAE,SAAS;YACpB,cAAc,EAAE,KAAK;YACrB,UAAU,EAAE,eAAe;SAC5B,CAAC,CAAC;IACL,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,UAAU,CAAC,cAAc,CAAC,CAAC;IAE/C,kDAAkD;IAClD,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;QAC5C,MAAM,UAAU,GAAG,KAA2C,CAAC;QAC/D,IAAI,UAAU,CAAC,QAAQ,EAAE,MAAM,KAAK,GAAG,EAAE,CAAC;YACxC,OAAO,CAAC,IAAI,CACV,yDAAyD,eAAe,CAAC,SAAS,GAAG,IAAI,GAAG,EAAE,UAAU,CACzG,CAAC;YACF,oEAAoE;YACpE,OAAO,eAAe,CAAC,SAAS,CAAC;QACnC,CAAC;QACD,2BAA2B;QAC3B,OAAO,SAAS,CAAC;IACnB,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE;QACvC,OAAO,CAAC,IAAI,CACV,iEAAiE,CAClE,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;GAGG;AACH,SAAS,iBAAiB,CAAC,GAAW;IACpC,2DAA2D;IAC3D,iEAAiE;IACjE,MAAM,KAAK,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IACjC,OAAO,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC;AACxB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAmB;IAKrD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAChC,OAAO;QACL,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,SAAS,EAAE,IAAI,EAAE,uDAAuD;KACzE,CAAC;AACJ,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@panoptic-it-solutions/zoho-projects-client",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TypeScript client for Zoho Projects V3 API with OAuth 2.0 and rate limiting",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc",
|
|
19
|
+
"clean": "rm -rf dist",
|
|
20
|
+
"prepublishOnly": "npm run clean && npm run build"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"zoho",
|
|
24
|
+
"zoho-projects",
|
|
25
|
+
"api-client",
|
|
26
|
+
"typescript"
|
|
27
|
+
],
|
|
28
|
+
"author": "Panoptic IT Solutions",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/panoptic-it-solutions/zoho-projects-client.git"
|
|
33
|
+
},
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/panoptic-it-solutions/zoho-projects-client/issues"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/panoptic-it-solutions/zoho-projects-client#readme",
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"axios": "^1.7.0",
|
|
43
|
+
"bottleneck": "^2.19.5",
|
|
44
|
+
"zod": "^3.23.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/node": "^22.0.0",
|
|
48
|
+
"typescript": "^5.6.0"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=18.0.0"
|
|
52
|
+
}
|
|
53
|
+
}
|