@hasna/connectors 1.3.22 → 1.3.23

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.
Files changed (63) hide show
  1. package/bin/index.js +109 -59
  2. package/bin/mcp.js +236 -182
  3. package/bin/serve.js +77 -33
  4. package/connectors/connect-dropbox/src/api/bulk.ts +285 -0
  5. package/connectors/connect-dropbox/src/api/index.ts +4 -0
  6. package/connectors/connect-dropbox/src/cli/index.ts +151 -0
  7. package/connectors/connect-github/src/api/bulk.ts +328 -0
  8. package/connectors/connect-github/src/api/index.ts +4 -0
  9. package/connectors/connect-github/src/cli/index.ts +145 -0
  10. package/connectors/connect-gmail/src/api/history.ts +82 -0
  11. package/connectors/connect-gmail/src/api/index.ts +12 -0
  12. package/connectors/connect-gmail/src/api/settings.ts +280 -0
  13. package/connectors/connect-gmail/src/api/watch.ts +40 -0
  14. package/connectors/connect-gmail/src/cli/index.ts +237 -0
  15. package/connectors/connect-googleads/src/api/bulk.ts +354 -0
  16. package/connectors/connect-googleads/src/api/index.ts +4 -0
  17. package/connectors/connect-googleads/src/cli/index.ts +192 -0
  18. package/connectors/connect-googlecalendar/src/api/acl.ts +66 -0
  19. package/connectors/connect-googlecalendar/src/api/bulk.ts +452 -0
  20. package/connectors/connect-googlecalendar/src/api/freebusy.ts +35 -0
  21. package/connectors/connect-googlecalendar/src/api/index.ts +9 -0
  22. package/connectors/connect-googlecalendar/src/cli/index.ts +522 -0
  23. package/connectors/connect-googlecalendar/src/types/index.ts +90 -0
  24. package/connectors/connect-googlecontacts/src/api/bulk.ts +365 -0
  25. package/connectors/connect-googlecontacts/src/api/groups.ts +164 -0
  26. package/connectors/connect-googlecontacts/src/api/index.ts +8 -0
  27. package/connectors/connect-googlecontacts/src/cli/index.ts +340 -0
  28. package/connectors/connect-googledocs/src/api/bulk.ts +301 -0
  29. package/connectors/connect-googledocs/src/api/index.ts +4 -0
  30. package/connectors/connect-googledocs/src/cli/index.ts +187 -0
  31. package/connectors/connect-googledrive/src/api/bulk.ts +505 -0
  32. package/connectors/connect-googledrive/src/api/changes.ts +139 -0
  33. package/connectors/connect-googledrive/src/api/client.ts +34 -0
  34. package/connectors/connect-googledrive/src/api/index.ts +12 -0
  35. package/connectors/connect-googledrive/src/api/revisions.ts +132 -0
  36. package/connectors/connect-googledrive/src/cli/index.ts +624 -0
  37. package/connectors/connect-googledrive/src/index.ts +2 -0
  38. package/connectors/connect-googlephotos/src/api/bulk.ts +455 -0
  39. package/connectors/connect-googlephotos/src/api/index.ts +8 -0
  40. package/connectors/connect-googlephotos/src/cli/index.ts +185 -0
  41. package/connectors/connect-googletasks/src/api/bulk.ts +359 -0
  42. package/connectors/connect-googletasks/src/api/index.ts +1 -0
  43. package/connectors/connect-googletasks/src/cli/index.ts +178 -0
  44. package/connectors/connect-linear/src/api/bulk.ts +219 -0
  45. package/connectors/connect-linear/src/api/index.ts +4 -0
  46. package/connectors/connect-linear/src/cli/index.ts +116 -0
  47. package/connectors/connect-shopify/src/api/bulk.ts +288 -0
  48. package/connectors/connect-shopify/src/api/index.ts +4 -0
  49. package/connectors/connect-shopify/src/cli/index.ts +108 -0
  50. package/connectors/connect-slack/src/api/bulk.ts +237 -0
  51. package/connectors/connect-slack/src/api/index.ts +4 -0
  52. package/connectors/connect-slack/src/cli/index.ts +157 -0
  53. package/connectors/connect-stripe/src/api/bulk.ts +488 -0
  54. package/connectors/connect-stripe/src/api/index.ts +4 -0
  55. package/connectors/connect-stripe/src/cli/index.ts +206 -0
  56. package/dist/index.js +75 -33
  57. package/dist/lib/runner.d.ts +15 -5
  58. package/package.json +2 -2
  59. package/connectors/connect-cloudflare/bun.lock +0 -32
  60. package/connectors/connect-gmail/bin/index.js +0 -7027
  61. package/connectors/connect-gmail/dist/cli/index.js +0 -7035
  62. package/connectors/connect-gmail/dist/index.js +0 -2174
  63. package/dist/server/server.test.d.ts +0 -1
