ts-forge 1.0.2 → 2.0.0-beta.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/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## v2.0.0-beta.0
2
+
3
+ - Resolved warnings from TypeScript 6.0
4
+
5
+ ## v1.0.3
6
+
7
+ - README.md: updated documentation
8
+ - Keywords were added to package.json
9
+ - Fixed vulnerabilities in package.json
10
+
1
11
  ## v1.0.2
2
12
 
3
13
  - Files in the .github and scripts folders were added to .npmignore to avoid publishing them to npm registry
package/README.md CHANGED
@@ -1,20 +1,24 @@
1
- **Warning:** The first version of this library is ready, however documentation is still in progress
1
+ # ts-forge
2
+
3
+ [npm version](https://badge.fury.io/js/ts-forge)
4
+ [npm downloads](https://www.npmjs.com/package/ts-forge)
2
5
 
3
6
  This library allows you to define resolvers for your Atlassian Forge addon using decorators. It uses the `@forge/resolver` library under the hood to define resolvers, but it provides a more convenient way to define them using decorators.
4
7
 
5
8
  ## Table of contents
6
9
 
7
10
  - [Introduction](#introduction)
8
- - [Table of Contents](#table-of-contents)
11
+ - [Quick Start](#quick-start)
9
12
  - [Installation](#installation)
13
+ - [TypeScript Configuration](#typescript-configuration)
10
14
  - [@Resolver](#resolver)
11
15
  - [@ResolverFn](#resolverfn)
12
16
  - [getDefinitionsForClass](#getdefinitionsforclass)
13
17
  - [Middlewares](#middlewares)
14
18
  - [Error handler](#error-handler)
15
-
16
- [![npm version](https://badge.fury.io/js/ts-forge.svg)](https://badge.fury.io/js/ts-forge)
17
- [![npm downloads](https://img.shields.io/npm/dm/ts-forge.svg)](https://www.npmjs.com/package/ts-forge)
19
+ - [Frontend Usage](#frontend-usage)
20
+ - [Complete Example](#complete-example)
21
+ - [License](#license)
18
22
 
19
23
  ## Introduction
20
24
 
@@ -25,14 +29,14 @@ It provides two main decorators: `@Resolver` and `@ResolverFn`, which can be use
25
29
  When you create a resolver, you typically would do something like this:
26
30
 
27
31
  ```ts
28
- import Resolver "@forge/resolver";
32
+ import Resolver from "@forge/resolver";
29
33
 
30
34
  const resolver = new Resolver();
31
35
 
32
36
  resolver.define("hello", async (req) => {
33
37
  try {
34
38
  return { message: "Hello world!" };
35
- } catch(error) {
39
+ } catch (error) {
36
40
  console.error("An error occurred:", error);
37
41
 
38
42
  return { status: "error", message: "An error occurred" };
@@ -44,7 +48,7 @@ resolver.define("update-user", async (req) => {
44
48
  // Update user logic...
45
49
 
46
50
  return { status: "success", message: "User updated successfully" };
47
- } catch(error) {
51
+ } catch (error) {
48
52
  console.error("An error occurred:", error);
49
53
 
50
54
  return { status: "error", message: "An error occurred" };
@@ -69,7 +73,6 @@ class UserResolver {
69
73
  @ResolverFn("update-user")
70
74
  async updateUser(req) {
71
75
  // Update user logic...
72
- }
73
76
  return { status: "success", message: "User updated successfully" };
74
77
  }
75
78
  }
@@ -82,16 +85,101 @@ export const definitions = getDefinitionsForClass({
82
85
  });
83
86
  ```
84
87
 
88
+ ## Quick Start
89
+
90
+ 1. Install the library and its peer dependency:
91
+
92
+ ```bash
93
+ npm install ts-forge @forge/resolver
94
+ ```
95
+
96
+ 1. Configure TypeScript (see [TypeScript Configuration](#typescript-configuration))
97
+ 2. Create a resolver class:
98
+
99
+ ```ts
100
+ import { Resolver, ResolverFn } from "ts-forge";
101
+ import { Request } from "@forge/resolver";
102
+
103
+ @Resolver()
104
+ class MyResolver {
105
+ @ResolverFn("say-hello")
106
+ async sayHello(req: Request) {
107
+ return { message: "Hello from ts-forge!" };
108
+ }
109
+ }
110
+ ```
111
+
112
+ 1. Export definitions in any file you want, for example `src/resolvers/index.ts`:
113
+
114
+ ```ts
115
+ import { getDefinitionsForClass } from "ts-forge";
116
+ import MyResolver from "./MyResolver";
117
+
118
+ export const handler = getDefinitionsForClass({
119
+ resolvers: [MyResolver]
120
+ });
121
+ ```
122
+
123
+ 1. Define your Forge function in your manifest.yml:
124
+
125
+ ```yml
126
+ modules:
127
+ function:
128
+ - key: resolver-function
129
+ handler: resolvers/index.handler
130
+ ```
131
+
132
+ 1. Use in your Forge app's UI by invoking the resolver function:
133
+
134
+ ```ts
135
+ import { invoke } from "@forge/bridge";
136
+
137
+ async function callHelloResolver() {
138
+ const response = await invoke("resolver-function", {
139
+ key: "say-hello"
140
+ });
141
+
142
+ console.log(response.message); // "Hello from ts-forge!"
143
+ }
144
+ ```
145
+
85
146
  ## Installation
86
147
 
87
148
  This is a Node.js module available on npm. Make sure you have `@forge/resolver` installed as well, since this library uses it under the hood. The minimum version of `@forge/resolver` required is `^1.6.0`.
88
149
 
89
150
  ```bash
90
- npm install ts-forge
151
+ npm install ts-forge @forge/resolver
152
+ ```
153
+
154
+ Or if you're using yarn:
155
+
156
+ ```bash
157
+ yarn add ts-forge @forge/resolver
158
+ ```
159
+
160
+ Or if you're using pnpm:
161
+
162
+ ```bash
163
+ pnpm add ts-forge @forge/resolver
164
+ ```
165
+
166
+ ## TypeScript Configuration
167
+
168
+ To use decorators in TypeScript, you need to enable experimental decorators in your `tsconfig.json`:
169
+
170
+ ```json
171
+ {
172
+ "compilerOptions": {
173
+ "experimentalDecorators": true,
174
+ "emitDecoratorMetadata": true
175
+ }
176
+ }
91
177
  ```
92
178
 
93
179
  ## @Resolver
94
180
 
181
+ The `@Resolver` decorator is used to mark a class as a resolver. You can optionally provide configuration for middlewares and error handlers that will apply to all resolver functions in the class.
182
+
95
183
  ```ts
96
184
  import { Resolver, ResolverFn } from "ts-forge";
97
185
 
@@ -112,10 +200,12 @@ Parameter 1 (Resolver config) - Optional
112
200
  - `middlewares: ((request: Request) => any | Promise<any>)[]`
113
201
  - Optional
114
202
  - This is an array of middleware functions, each function will be called before the resolver function
115
- - If you return a value it will be sent to the frontend, pending middlewares and resolver function won't be called
203
+ - If a middleware returns a value, it will be sent to the frontend and pending middlewares and the resolver function won't be called
116
204
 
117
205
  ## @ResolverFn
118
206
 
207
+ The `@ResolverFn` decorator is used to mark a method as a resolver function. Each decorated method will be registered as a resolver that can be invoked from your Forge app's frontend.
208
+
119
209
  ```ts
120
210
  import { Request } from "@forge/resolver";
121
211
  import { Resolver, ResolverFn } from "ts-forge";
@@ -133,7 +223,7 @@ class HelloWorldResolver {
133
223
 
134
224
  **Parameter 1 (Resolver key or resolver function config) - Required**
135
225
 
136
- You could use either, a string or an object
226
+ You can use either a string or an object.
137
227
 
138
228
  **Resolver key**
139
229
 
@@ -150,23 +240,25 @@ You could use either, a string or an object
150
240
  - `middlewares: ((request: Request) => any | Promise<any>)[]`
151
241
  - Optional
152
242
  - This is an array of middleware functions, each function will be called before the resolver function
153
- - If you return a value it will be sent to the frontend, pending middlewares and resolver function won't be called
243
+ - If a middleware returns a value, it will be sent to the frontend and pending middlewares and the resolver function won't be called
154
244
  - `errorHandler: (error: any, request: Request) => any | Promise<any>`
155
245
  - Optional
156
246
  - If an error is thrown in your resolver function or middlewares, this function will be called so that you can handle the error and return a response
157
247
 
158
- The `@ResolverFn()` decorator takes only one argument, which can be either a string or an object
159
- If you don't need to define middlewares or an error handler, you could just pass the resolver key
248
+ The `@ResolverFn()` decorator takes only one argument, which can be either a string or an object.
249
+ If you don't need to define middlewares or an error handler, you can just pass the resolver key:
160
250
 
161
251
  ```ts
162
252
  @Resolver()
163
253
  class HelloWorldResolver {
164
254
  @ResolverFn("my-resolver")
165
- async hello(req: Request) {}
255
+ async hello(req: Request) {
256
+ return { message: "Hello!" };
257
+ }
166
258
  }
167
259
  ```
168
260
 
169
- or, if you need to define middlewares or an error handler for that resolver function, you could pass a config object like this:
261
+ Or, if you need to define middlewares or an error handler for that resolver function, you can pass a config object like this:
170
262
 
171
263
  ```ts
172
264
  @Resolver()
@@ -176,38 +268,42 @@ class HelloWorldResolver {
176
268
  middlewares: [authMiddleware, adminMiddleware],
177
269
  errorHandler: myErrorHandler
178
270
  })
179
- async hello(req: Request) {}
271
+ async hello(req: Request) {
272
+ return { message: "Hello!" };
273
+ }
180
274
  }
181
275
  ```
182
276
 
183
277
  ## getDefinitionsForClass
184
278
 
185
- This function is used to get definitions of the resolvers that you created, it uses `@forge/resolver` under the hood to define resolvers then calls `getDefinitions()` method and returns its result
279
+ This function is used to get definitions of the resolvers that you created. It uses `@forge/resolver` under the hood to define resolvers, then calls `getDefinitions()` method and returns its result. The returned value should be exported as the `handler` for your Forge function.
186
280
 
187
281
  ```ts
188
282
  import { getDefinitionsForClass } from "ts-forge";
189
283
 
190
- getDefinitionsForClass({
284
+ const handler = getDefinitionsForClass({
191
285
  resolvers: [HelloWorldResolver, GetJiraProjectResolver],
192
- errorHandler: ErrorHandlerFn,
193
- middlewares: MiddlewareFn[]
286
+ errorHandler: myErrorHandler,
287
+ middlewares: [authMiddleware]
194
288
  });
289
+
290
+ export { handler };
195
291
  ```
196
292
 
197
293
  #### Parameters
198
294
 
199
295
  **Parameter 1 (Config) - Required**
200
296
 
201
- - `{ resolvers: [], middlewares: MiddlewareFn[], errorHandler: ErrorHandlerFn }`
297
+ - `{ resolvers: [], middlewares?: MiddlewareFn[], errorHandler?: ErrorHandlerFn }`
202
298
  - `resolvers`
203
299
  - Required
204
- - This is an array of class instances in which you used the `@Resolver()` decorator
300
+ - This is an array of classes (not instances) that have been decorated with `@Resolver()`
205
301
  - Must have at least one element
206
302
  - `middlewares: ((request: Request) => any | Promise<any>)[]`
207
303
  - Optional
208
304
  - This is an array of middleware functions, each function will be called before the resolver function
209
- - If you return a value it will be sent to the frontend, pending middlewares and resolver function won't be called
210
- - Those middlewares will be called before each resolver function defined in `resolvers`
305
+ - If a middleware returns a value, it will be sent to the frontend and pending middlewares and the resolver function won't be called
306
+ - These middlewares will be called before each resolver function defined in `resolvers`
211
307
  - **Note:** Middlewares defined here will be the last to be called, in case you have defined middlewares in `@Resolver()` or `@ResolverFn()`
212
308
  - `errorHandler: (error: any, request: Request) => any | Promise<any>`
213
309
  - Optional
@@ -215,32 +311,57 @@ getDefinitionsForClass({
215
311
  - This function will handle errors for all the resolver functions defined in `resolvers`
216
312
  - **Note:** This function will be called only when an error handler was not defined in `@Resolver()` and `@ResolverFn()`
217
313
 
314
+ #### Return Value
315
+
316
+ Returns the definitions object that should be exported for use in your Forge manifest. This object contains all the resolver function definitions that can be invoked from your Forge app's frontend.
317
+
218
318
  ## Middlewares
219
319
 
220
- You can define middlewares in `@ResolverFn()`, `@Resolver()` and `getDefinitionsForClass()`. It only takes one parameter which is the same request object you have access to in resolver functions
320
+ You can define middlewares in `@ResolverFn()`, `@Resolver()` and `getDefinitionsForClass()`. Each middleware takes one parameter which is the same request object you have access to in resolver functions.
221
321
 
222
- Middleware functions have priority depending where they were defined. When a resolver function is invoked, the middlewares will be called in the following order:
322
+ Middleware functions have priority depending on where they were defined. When a resolver function is invoked, the middlewares will be called in the following order:
223
323
 
224
324
  1. `@ResolverFn()`: Middlewares defined in this decorator's config will be the first to be called
225
325
  2. `@Resolver()`: Middlewares defined in this decorator's config will run after `@ResolverFn()`'s middlewares are called. If no middlewares were defined in `@ResolverFn()`, middlewares defined in `@Resolver()` would be the first to be called
226
- 3. `getDefinitionsForClass()`: Middlewares defined in this function will be the last to be called. If no middlewares were defined in `@ResolverFn()` and `@Resolver()`, middlewares defined in `getDefinitionsForClass()` would be the first to be called, this is useful when you want the same middlewares to be called for all of your resolver functions, so that you don't have to define the same middlewares in each `@ResolverFn()` or `@Resolver()`
326
+ 3. `getDefinitionsForClass()`: Middlewares defined in this function will be the last to be called. If no middlewares were defined in `@ResolverFn()` and `@Resolver()`, middlewares defined in `getDefinitionsForClass()` would be the first to be called. This is useful when you want the same middlewares to be called for all of your resolver functions, so that you don't have to define the same middlewares in each `@ResolverFn()` or `@Resolver()`
327
+
328
+ **Important**: If a middleware returns a value, that value will be sent to the frontend immediately and any pending middlewares and the resolver function will not be executed. If a middleware should just perform checks or logging without stopping execution, it should not return anything.
227
329
 
228
330
  #### Usage
229
331
 
230
- This is an example of a middleware function
332
+ This is an example of a middleware function that returns early (blocking execution):
231
333
 
232
334
  ```ts
233
335
  import { Request } from "@forge/resolver";
234
336
 
235
- // Request is the same object you have access to in your resolver functions
337
+ // This middleware returns a value, stopping execution if user is not admin
236
338
  function verifyUserIsJiraAdmin(request: Request): any | Promise<any> {
237
- // Verify that the current user is a jira admin ...
339
+ const isAdmin = checkIfUserIsAdmin(request.context.accountId);
340
+
341
+ if (!isAdmin) {
342
+ // Returning a value stops execution
343
+ return { error: "Unauthorized", message: "Admin access required" };
344
+ }
345
+
346
+ // Not returning anything allows execution to continue
238
347
  }
239
348
  ```
240
349
 
241
- Then you can use this middleware in `@ResolverFn()`, `@Resolver()` or `getDefinitionsForClass()`
350
+ Example of a middleware that doesn't return (passes through):
242
351
 
243
- In the following example the `verifyUserIsJiraAdmin` middleware is used in `@ResolverFn()`. This is useful when you want certain middleware to be called for some resolver functions
352
+ ```ts
353
+ import { Request } from "@forge/resolver";
354
+
355
+ // This middleware logs but doesn't return, so execution continues
356
+ function loggingMiddleware(request: Request): void {
357
+ console.log(`Resolver called: ${request.payload?.action}`);
358
+ // No return statement - execution continues to next middleware/resolver
359
+ }
360
+ ```
361
+
362
+ Then you can use these middlewares in `@ResolverFn()`, `@Resolver()` or `getDefinitionsForClass()`.
363
+
364
+ In the following example the `verifyUserIsJiraAdmin` middleware is used in `@ResolverFn()`. This is useful when you want certain middleware to be called for specific resolver functions:
244
365
 
245
366
  ```ts
246
367
  @Resolver()
@@ -254,21 +375,18 @@ class HelloWorldResolver {
254
375
  }
255
376
 
256
377
  // verifyUserIsJiraAdmin won't be called when this resolver function is invoked
257
- @ResolverFn({
258
- key: "get-current-user",
259
- middlewares: []
260
- })
378
+ @ResolverFn("get-current-user")
261
379
  async getCurrentUser() {
262
380
  // Return current user
263
381
  }
264
382
  }
265
383
  ```
266
384
 
267
- In this example the `verifyUserIsJiraAdmin` middleware is used in `@Resolver()`. Middlewares defined in this decorator will be called for all resolver functions defined in the class.
385
+ In this example the `verifyUserIsJiraAdmin` middleware is used in `@Resolver()`. Middlewares defined in this decorator will be called for all resolver functions defined in the class:
268
386
 
269
387
  ```ts
270
388
  @Resolver({
271
- // Middlewares defined in this array will be called in each resolver function defined in this class
389
+ // Middlewares defined in this array will be called for each resolver function defined in this class
272
390
  middlewares: [verifyUserIsJiraAdmin]
273
391
  })
274
392
  class HelloWorldResolver {
@@ -286,7 +404,7 @@ class HelloWorldResolver {
286
404
  }
287
405
  ```
288
406
 
289
- Last but not least, you can define middlewares in the `getDefinitionsForClass` function. Middlewares defined here will be called in all resolvers that you pass to the `resolvers` array
407
+ Last but not least, you can define middlewares in the `getDefinitionsForClass` function. Middlewares defined here will be called for all resolvers that you pass to the `resolvers` array:
290
408
 
291
409
  ```ts
292
410
  @Resolver()
@@ -305,12 +423,12 @@ class UserResolver {
305
423
  }
306
424
  }
307
425
 
308
- const definitions = getDefinitionsForClass({
426
+ const handler = getDefinitionsForClass({
309
427
  middlewares: [verifyUserIsJiraAdmin],
310
428
  // Middlewares will be called before each resolver function, you don't need to add the verifyUserIsJiraAdmin middleware
311
429
  // in @Resolver or @ResolverFn decorators
312
430
  resolvers: [HelloWorldResolver, UserResolver]
313
- })
431
+ });
314
432
  ```
315
433
 
316
434
  ## Error handler
@@ -322,13 +440,13 @@ When an error is thrown, the error handler will be called with the error and the
322
440
  When an error is thrown, the error handler will be called in the following order:
323
441
 
324
442
  1. If an error handler is defined in the `@ResolverFn()` decorator, it will be called
325
- 2. Error handler defined in `@Resolver()`, will be called when an error handler was not defined in `@ResolverFn()`
443
+ 2. Error handler defined in `@Resolver()` will be called when an error handler was not defined in `@ResolverFn()`
326
444
  3. If no error handler was defined in `@ResolverFn()` and `@Resolver()`, the error handler defined in `getDefinitionsForClass()` will be called
327
445
  4. If no error handler was defined in any of the above, the error will be logged to the console and will be returned to the frontend
328
446
 
329
447
  #### Usage
330
448
 
331
- This is an example of an error handler function
449
+ This is an example of an error handler function:
332
450
 
333
451
  ```ts
334
452
  import { Request } from "@forge/resolver";
@@ -341,8 +459,9 @@ function myErrorHandler(error: any, request: Request): any | Promise<any> {
341
459
  }
342
460
  ```
343
461
 
344
- Then you can use this error handler in `@ResolverFn()`, `@Resolver()` or `getDefinitionsForClass()`
345
- In the following example the `myErrorHandler` error handler is used in `@ResolverFn()`. This is useful when you want certain error handler to be called for some resolver functions
462
+ Then you can use this error handler in `@ResolverFn()`, `@Resolver()` or `getDefinitionsForClass()`.
463
+
464
+ In the following example the `myErrorHandler` error handler is used in `@ResolverFn()`. This is useful when you want a specific error handler to be called for some resolver functions:
346
465
 
347
466
  ```ts
348
467
  @Resolver()
@@ -363,11 +482,11 @@ class HelloWorldResolver {
363
482
  }
364
483
  ```
365
484
 
366
- In this example the `myErrorHandler` error handler is used in `@Resolver()`. Error handlers defined in this decorator will be called for all resolver functions defined in the class.
485
+ In this example the `myErrorHandler` error handler is used in `@Resolver()`. Error handlers defined in this decorator will be called for all resolver functions defined in the class:
367
486
 
368
487
  ```ts
369
488
  @Resolver({
370
- // Error handler defined in this function will be called if an error is thrown in any of the resolver functions defined in this class
489
+ // Error handler defined in this decorator will be called if an error is thrown in any of the resolver functions defined in this class
371
490
  errorHandler: myErrorHandler
372
491
  })
373
492
  class HelloWorldResolver {
@@ -385,7 +504,7 @@ class HelloWorldResolver {
385
504
  }
386
505
  ```
387
506
 
388
- Last but not least, you can define an error handler in the `getDefinitionsForClass` function. Error handlers defined here will be called if an error is thrown in any of the resolvers that you pass to the `resolvers` array.
507
+ Last but not least, you can define an error handler in the `getDefinitionsForClass` function. Error handlers defined here will be called if an error is thrown in any of the resolvers that you pass to the `resolvers` array:
389
508
 
390
509
  ```ts
391
510
  @Resolver()
@@ -404,10 +523,219 @@ class UserResolver {
404
523
  }
405
524
  }
406
525
 
407
- const definitions = getDefinitionsForClass({
526
+ const handler = getDefinitionsForClass({
408
527
  errorHandler: myErrorHandler,
409
528
  // Error handler will be called if an error is thrown in any of the resolver functions
410
529
  // You don't need to add the myErrorHandler error handler in @Resolver or @ResolverFn decorators
411
530
  resolvers: [HelloWorldResolver, UserResolver]
412
531
  });
413
532
  ```
533
+
534
+ ## Frontend Usage
535
+
536
+ After defining your resolvers and exporting the handler, you can invoke them from your Forge app's frontend using the `invoke()` method from `@forge/bridge`.
537
+
538
+ **Backend (src/resolvers/index.ts):**
539
+
540
+ ```ts
541
+ import { getDefinitionsForClass, Resolver, ResolverFn } from "ts-forge";
542
+ import { Request } from "@forge/resolver";
543
+
544
+ @Resolver()
545
+ class UserResolver {
546
+ @ResolverFn("get-user")
547
+ async getUser(req: Request) {
548
+ const userId = req.payload.userId;
549
+ // Fetch user logic...
550
+ return { id: userId, name: "John Doe" };
551
+ }
552
+
553
+ @ResolverFn("update-user")
554
+ async updateUser(req: Request) {
555
+ const { userId, name } = req.payload;
556
+ // Update user logic...
557
+ return { success: true, message: "User updated" };
558
+ }
559
+ }
560
+
561
+ export const handler = getDefinitionsForClass({
562
+ resolvers: [UserResolver]
563
+ });
564
+ ```
565
+
566
+ **Frontend (src/index.tsx or src/index.jsx):**
567
+
568
+ ```tsx
569
+ import React, { useState, useEffect } from "react";
570
+ import { invoke } from "@forge/bridge";
571
+
572
+ function App() {
573
+ const [user, setUser] = useState(null);
574
+
575
+ useEffect(() => {
576
+ // Invoke the "get-user" resolver
577
+ invoke("get-user", { userId: "123" })
578
+ .then((response) => {
579
+ setUser(response);
580
+ })
581
+ .catch((error) => {
582
+ console.error("Error fetching user:", error);
583
+ });
584
+ }, []);
585
+
586
+ const handleUpdateUser = async () => {
587
+ try {
588
+ // Invoke the "update-user" resolver
589
+ const response = await invoke("update-user", {
590
+ userId: "123",
591
+ name: "Jane Doe"
592
+ });
593
+ console.log(response.message);
594
+ } catch (error) {
595
+ console.error("Error updating user:", error);
596
+ }
597
+ };
598
+
599
+ return (
600
+ <div>
601
+ {user && (
602
+ <>
603
+ <h1>{user.name}</h1>
604
+ <button onClick={handleUpdateUser}>Update User</button>
605
+ </>
606
+ )}
607
+ </div>
608
+ );
609
+ }
610
+
611
+ export default App;
612
+ ```
613
+
614
+ ## Complete Example
615
+
616
+ Here's a complete example showing all features together:
617
+
618
+ **src/middlewares/auth.ts:**
619
+
620
+ ```ts
621
+ import { Request } from "@forge/resolver";
622
+
623
+ export function authMiddleware(request: Request) {
624
+ const accountId = request.context.accountId;
625
+
626
+ if (!accountId) {
627
+ return { error: "Unauthorized", message: "User not authenticated" };
628
+ }
629
+ // Don't return anything to continue execution
630
+ }
631
+
632
+ export function adminMiddleware(request: Request) {
633
+ // Check if user is admin
634
+ const isAdmin = checkIfUserIsAdmin(request.context.accountId);
635
+
636
+ if (!isAdmin) {
637
+ return { error: "Forbidden", message: "Admin access required" };
638
+ }
639
+ }
640
+ ```
641
+
642
+ **src/handlers/errorHandler.ts:**
643
+
644
+ ```ts
645
+ import { Request } from "@forge/resolver";
646
+
647
+ export function globalErrorHandler(error: any, request: Request) {
648
+ console.error("Error in resolver:", error);
649
+
650
+ // Log to external service
651
+ logErrorToService(error, request.context);
652
+
653
+ return {
654
+ success: false,
655
+ error: "An unexpected error occurred",
656
+ message: error.message
657
+ };
658
+ }
659
+ ```
660
+
661
+ **src/resolvers/UserResolver.ts:**
662
+
663
+ ```ts
664
+ import { Resolver, ResolverFn } from "ts-forge";
665
+ import { Request } from "@forge/resolver";
666
+ import { authMiddleware, adminMiddleware } from "../middlewares/auth";
667
+
668
+ @Resolver({
669
+ middlewares: [authMiddleware]
670
+ })
671
+ class UserResolver {
672
+ @ResolverFn("get-current-user")
673
+ async getCurrentUser(req: Request) {
674
+ const accountId = req.context.accountId;
675
+ // Fetch user from API
676
+ return { id: accountId, name: "John Doe" };
677
+ }
678
+
679
+ @ResolverFn({
680
+ key: "delete-user",
681
+ middlewares: [adminMiddleware] // Additional middleware for this resolver
682
+ })
683
+ async deleteUser(req: Request) {
684
+ const userId = req.payload.userId;
685
+ // Delete user logic
686
+ return { success: true, message: `User ${userId} deleted` };
687
+ }
688
+ }
689
+
690
+ export default UserResolver;
691
+ ```
692
+
693
+ **src/resolvers/ProjectResolver.ts:**
694
+
695
+ ```ts
696
+ import { Resolver, ResolverFn } from "ts-forge";
697
+ import { Request } from "@forge/resolver";
698
+
699
+ @Resolver()
700
+ class ProjectResolver {
701
+ @ResolverFn("get-projects")
702
+ async getProjects(req: Request) {
703
+ // Fetch projects
704
+ return { projects: [] };
705
+ }
706
+ }
707
+
708
+ export default ProjectResolver;
709
+ ```
710
+
711
+ **src/resolvers/index.ts:**
712
+
713
+ ```ts
714
+ import { getDefinitionsForClass } from "ts-forge";
715
+ import UserResolver from "./UserResolver";
716
+ import ProjectResolver from "./ProjectResolver";
717
+ import { globalErrorHandler } from "../handlers/errorHandler";
718
+
719
+ export const handler = getDefinitionsForClass({
720
+ resolvers: [UserResolver, ProjectResolver],
721
+ errorHandler: globalErrorHandler
722
+ });
723
+ ```
724
+
725
+ **manifest.yml:**
726
+
727
+ ```yml
728
+ modules:
729
+ function:
730
+ - key: resolver-function
731
+ handler: resolvers/index.handler
732
+ jira:issuePanel:
733
+ - key: my-issue-panel
734
+ function: resolver-function
735
+ title: My Issue Panel
736
+ icon: https://example.com/icon.png
737
+ ```
738
+
739
+ ## License
740
+
741
+ MIT
@@ -15,8 +15,7 @@ import _ from "../constants";
15
15
  * class MyResolver{}
16
16
  * ```
17
17
  */
18
- export function Resolver(config) {
19
- if (config === void 0) { config = { middlewares: [], errorHandler: undefined }; }
18
+ export function Resolver(config = { middlewares: [], errorHandler: undefined }) {
20
19
  return function (constructor) {
21
20
  constructor.prototype[_.RESOLVER_CONFIG] = {
22
21
  middlewares: Array.from(config.middlewares || []),
@@ -7,33 +7,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- var __generator = (this && this.__generator) || function (thisArg, body) {
11
- var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
12
- return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
13
- function verb(n) { return function (v) { return step([n, v]); }; }
14
- function step(op) {
15
- if (f) throw new TypeError("Generator is already executing.");
16
- while (g && (g = 0, op[0] && (_ = 0)), _) try {
17
- if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
18
- if (y = 0, t) op = [op[0] & 2, t.value];
19
- switch (op[0]) {
20
- case 0: case 1: t = op; break;
21
- case 4: _.label++; return { value: op[1], done: false };
22
- case 5: _.label++; y = op[1]; op = [0]; continue;
23
- case 7: op = _.ops.pop(); _.trys.pop(); continue;
24
- default:
25
- if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
26
- if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
27
- if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
28
- if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
29
- if (t[2]) _.ops.pop();
30
- _.trys.pop(); continue;
31
- }
32
- op = body.call(thisArg, _);
33
- } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
34
- if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
35
- }
36
- };
37
10
  import isResolverFnConfig from "../utils/isResolverFnConfig";
38
11
  import _ from "../constants";
39
12
  /**
@@ -46,7 +19,7 @@ import _ from "../constants";
46
19
  export function ResolverFn(resolverFnConfig) {
47
20
  return function (target, propertyKey, descriptor) {
48
21
  // Handle the case where resolverFnConfig is a string
49
- var config = isResolverFnConfig(resolverFnConfig)
22
+ const config = isResolverFnConfig(resolverFnConfig)
50
23
  ? resolverFnConfig
51
24
  : { key: resolverFnConfig, middlewares: [], errorHandler: undefined };
52
25
  // If the middlewares property is not an array, initialize it as an empty array
@@ -59,76 +32,61 @@ export function ResolverFn(resolverFnConfig) {
59
32
  }
60
33
  // Add the resolver's data to the target
61
34
  target[_.RESOLVER_FUNCTIONS].push({
62
- config: config,
35
+ config,
63
36
  methodName: propertyKey
64
37
  });
65
- var method = descriptor.value;
38
+ const method = descriptor.value;
66
39
  descriptor.value = function (req) {
67
- return __awaiter(this, void 0, void 0, function () {
68
- var targetConfig, middlewares, _i, middlewares_1, middleware, response, error_1, errorHandlerFn, err_1;
69
- return __generator(this, function (_a) {
70
- switch (_a.label) {
71
- case 0:
72
- targetConfig = this[_.RESOLVER_CONFIG] || {};
73
- _a.label = 1;
74
- case 1:
75
- _a.trys.push([1, 7, , 12]);
76
- middlewares = Array.from((config === null || config === void 0 ? void 0 : config.middlewares) || []);
77
- // If there are middlewares defined in the resolver class, add them to the middlewares array
78
- if (Array.isArray(targetConfig.middlewares)) {
79
- middlewares.push.apply(middlewares, targetConfig.middlewares);
80
- }
81
- // If there are middlewares defined in the getDefinitionsForClass config, add them to the middlewares array
82
- if (Array.isArray(targetConfig.globalMiddlewares)) {
83
- middlewares.push.apply(middlewares, targetConfig.globalMiddlewares);
84
- }
85
- _i = 0, middlewares_1 = middlewares;
86
- _a.label = 2;
87
- case 2:
88
- if (!(_i < middlewares_1.length)) return [3 /*break*/, 5];
89
- middleware = middlewares_1[_i];
90
- return [4 /*yield*/, middleware(req)];
91
- case 3:
92
- response = _a.sent();
93
- // If response is provided, send it to the frontend
94
- // This means that the resolver or next middleware will not be called
95
- if (response) {
96
- return [2 /*return*/, response];
97
- }
98
- _a.label = 4;
99
- case 4:
100
- _i++;
101
- return [3 /*break*/, 2];
102
- case 5: return [4 /*yield*/, method.call(this, req)];
103
- case 6:
104
- // Call the original method
105
- return [2 /*return*/, _a.sent()];
106
- case 7:
107
- error_1 = _a.sent();
108
- errorHandlerFn = config.errorHandler || (targetConfig === null || targetConfig === void 0 ? void 0 : targetConfig.errorHandler) || (targetConfig === null || targetConfig === void 0 ? void 0 : targetConfig.globalErrorHandler);
109
- if (!errorHandlerFn) return [3 /*break*/, 11];
110
- _a.label = 8;
111
- case 8:
112
- _a.trys.push([8, 10, , 11]);
40
+ return __awaiter(this, void 0, void 0, function* () {
41
+ const targetConfig = this[_.RESOLVER_CONFIG] || {};
42
+ try {
43
+ // Merge middlewares from the resolver function and the resolver class
44
+ // Method middlewares are always executed first
45
+ const middlewares = Array.from((config === null || config === void 0 ? void 0 : config.middlewares) || []);
46
+ // If there are middlewares defined in the resolver class, add them to the middlewares array
47
+ if (Array.isArray(targetConfig.middlewares)) {
48
+ middlewares.push(...targetConfig.middlewares);
49
+ }
50
+ // If there are middlewares defined in the getDefinitionsForClass config, add them to the middlewares array
51
+ if (Array.isArray(targetConfig.globalMiddlewares)) {
52
+ middlewares.push(...targetConfig.globalMiddlewares);
53
+ }
54
+ // Run provided middlewares
55
+ for (const middleware of middlewares) {
56
+ const response = yield middleware(req);
57
+ // If response is provided, send it to the frontend
58
+ // This means that the resolver or next middleware will not be called
59
+ if (response) {
60
+ return response;
61
+ }
62
+ }
63
+ // Call the original method
64
+ return yield method.call(this, req);
65
+ }
66
+ catch (error) {
67
+ // Resolver function error handler has priority over the resolver error handler
68
+ const errorHandlerFn = config.errorHandler || (targetConfig === null || targetConfig === void 0 ? void 0 : targetConfig.errorHandler) || (targetConfig === null || targetConfig === void 0 ? void 0 : targetConfig.globalErrorHandler);
69
+ // If an error handler is provided, call it with the request data and error object
70
+ // This allows the error handler to handle the error and return a response
71
+ if (errorHandlerFn) {
72
+ try {
113
73
  req.context.resolver = {
114
74
  key: config.key,
115
75
  className: target.constructor.name,
116
76
  methodName: propertyKey
117
77
  };
118
- return [4 /*yield*/, errorHandlerFn(error_1, req)];
119
- case 9: return [2 /*return*/, _a.sent()];
120
- case 10:
121
- err_1 = _a.sent();
78
+ return yield errorHandlerFn(error, req);
79
+ }
80
+ catch (err) {
122
81
  // If the error handler throws an error, log it and return the original error
123
- console.error(err_1);
124
- return [2 /*return*/, error_1];
125
- case 11:
126
- // If no error handler is provided, log the error, then return it
127
- console.error(error_1);
128
- return [2 /*return*/, error_1];
129
- case 12: return [2 /*return*/];
82
+ console.error(err);
83
+ return error;
84
+ }
130
85
  }
131
- });
86
+ // If no error handler is provided, log the error, then return it
87
+ console.error(error);
88
+ return error;
89
+ }
132
90
  });
133
91
  };
134
92
  };
@@ -8,23 +8,21 @@ import _ from "../constants";
8
8
  * @param config.errorHandler - Custom error handler to be applied to the provided resolvers
9
9
  * @returns - Resolver definitions
10
10
  */
11
- export function getDefinitionsForClass(_a) {
12
- var resolvers = _a.resolvers, _b = _a.middlewares, middlewares = _b === void 0 ? [] : _b, errorHandler = _a.errorHandler;
13
- var forgeResolver = new ForgeResolver();
11
+ export function getDefinitionsForClass({ resolvers, middlewares = [], errorHandler }) {
12
+ const forgeResolver = new ForgeResolver();
14
13
  if (!Array.isArray(middlewares)) {
15
14
  throw new Error("Middlewares must be an array");
16
15
  }
17
- if (middlewares.length > 0 && !middlewares.every(function (m) { return typeof m === "function"; })) {
16
+ if (middlewares.length > 0 && !middlewares.every((m) => typeof m === "function")) {
18
17
  throw new Error("All middlewares must be functions");
19
18
  }
20
19
  if (errorHandler && typeof errorHandler !== "function") {
21
20
  throw new Error("Error handler must be a function");
22
21
  }
23
- for (var _i = 0, resolvers_1 = resolvers; _i < resolvers_1.length; _i++) {
24
- var instance = resolvers_1[_i];
22
+ for (const instance of resolvers) {
25
23
  if (instance[_.RESOLVER_CONFIG]) {
26
24
  // Set global middlewares and error handler for the resolver class
27
- var instanceConfig = {
25
+ const instanceConfig = {
28
26
  middlewares: Array.from(instance[_.RESOLVER_CONFIG].middlewares || []),
29
27
  errorHandler: instance[_.RESOLVER_CONFIG].errorHandler
30
28
  };
@@ -34,8 +32,7 @@ export function getDefinitionsForClass(_a) {
34
32
  // Set the updated config on the instance
35
33
  instance[_.RESOLVER_CONFIG] = instanceConfig;
36
34
  }
37
- for (var _c = 0, _d = instance[_.RESOLVER_FUNCTIONS]; _c < _d.length; _c++) {
38
- var resolver = _d[_c];
35
+ for (const resolver of instance[_.RESOLVER_FUNCTIONS]) {
39
36
  forgeResolver.define(resolver.config.key, instance[resolver.methodName].bind(instance));
40
37
  }
41
38
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-forge",
3
- "version": "1.0.2",
3
+ "version": "2.0.0-beta.0",
4
4
  "main": "dist/index.js",
5
5
  "scripts": {
6
6
  "test": "vitest --run --coverage",
@@ -16,7 +16,9 @@
16
16
  "keywords": [
17
17
  "forge",
18
18
  "atlassian",
19
- "addon"
19
+ "addon",
20
+ "ts-forge",
21
+ "resolvers"
20
22
  ],
21
23
  "author": "Gustavo Velazquez",
22
24
  "license": "ISC",
@@ -26,10 +28,10 @@
26
28
  },
27
29
  "devDependencies": {
28
30
  "@types/node": "^24.10.0",
29
- "@vitest/coverage-v8": "4.0.8",
30
- "@vitest/ui": "4.0.8",
31
- "jsdom": "^27.1.0",
32
- "typescript": "5.9.3",
33
- "vitest": "^4.0.8"
31
+ "@vitest/coverage-v8": "4.1.2",
32
+ "@vitest/ui": "4.1.2",
33
+ "jsdom": "^29.0.1",
34
+ "typescript": "6.0.2",
35
+ "vitest": "^4.1.2"
34
36
  }
35
- }
37
+ }