@@ -0,0 +1,219 @@
1
+ import type { LinearClient } from './client';
2
+
3
+ // ============================================
4
+ // Bulk Operation Types
5
+ // ============================================
6
+
7
+ export interface BulkOperationOptions {
8
+ /** Maximum concurrent API calls (default: 10) */
9
+ concurrency?: number;
10
+ /** Dry run - don't actually modify */
11
+ dryRun?: boolean;
12
+ /** Progress callback */
13
+ onProgress?: (current: number, total: number, item: unknown) => void;
14
+ /** Error callback */
15
+ onError?: (error: Error, item: unknown) => void;
16
+ }
17
+
18
+ // --- Issue Bulk Operations ---
19
+
20
+ export interface BulkIssueOptions extends BulkOperationOptions {
21
+ issueIds: string[];
22
+ /** Action to perform */
23
+ action: 'archive' | 'change_state' | 'assign' | 'set_priority' | 'add_to_project';
24
+ /** State ID (for change_state) */
25
+ stateId?: string;
26
+ /** User ID (for assign) */
27
+ assigneeId?: string;
28
+ /** Priority 0-4 (for set_priority) */
29
+ priority?: number;
30
+ /** Project ID (for add_to_project) */
31
+ projectId?: string;
32
+ }
33
+
34
+ export interface BulkIssueResult {
35
+ total: number;
36
+ success: number;
37
+ failed: number;
38
+ errors: Array<{ issueId: string; error: string }>;
39
+ results: Array<{ issueId: string; response: unknown }>;
40
+ }
41
+
42
+ // --- Label Bulk Operations ---
43
+
44
+ export interface BulkLabelOptions extends BulkOperationOptions {
45
+ /** Issues to apply/remove label from */
46
+ issueIds: string[];
47
+ /** Label ID to add or remove */
48
+ labelId: string;
49
+ /** Action: add or remove label */
50
+ action: 'add' | 'remove';
51
+ }
52
+
53
+ export interface BulkLabelResult {
54
+ total: number;
55
+ success: number;
56
+ failed: number;
57
+ errors: Array<{ issueId: string; error: string }>;
58
+ results: Array<{ issueId: string; response: unknown }>;
59
+ }
60
+
61
+ // ============================================
62
+ // Bulk Operations API
63
+ // ============================================
64
+
65
+ export class BulkApi {
66
+ private readonly client: LinearClient;
67
+
68
+ constructor(client: LinearClient) {
69
+ this.client = client;
70
+ }
71
+
72
+ // ============================================
73
+ // Bulk Issue Operations
74
+ // ============================================
75
+
76
+ async issues(options: BulkIssueOptions): Promise<BulkIssueResult> {
77
+ const { issueIds, action, concurrency = 10, dryRun = false, onProgress, onError, stateId, assigneeId, priority, projectId } = options;
78
+
79
+ const result: BulkIssueResult = {
80
+ total: issueIds.length,
81
+ success: 0,
82
+ failed: 0,
83
+ errors: [],
84
+ results: [],
85
+ };
86
+
87
+ if (issueIds.length === 0) return result;
88
+
89
+ const chunks = this.chunkArray(issueIds, concurrency);
90
+
91
+ for (const chunk of chunks) {
92
+ await Promise.all(
93
+ chunk.map(async (issueId) => {
94
+ try {
95
+ if (dryRun) {
96
+ result.success++;
97
+ } else {
98
+ let response: unknown;
99
+ if (action === 'archive') {
100
+ response = await this.archiveIssue(issueId);
101
+ } else if (action === 'change_state') {
102
+ if (!stateId) throw new Error('stateId is required for change_state');
103
+ response = await this.updateIssue(issueId, { stateId });
104
+ } else if (action === 'assign') {
105
+ if (!assigneeId) throw new Error('assigneeId is required for assign');
106
+ response = await this.updateIssue(issueId, { assigneeId });
107
+ } else if (action === 'set_priority') {
108
+ if (priority === undefined) throw new Error('priority is required for set_priority');
109
+ response = await this.updateIssue(issueId, { priority });
110
+ } else if (action === 'add_to_project') {
111
+ if (!projectId) throw new Error('projectId is required for add_to_project');
112
+ response = await this.updateIssue(issueId, { projectId });
113
+ }
114
+ result.success++;
115
+ result.results.push({ issueId, response });
116
+ }
117
+
118
+ if (onProgress) onProgress(result.success + result.failed, result.total, issueId);
119
+ } catch (err) {
120
+ result.failed++;
121
+ const errorMessage = err instanceof Error ? err.message : String(err);
122
+ result.errors.push({ issueId, error: errorMessage });
123
+ if (onError) onError(err instanceof Error ? err : new Error(errorMessage), issueId);
124
+ }
125
+ })
126
+ );
127
+ }
128
+
129
+ return result;
130
+ }
131
+
132
+ // ============================================
133
+ // Bulk Label Operations
134
+ // ============================================
135
+
136
+ async labels(options: BulkLabelOptions): Promise<BulkLabelResult> {
137
+ const { issueIds, labelId, action, concurrency = 10, dryRun = false, onProgress, onError } = options;
138
+
139
+ const result: BulkLabelResult = {
140
+ total: issueIds.length,
141
+ success: 0,
142
+ failed: 0,
143
+ errors: [],
144
+ results: [],
145
+ };
146
+
147
+ if (issueIds.length === 0) return result;
148
+
149
+ const chunks = this.chunkArray(issueIds, concurrency);
150
+
151
+ for (const chunk of chunks) {
152
+ await Promise.all(
153
+ chunk.map(async (issueId) => {
154
+ try {
155
+ if (dryRun) {
156
+ result.success++;
157
+ } else {
158
+ // Linear doesn't have a direct add/remove label mutation,
159
+ // so we update the issue's labelIds
160
+ const response = await this.updateIssue(issueId, {
161
+ labelIds: action === 'add' ? [labelId] : undefined,
162
+ });
163
+ result.success++;
164
+ result.results.push({ issueId, response });
165
+ }
166
+
167
+ if (onProgress) onProgress(result.success + result.failed, result.total, issueId);
168
+ } catch (err) {
169
+ result.failed++;
170
+ const errorMessage = err instanceof Error ? err.message : String(err);
171
+ result.errors.push({ issueId, error: errorMessage });
172
+ if (onError) onError(err instanceof Error ? err : new Error(errorMessage), issueId);
173
+ }
174
+ })
175
+ );
176
+ }
177
+
178
+ return result;
179
+ }
180
+
181
+ // ============================================
182
+ // Private Helpers
183
+ // ============================================
184
+
185
+ private async updateIssue(id: string, input: Record<string, unknown>): Promise<unknown> {
186
+ const mutation = `
187
+ mutation IssueUpdate($id: String!, $input: IssueUpdateInput!) {
188
+ issueUpdate(id: $id, input: $input) {
189
+ success
190
+ issue {
191
+ id
192
+ identifier
193
+ title
194
+ }
195
+ }
196
+ }
197
+ `;
198
+ return this.client.mutate(mutation, { id, input });
199
+ }
200
+
201
+ private async archiveIssue(id: string): Promise<unknown> {
202
+ const mutation = `
203
+ mutation IssueArchive($id: String!) {
204
+ issueArchive(id: $id) {
205
+ success
206
+ }
207
+ }
208
+ `;
209
+ return this.client.mutate(mutation, { id });
210
+ }
211
+
212
+ private chunkArray<T>(array: T[], size: number): T[][] {
213
+ const chunks: T[][] = [];
214
+ for (let i = 0; i < array.length; i += size) {
215
+ chunks.push(array.slice(i, i + size));
216
+ }
217
+ return chunks;
218
+ }
219
+ }
@@ -6,6 +6,7 @@ import { TeamsApi } from './teams';
6
6
  import { UsersApi } from './users';
7
7
  import { CommentsApi } from './comments';
8
8
  import { StatesApi } from './states';
9
+ import { BulkApi } from './bulk';
9
10
 
10
11
  export { LinearClient } from './client';
11
12
  export { IssuesApi } from './issues';
@@ -14,6 +15,7 @@ export { TeamsApi } from './teams';
14
15
  export { UsersApi } from './users';
15
16
  export { CommentsApi } from './comments';
16
17
  export { StatesApi } from './states';
18
+ export { BulkApi } from './bulk';
17
19
 
18
20
  /**
19
21
  * Main Linear API class
@@ -27,6 +29,7 @@ export class Linear {
27
29
  public readonly users: UsersApi;
28
30
  public readonly comments: CommentsApi;
29
31
  public readonly states: StatesApi;
32
+ public readonly bulk: BulkApi;
30
33
 
31
34
  constructor(config: LinearConfig) {
32
35
  this.client = new LinearClient(config);
@@ -36,6 +39,7 @@ export class Linear {
36
39
  this.users = new UsersApi(this.client);
37
40
  this.comments = new CommentsApi(this.client);
38
41
  this.states = new StatesApi(this.client);
42
+ this.bulk = new BulkApi(this.client);
39
43
  }
40
44
 
41
45
  /**
@@ -557,4 +557,120 @@ usersCmd
557
557
  }
558
558
  });
559
559
 
560
+ // ============================================
561
+ // Bulk Operations
562
+ // ============================================
563
+
564
+ const bulkCmd = program
565
+ .command('bulk')
566
+ .description('Bulk operations');
567
+
568
+ bulkCmd
569
+ .command('issues')
570
+ .description('Bulk issue operations')
571
+ .option('--ids <ids>', 'Comma-separated issue IDs')
572
+ .option('--action <action>', 'Action: archive/change_state/assign/set_priority/add_to_project')
573
+ .option('--state-id <id>', 'Target state ID (for change_state)')
574
+ .option('--assignee-id <id>', 'Target user ID (for assign)')
575
+ .option('--priority <n>', 'Priority 0-4 (for set_priority)')
576
+ .option('--project-id <id>', 'Target project ID (for add_to_project)')
577
+ .option('--concurrency <n>', 'Max concurrent requests', '10')
578
+ .option('--dry-run', 'Preview without executing')
579
+ .action(async (opts) => {
580
+ try {
581
+ const client = getClient();
582
+ const format = program.opts().format as OutputFormat;
583
+
584
+ if (!opts.ids || !opts.action) {
585
+ error('--ids and --action are required');
586
+ process.exit(1);
587
+ }
588
+
589
+ const issueIds = opts.ids.split(',').map((id: string) => id.trim()).filter(Boolean);
590
+
591
+ info(`Bulk ${opts.action} on ${issueIds.length} issues (concurrency: ${opts.concurrency}, dry-run: ${!!opts.dryRun})`);
592
+
593
+ const result = await client.bulk.issues({
594
+ issueIds,
595
+ action: opts.action,
596
+ stateId: opts.stateId,
597
+ assigneeId: opts.assigneeId,
598
+ priority: opts.priority ? parseInt(opts.priority, 10) : undefined,
599
+ projectId: opts.projectId,
600
+ concurrency: parseInt(opts.concurrency, 10),
601
+ dryRun: opts.dryRun,
602
+ onProgress: (current, total) => {
603
+ process.stdout.write(`\rProgress: ${current}/${total}`);
604
+ },
605
+ });
606
+
607
+ console.log();
608
+ if (format === 'json') {
609
+ print(result, format);
610
+ } else {
611
+ info(`Done: ${result.success} succeeded, ${result.failed} failed out of ${result.total}`);
612
+ if (result.errors.length > 0) {
613
+ error('Failures:');
614
+ result.errors.forEach((e: { issueId: string; error: string }) => {
615
+ console.error(` - ${e.issueId}: ${e.error}`);
616
+ });
617
+ }
618
+ }
619
+ } catch (e) {
620
+ error((e as Error).message);
621
+ process.exit(1);
622
+ }
623
+ });
624
+
625
+ bulkCmd
626
+ .command('labels')
627
+ .description('Bulk add/remove labels from issues')
628
+ .option('--issue-ids <ids>', 'Comma-separated issue IDs')
629
+ .option('--label-id <id>', 'Label ID to add or remove')
630
+ .option('--action <action>', 'Action: add or remove')
631
+ .option('--concurrency <n>', 'Max concurrent requests', '10')
632
+ .option('--dry-run', 'Preview without executing')
633
+ .action(async (opts) => {
634
+ try {
635
+ const client = getClient();
636
+ const format = program.opts().format as OutputFormat;
637
+
638
+ if (!opts.issueIds || !opts.labelId || !opts.action) {
639
+ error('--issue-ids, --label-id, and --action are required');
640
+ process.exit(1);
641
+ }
642
+
643
+ const issueIds = opts.issueIds.split(',').map((id: string) => id.trim()).filter(Boolean);
644
+
645
+ info(`Bulk ${opts.action} label ${opts.labelId} to ${issueIds.length} issues (concurrency: ${opts.concurrency}, dry-run: ${!!opts.dryRun})`);
646
+
647
+ const result = await client.bulk.labels({
648
+ issueIds,
649
+ labelId: opts.labelId,
650
+ action: opts.action as 'add' | 'remove',
651
+ concurrency: parseInt(opts.concurrency, 10),
652
+ dryRun: opts.dryRun,
653
+ onProgress: (current, total) => {
654
+ process.stdout.write(`\rProgress: ${current}/${total}`);
655
+ },
656
+ });
657
+
658
+ console.log();
659
+ if (format === 'json') {
660
+ print(result, format);
661
+ } else {
662
+ info(`Done: ${result.success} succeeded, ${result.failed} failed out of ${result.total}`);
663
+ if (result.errors.length > 0) {
664
+ error('Failures:');
665
+ result.errors.forEach((e: { issueId: string; error: string }) => {
666
+ console.error(` - ${e.issueId}: ${e.error}`);
667
+ });
668
+ }
669
+ }
670
+ } catch (e) {
671
+ error((e as Error).message);
672
+ process.exit(1);
673
+ }
674
+ });
675
+
560
676
  program.parse();
@@ -0,0 +1,288 @@
1
+ import type { ShopifyClient } from './client';
2
+
3
+ // ============================================
4
+ // Bulk Operation Types
5
+ // ============================================
6
+
7
+ export interface BulkOperationOptions {
8
+ /** Maximum concurrent API calls (default: 10) */
9
+ concurrency?: number;
10
+ /** Dry run - don't actually modify */
11
+ dryRun?: boolean;
12
+ /** Progress callback */
13
+ onProgress?: (current: number, total: number, item: unknown) => void;
14
+ /** Error callback */
15
+ onError?: (error: Error, item: unknown) => void;
16
+ }
17
+
18
+ // --- Product Bulk Operations ---
19
+
20
+ export interface BulkProductOptions extends BulkOperationOptions {
21
+ productIds: number[];
22
+ /** Action to perform */
23
+ action: 'delete' | 'activate' | 'archive';
24
+ }
25
+
26
+ export interface BulkProductResult {
27
+ total: number;
28
+ success: number;
29
+ failed: number;
30
+ errors: Array<{ productId: number; error: string }>;
31
+ results: Array<{ productId: number; response: unknown }>;
32
+ }
33
+
34
+ // --- Order Bulk Operations ---
35
+
36
+ export interface BulkOrderOptions extends BulkOperationOptions {
37
+ orderIds: number[];
38
+ /** Action to perform */
39
+ action: 'close' | 'open' | 'cancel';
40
+ /** Cancel reason (for cancel action only) */
41
+ cancelReason?: 'customer' | 'inventory' | 'fraud' | 'declined' | 'other';
42
+ /** Send cancel email notification */
43
+ cancelEmail?: boolean;
44
+ /** Restock items on cancel */
45
+ cancelRestock?: boolean;
46
+ }
47
+
48
+ export interface BulkOrderResult {
49
+ total: number;
50
+ success: number;
51
+ failed: number;
52
+ errors: Array<{ orderId: number; error: string }>;
53
+ results: Array<{ orderId: number; response: unknown }>;
54
+ }
55
+
56
+ // --- Inventory Bulk Operations ---
57
+
58
+ export interface BulkInventoryOptions extends BulkOperationOptions {
59
+ /** Items with their target levels per location */
60
+ items: Array<{ inventoryItemId: number; locationId: number; available: number }>;
61
+ /** Action: set to exact value, or adjust by amount */
62
+ action: 'set' | 'adjust';
63
+ }
64
+
65
+ export interface BulkInventoryResult {
66
+ total: number;
67
+ success: number;
68
+ failed: number;
69
+ errors: Array<{ inventoryItemId: number; locationId: number; error: string }>;
70
+ results: Array<{ inventoryItemId: number; locationId: number; response: unknown }>;
71
+ }
72
+
73
+ // ============================================
74
+ // Bulk Operations API
75
+ // ============================================
76
+
77
+ export class BulkApi {
78
+ private readonly client: ShopifyClient;
79
+
80
+ constructor(client: ShopifyClient) {
81
+ this.client = client;
82
+ }
83
+
84
+ // ============================================
85
+ // Bulk Product Operations
86
+ // ============================================
87
+
88
+ async products(options: BulkProductOptions): Promise<BulkProductResult> {
89
+ const { productIds, action, concurrency = 10, dryRun = false, onProgress, onError } = options;
90
+
91
+ const result: BulkProductResult = {
92
+ total: productIds.length,
93
+ success: 0,
94
+ failed: 0,
95
+ errors: [],
96
+ results: [],
97
+ };
98
+
99
+ if (productIds.length === 0) return result;
100
+
101
+ const chunks = this.chunkArray(productIds, concurrency);
102
+
103
+ for (const chunk of chunks) {
104
+ await Promise.all(
105
+ chunk.map(async (productId) => {
106
+ try {
107
+ if (dryRun) {
108
+ result.success++;
109
+ } else {
110
+ let response: unknown;
111
+ if (action === 'delete') {
112
+ await this.client.request<void>(`/products/${productId}.json`, { method: 'DELETE' });
113
+ response = { deleted: productId };
114
+ } else {
115
+ response = await this.client.request<{ product: Record<string, unknown> }>(
116
+ `/products/${productId}.json`,
117
+ {
118
+ method: 'PUT',
119
+ body: { product: { id: productId, status: action === 'activate' ? 'active' : 'archived' } },
120
+ }
121
+ );
122
+ }
123
+ result.success++;
124
+ result.results.push({ productId, response });
125
+ }
126
+
127
+ if (onProgress) onProgress(result.success + result.failed, result.total, productId);
128
+ } catch (err) {
129
+ result.failed++;
130
+ const errorMessage = err instanceof Error ? err.message : String(err);
131
+ result.errors.push({ productId, error: errorMessage });
132
+ if (onError) onError(err instanceof Error ? err : new Error(errorMessage), productId);
133
+ }
134
+ })
135
+ );
136
+ }
137
+
138
+ return result;
139
+ }
140
+
141
+ // ============================================
142
+ // Bulk Order Operations
143
+ // ============================================
144
+
145
+ async orders(options: BulkOrderOptions): Promise<BulkOrderResult> {
146
+ const { orderIds, action, cancelReason, cancelEmail, cancelRestock, concurrency = 10, dryRun = false, onProgress, onError } = options;
147
+
148
+ const result: BulkOrderResult = {
149
+ total: orderIds.length,
150
+ success: 0,
151
+ failed: 0,
152
+ errors: [],
153
+ results: [],
154
+ };
155
+
156
+ if (orderIds.length === 0) return result;
157
+
158
+ const chunks = this.chunkArray(orderIds, concurrency);
159
+
160
+ for (const chunk of chunks) {
161
+ await Promise.all(
162
+ chunk.map(async (orderId) => {
163
+ try {
164
+ if (dryRun) {
165
+ result.success++;
166
+ } else {
167
+ let response: unknown;
168
+ if (action === 'close') {
169
+ response = await this.client.request<{ order: Record<string, unknown> }>(
170
+ `/orders/${orderId}/close.json`,
171
+ { method: 'POST' }
172
+ );
173
+ } else if (action === 'open') {
174
+ response = await this.client.request<{ order: Record<string, unknown> }>(
175
+ `/orders/${orderId}/open.json`,
176
+ { method: 'POST' }
177
+ );
178
+ } else {
179
+ const body: Record<string, unknown> = {};
180
+ if (cancelReason) body.reason = cancelReason;
181
+ if (cancelEmail !== undefined) body.email = cancelEmail;
182
+ if (cancelRestock !== undefined) body.restock = cancelRestock;
183
+ response = await this.client.request<{ order: Record<string, unknown> }>(
184
+ `/orders/${orderId}/cancel.json`,
185
+ { method: 'POST', body: Object.keys(body).length > 0 ? body : undefined }
186
+ );
187
+ }
188
+ result.success++;
189
+ result.results.push({ orderId, response });
190
+ }
191
+
192
+ if (onProgress) onProgress(result.success + result.failed, result.total, orderId);
193
+ } catch (err) {
194
+ result.failed++;
195
+ const errorMessage = err instanceof Error ? err.message : String(err);
196
+ result.errors.push({ orderId, error: errorMessage });
197
+ if (onError) onError(err instanceof Error ? err : new Error(errorMessage), orderId);
198
+ }
199
+ })
200
+ );
201
+ }
202
+
203
+ return result;
204
+ }
205
+
206
+ // ============================================
207
+ // Bulk Inventory Operations
208
+ // ============================================
209
+
210
+ async inventory(options: BulkInventoryOptions): Promise<BulkInventoryResult> {
211
+ const { items, action, concurrency = 10, dryRun = false, onProgress, onError } = options;
212
+
213
+ const result: BulkInventoryResult = {
214
+ total: items.length,
215
+ success: 0,
216
+ failed: 0,
217
+ errors: [],
218
+ results: [],
219
+ };
220
+
221
+ if (items.length === 0) return result;
222
+
223
+ const chunks = this.chunkArray(items, concurrency);
224
+
225
+ for (const chunk of chunks) {
226
+ await Promise.all(
227
+ chunk.map(async (item) => {
228
+ const { inventoryItemId, locationId, available } = item;
229
+ try {
230
+ if (dryRun) {
231
+ result.success++;
232
+ } else {
233
+ let response: unknown;
234
+ if (action === 'set') {
235
+ response = await this.client.request<{ inventory_level: Record<string, unknown> }>(
236
+ '/inventory_levels/set.json',
237
+ {
238
+ method: 'POST',
239
+ body: {
240
+ location_id: locationId,
241
+ inventory_item_id: inventoryItemId,
242
+ available,
243
+ },
244
+ }
245
+ );
246
+ } else {
247
+ response = await this.client.request<{ inventory_level: Record<string, unknown> }>(
248
+ '/inventory_levels/adjust.json',
249
+ {
250
+ method: 'POST',
251
+ body: {
252
+ location_id: locationId,
253
+ inventory_item_id: inventoryItemId,
254
+ available_adjustment: available,
255
+ },
256
+ }
257
+ );
258
+ }
259
+ result.success++;
260
+ result.results.push({ inventoryItemId, locationId, response });
261
+ }
262
+
263
+ if (onProgress) onProgress(result.success + result.failed, result.total, item);
264
+ } catch (err) {
265
+ result.failed++;
266
+ const errorMessage = err instanceof Error ? err.message : String(err);
267
+ result.errors.push({ inventoryItemId, locationId, error: errorMessage });
268
+ if (onError) onError(err instanceof Error ? err : new Error(errorMessage), item);
269
+ }
270
+ })
271
+ );
272
+ }
273
+
274
+ return result;
275
+ }
276
+
277
+ // ============================================
278
+ // Helper Methods
279
+ // ============================================
280
+
281
+ private chunkArray<T>(array: T[], size: number): T[][] {
282
+ const chunks: T[][] = [];
283
+ for (let i = 0; i < array.length; i += size) {
284
+ chunks.push(array.slice(i, i + size));
285
+ }
286
+ return chunks;
287
+ }
288
+ }
@@ -6,6 +6,7 @@ import { CustomersApi } from './customers';
6
6
  import { InventoryApi } from './inventory';
7
7
  import { CollectionsApi } from './collections';
8
8
  import { ShopApi } from './shop';
9
+ import { BulkApi } from './bulk';
9
10
 
10
11
  /**
11
12
  * Main Shopify Connector class
@@ -21,6 +22,7 @@ export class Shopify {
21
22
  public readonly inventory: InventoryApi;
22
23
  public readonly collections: CollectionsApi;
23
24
  public readonly shop: ShopApi;
25
+ public readonly bulk: BulkApi;
24
26
 
25
27
  constructor(config: ShopifyConfig) {
26
28
  this.client = new ShopifyClient(config);
@@ -30,6 +32,7 @@ export class Shopify {
30
32
  this.inventory = new InventoryApi(this.client);
31
33
  this.collections = new CollectionsApi(this.client);
32
34
  this.shop = new ShopApi(this.client);
35
+ this.bulk = new BulkApi(this.client);
33
36
  }
34
37
 
35
38
  /**
@@ -87,3 +90,4 @@ export { CustomersApi } from './customers';
87
90
  export { InventoryApi } from './inventory';
88
91
  export { CollectionsApi } from './collections';
89
92
  export { ShopApi } from './shop';
93
+ export { BulkApi } from './bulk';