@friggframework/admin-scripts 2.0.0--canary.545.b4ca16d.0 → 2.0.0--canary.517.ff03f2c.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.
Files changed (45) hide show
  1. package/README.md +272 -0
  2. package/index.js +22 -19
  3. package/package.json +6 -6
  4. package/src/adapters/__tests__/aws-scheduler-adapter.test.js +106 -45
  5. package/src/adapters/__tests__/local-scheduler-adapter.test.js +24 -10
  6. package/src/adapters/__tests__/scheduler-adapter-factory.test.js +30 -9
  7. package/src/adapters/__tests__/scheduler-adapter.test.js +6 -2
  8. package/src/adapters/aws-scheduler-adapter.js +55 -28
  9. package/src/adapters/local-scheduler-adapter.js +2 -2
  10. package/src/adapters/scheduler-adapter-factory.js +3 -1
  11. package/src/adapters/scheduler-adapter.js +9 -3
  12. package/src/application/__tests__/admin-frigg-commands.test.js +73 -30
  13. package/src/application/__tests__/admin-script-base.test.js +23 -6
  14. package/src/application/__tests__/script-factory.test.js +30 -6
  15. package/src/application/__tests__/script-runner.test.js +113 -24
  16. package/src/application/__tests__/validate-script-input.test.js +54 -15
  17. package/src/application/admin-frigg-commands.js +21 -9
  18. package/src/application/admin-script-base.js +3 -2
  19. package/src/application/script-factory.js +3 -1
  20. package/src/application/script-runner.js +90 -48
  21. package/src/application/use-cases/__tests__/delete-schedule-use-case.test.js +16 -7
  22. package/src/application/use-cases/__tests__/get-effective-schedule-use-case.test.js +9 -4
  23. package/src/application/use-cases/__tests__/upsert-schedule-use-case.test.js +21 -10
  24. package/src/application/use-cases/delete-schedule-use-case.js +6 -4
  25. package/src/application/use-cases/get-effective-schedule-use-case.js +3 -1
  26. package/src/application/use-cases/index.js +3 -1
  27. package/src/application/use-cases/upsert-schedule-use-case.js +30 -11
  28. package/src/application/validate-script-input.js +10 -3
  29. package/src/infrastructure/__tests__/admin-auth-middleware.test.js +9 -3
  30. package/src/infrastructure/__tests__/admin-script-router.test.js +125 -52
  31. package/src/infrastructure/admin-auth-middleware.js +3 -1
  32. package/src/infrastructure/admin-script-router.js +129 -14
  33. package/src/infrastructure/bootstrap.js +82 -0
  34. package/src/infrastructure/script-executor-handler.js +119 -52
  35. package/src/application/__tests__/dry-run-http-interceptor.test.js +0 -313
  36. package/src/application/__tests__/dry-run-repository-wrapper.test.js +0 -257
  37. package/src/application/__tests__/schedule-management-use-case.test.js +0 -276
  38. package/src/application/dry-run-http-interceptor.js +0 -296
  39. package/src/application/dry-run-repository-wrapper.js +0 -261
  40. package/src/application/schedule-management-use-case.js +0 -230
  41. package/src/builtins/__tests__/integration-health-check.test.js +0 -607
  42. package/src/builtins/__tests__/oauth-token-refresh.test.js +0 -354
  43. package/src/builtins/index.js +0 -28
  44. package/src/builtins/integration-health-check.js +0 -278
  45. package/src/builtins/oauth-token-refresh.js +0 -220
@@ -162,11 +162,19 @@ describe('ScriptFactory', () => {
162
162
  describe('getNames()', () => {
163
163
  it('should return array of all registered script names', () => {
164
164
  class Script1 extends AdminScriptBase {
165
- static Definition = { name: 'script-1', version: '1.0.0', description: 'One' };
165
+ static Definition = {
166
+ name: 'script-1',
167
+ version: '1.0.0',
168
+ description: 'One',
169
+ };
166
170
  }
167
171
 
168
172
  class Script2 extends AdminScriptBase {
169
- static Definition = { name: 'script-2', version: '1.0.0', description: 'Two' };
173
+ static Definition = {
174
+ name: 'script-2',
175
+ version: '1.0.0',
176
+ description: 'Two',
177
+ };
170
178
  }
171
179
 
172
180
  factory.registerAll([Script1, Script2]);
@@ -272,11 +280,19 @@ describe('ScriptFactory', () => {
272
280
  describe('clear()', () => {
273
281
  it('should remove all registered scripts', () => {
274
282
  class Script1 extends AdminScriptBase {
275
- static Definition = { name: 'script-1', version: '1.0.0', description: 'One' };
283
+ static Definition = {
284
+ name: 'script-1',
285
+ version: '1.0.0',
286
+ description: 'One',
287
+ };
276
288
  }
277
289
 
278
290
  class Script2 extends AdminScriptBase {
279
- static Definition = { name: 'script-2', version: '1.0.0', description: 'Two' };
291
+ static Definition = {
292
+ name: 'script-2',
293
+ version: '1.0.0',
294
+ description: 'Two',
295
+ };
280
296
  }
281
297
 
282
298
  factory.registerAll([Script1, Script2]);
@@ -295,14 +311,22 @@ describe('ScriptFactory', () => {
295
311
  expect(factory.size).toBe(0);
296
312
 
297
313
  class Script1 extends AdminScriptBase {
298
- static Definition = { name: 'script-1', version: '1.0.0', description: 'One' };
314
+ static Definition = {
315
+ name: 'script-1',
316
+ version: '1.0.0',
317
+ description: 'One',
318
+ };
299
319
  }
300
320
 
301
321
  factory.register(Script1);
302
322
  expect(factory.size).toBe(1);
303
323
 
304
324
  class Script2 extends AdminScriptBase {
305
- static Definition = { name: 'script-2', version: '1.0.0', description: 'Two' };
325
+ static Definition = {
326
+ name: 'script-2',
327
+ version: '1.0.0',
328
+ description: 'Two',
329
+ };
306
330
  }
307
331
 
308
332
  factory.register(Script2);
@@ -6,13 +6,15 @@ const { AdminScriptBase } = require('../admin-script-base');
6
6
  jest.mock('../admin-frigg-commands');
7
7
  jest.mock('@friggframework/core/application/commands/admin-script-commands');
8
8
 
9
- const { createAdminFriggCommands } = require('../admin-frigg-commands');
10
- const { createAdminScriptCommands } = require('@friggframework/core/application/commands/admin-script-commands');
9
+ const { createAdminScriptContext } = require('../admin-frigg-commands');
10
+ const {
11
+ createAdminScriptCommands,
12
+ } = require('@friggframework/core/application/commands/admin-script-commands');
11
13
 
12
14
  describe('ScriptRunner', () => {
13
15
  let scriptFactory;
14
16
  let mockCommands;
15
- let mockFrigg;
17
+ let mockContext;
16
18
  let testScript;
17
19
 
18
20
  class TestScript extends AdminScriptBase {
@@ -41,13 +43,14 @@ describe('ScriptRunner', () => {
41
43
  completeAdminProcess: jest.fn(),
42
44
  };
43
45
 
44
- mockFrigg = {
46
+ mockContext = {
45
47
  log: jest.fn(),
46
48
  getExecutionId: jest.fn(),
49
+ getLogs: jest.fn(() => []),
47
50
  };
48
51
 
49
52
  createAdminScriptCommands.mockReturnValue(mockCommands);
50
- createAdminFriggCommands.mockReturnValue(mockFrigg);
53
+ createAdminScriptContext.mockReturnValue(mockContext);
51
54
 
52
55
  mockCommands.createAdminProcess.mockResolvedValue({
53
56
  id: 'exec-123',
@@ -62,17 +65,27 @@ describe('ScriptRunner', () => {
62
65
 
63
66
  describe('execute()', () => {
64
67
  it('should execute script successfully', async () => {
65
- const runner = new ScriptRunner({ scriptFactory, commands: mockCommands });
66
-
67
- const result = await runner.execute('test-script', { foo: 'bar' }, {
68
- trigger: 'MANUAL',
69
- mode: 'async',
70
- audit: { apiKeyName: 'test-key' },
68
+ const runner = new ScriptRunner({
69
+ scriptFactory,
70
+ commands: mockCommands,
71
71
  });
72
72
 
73
+ const result = await runner.execute(
74
+ 'test-script',
75
+ { foo: 'bar' },
76
+ {
77
+ trigger: 'MANUAL',
78
+ mode: 'async',
79
+ audit: { apiKeyName: 'test-key' },
80
+ }
81
+ );
82
+
73
83
  expect(result.status).toBe('COMPLETED');
74
84
  expect(result.scriptName).toBe('test-script');
75
- expect(result.output).toEqual({ success: true, params: { foo: 'bar' } });
85
+ expect(result.output).toEqual({
86
+ success: true,
87
+ params: { foo: 'bar' },
88
+ });
76
89
  expect(result.executionId).toBe('exec-123');
77
90
  expect(result.metrics.durationMs).toBeGreaterThanOrEqual(0);
78
91
 
@@ -103,7 +116,10 @@ describe('ScriptRunner', () => {
103
116
  });
104
117
 
105
118
  it('should throw error if trigger is not provided', async () => {
106
- const runner = new ScriptRunner({ scriptFactory, commands: mockCommands });
119
+ const runner = new ScriptRunner({
120
+ scriptFactory,
121
+ commands: mockCommands,
122
+ });
107
123
 
108
124
  await expect(
109
125
  runner.execute('test-script', { foo: 'bar' }, {})
@@ -111,7 +127,10 @@ describe('ScriptRunner', () => {
111
127
  });
112
128
 
113
129
  it('should throw error if options are omitted entirely', async () => {
114
- const runner = new ScriptRunner({ scriptFactory, commands: mockCommands });
130
+ const runner = new ScriptRunner({
131
+ scriptFactory,
132
+ commands: mockCommands,
133
+ });
115
134
 
116
135
  await expect(
117
136
  runner.execute('test-script', { foo: 'bar' })
@@ -133,13 +152,20 @@ describe('ScriptRunner', () => {
133
152
  }
134
153
 
135
154
  scriptFactory.register(FailingScript);
136
- const runner = new ScriptRunner({ scriptFactory, commands: mockCommands });
137
-
138
- const result = await runner.execute('failing-script', {}, {
139
- trigger: 'MANUAL',
140
- mode: 'sync',
155
+ const runner = new ScriptRunner({
156
+ scriptFactory,
157
+ commands: mockCommands,
141
158
  });
142
159
 
160
+ const result = await runner.execute(
161
+ 'failing-script',
162
+ {},
163
+ {
164
+ trigger: 'MANUAL',
165
+ mode: 'sync',
166
+ }
167
+ );
168
+
143
169
  expect(result.status).toBe('FAILED');
144
170
  expect(result.scriptName).toBe('failing-script');
145
171
  expect(result.error.message).toBe('Script failed');
@@ -186,13 +212,20 @@ describe('ScriptRunner', () => {
186
212
  });
187
213
 
188
214
  it('should reuse existing execution ID when provided', async () => {
189
- const runner = new ScriptRunner({ scriptFactory, commands: mockCommands });
190
-
191
- const result = await runner.execute('test-script', { foo: 'bar' }, {
192
- trigger: 'QUEUE',
193
- executionId: 'existing-exec-456',
215
+ const runner = new ScriptRunner({
216
+ scriptFactory,
217
+ commands: mockCommands,
194
218
  });
195
219
 
220
+ const result = await runner.execute(
221
+ 'test-script',
222
+ { foo: 'bar' },
223
+ {
224
+ trigger: 'QUEUE',
225
+ executionId: 'existing-exec-456',
226
+ }
227
+ );
228
+
196
229
  expect(result.executionId).toBe('existing-exec-456');
197
230
  expect(mockCommands.createAdminProcess).not.toHaveBeenCalled();
198
231
  expect(mockCommands.updateAdminProcessState).toHaveBeenCalledWith(
@@ -200,6 +233,62 @@ describe('ScriptRunner', () => {
200
233
  'RUNNING'
201
234
  );
202
235
  });
236
+
237
+ it('reports COMPLETED even when persisting completion fails', async () => {
238
+ // Commands return an error object (never throw). A successful script
239
+ // must not be misreported as FAILED if the completion write fails.
240
+ mockCommands.completeAdminProcess.mockResolvedValue({
241
+ error: 500,
242
+ reason: 'DB write failed',
243
+ });
244
+ const runner = new ScriptRunner({
245
+ scriptFactory,
246
+ commands: mockCommands,
247
+ });
248
+
249
+ const result = await runner.execute(
250
+ 'test-script',
251
+ {},
252
+ { trigger: 'MANUAL' }
253
+ );
254
+
255
+ expect(result.status).toBe('COMPLETED');
256
+ expect(result.stateUpdateFailed).toBe(true);
257
+ });
258
+
259
+ it('throws when the execution record cannot be created', async () => {
260
+ mockCommands.createAdminProcess.mockResolvedValue({
261
+ error: 500,
262
+ reason: 'DB down',
263
+ });
264
+ const runner = new ScriptRunner({
265
+ scriptFactory,
266
+ commands: mockCommands,
267
+ });
268
+
269
+ await expect(
270
+ runner.execute('test-script', {}, { trigger: 'MANUAL' })
271
+ ).rejects.toThrow('DB down');
272
+ });
273
+
274
+ it('persists collected logs on completion', async () => {
275
+ mockContext.getLogs.mockReturnValue([
276
+ { level: 'info', message: 'hi' },
277
+ ]);
278
+ const runner = new ScriptRunner({
279
+ scriptFactory,
280
+ commands: mockCommands,
281
+ });
282
+
283
+ await runner.execute('test-script', {}, { trigger: 'MANUAL' });
284
+
285
+ expect(mockCommands.completeAdminProcess).toHaveBeenCalledWith(
286
+ 'exec-123',
287
+ expect.objectContaining({
288
+ logs: [{ level: 'info', message: 'hi' }],
289
+ })
290
+ );
291
+ });
203
292
  });
204
293
 
205
294
  describe('createScriptRunner()', () => {
@@ -1,4 +1,8 @@
1
- const { validateScriptInput, validateParams, validateType } = require('../validate-script-input');
1
+ const {
2
+ validateScriptInput,
3
+ validateParams,
4
+ validateType,
5
+ } = require('../validate-script-input');
2
6
  const { ScriptFactory } = require('../script-factory');
3
7
  const { AdminScriptBase } = require('../admin-script-base');
4
8
 
@@ -61,12 +65,18 @@ describe('validateScriptInput', () => {
61
65
  }
62
66
 
63
67
  beforeEach(() => {
64
- scriptFactory = new ScriptFactory([TestScript, SchemaScript, TypedScript]);
68
+ scriptFactory = new ScriptFactory([
69
+ TestScript,
70
+ SchemaScript,
71
+ TypedScript,
72
+ ]);
65
73
  });
66
74
 
67
75
  describe('validateScriptInput()', () => {
68
76
  it('should return VALID for script without schema', () => {
69
- const result = validateScriptInput(scriptFactory, 'test-script', { foo: 'bar' });
77
+ const result = validateScriptInput(scriptFactory, 'test-script', {
78
+ foo: 'bar',
79
+ });
70
80
 
71
81
  expect(result.status).toBe('VALID');
72
82
  expect(result.scriptName).toBe('test-script');
@@ -77,11 +87,17 @@ describe('validateScriptInput', () => {
77
87
  });
78
88
 
79
89
  it('should return INVALID when required parameters are missing', () => {
80
- const result = validateScriptInput(scriptFactory, 'schema-script', {});
90
+ const result = validateScriptInput(
91
+ scriptFactory,
92
+ 'schema-script',
93
+ {}
94
+ );
81
95
 
82
96
  expect(result.status).toBe('INVALID');
83
97
  expect(result.preview.validation.valid).toBe(false);
84
- expect(result.preview.validation.errors).toContain('Missing required parameter: requiredParam');
98
+ expect(result.preview.validation.errors).toContain(
99
+ 'Missing required parameter: requiredParam'
100
+ );
85
101
  });
86
102
 
87
103
  it('should return INVALID for wrong parameter types', () => {
@@ -122,7 +138,11 @@ describe('validateScriptInput', () => {
122
138
  });
123
139
 
124
140
  it('should return null inputSchema when script has no schema', () => {
125
- const result = validateScriptInput(scriptFactory, 'test-script', {});
141
+ const result = validateScriptInput(
142
+ scriptFactory,
143
+ 'test-script',
144
+ {}
145
+ );
126
146
 
127
147
  expect(result.preview.inputSchema).toBeNull();
128
148
  });
@@ -130,7 +150,10 @@ describe('validateScriptInput', () => {
130
150
 
131
151
  describe('validateParams()', () => {
132
152
  it('should return valid when no schema defined', () => {
133
- const result = validateParams({ name: 'test' }, { anything: 'goes' });
153
+ const result = validateParams(
154
+ { name: 'test' },
155
+ { anything: 'goes' }
156
+ );
134
157
 
135
158
  expect(result.valid).toBe(true);
136
159
  expect(result.errors).toHaveLength(0);
@@ -158,35 +181,51 @@ describe('validateScriptInput', () => {
158
181
  describe('validateType()', () => {
159
182
  it('should validate integer type', () => {
160
183
  expect(validateType('x', 42, { type: 'integer' })).toBeNull();
161
- expect(validateType('x', 3.14, { type: 'integer' })).toContain('must be an integer');
162
- expect(validateType('x', 'foo', { type: 'integer' })).toContain('must be an integer');
184
+ expect(validateType('x', 3.14, { type: 'integer' })).toContain(
185
+ 'must be an integer'
186
+ );
187
+ expect(validateType('x', 'foo', { type: 'integer' })).toContain(
188
+ 'must be an integer'
189
+ );
163
190
  });
164
191
 
165
192
  it('should validate number type', () => {
166
193
  expect(validateType('x', 3.14, { type: 'number' })).toBeNull();
167
194
  expect(validateType('x', 42, { type: 'number' })).toBeNull();
168
- expect(validateType('x', 'foo', { type: 'number' })).toContain('must be a number');
195
+ expect(validateType('x', 'foo', { type: 'number' })).toContain(
196
+ 'must be a number'
197
+ );
169
198
  });
170
199
 
171
200
  it('should validate string type', () => {
172
201
  expect(validateType('x', 'hello', { type: 'string' })).toBeNull();
173
- expect(validateType('x', 123, { type: 'string' })).toContain('must be a string');
202
+ expect(validateType('x', 123, { type: 'string' })).toContain(
203
+ 'must be a string'
204
+ );
174
205
  });
175
206
 
176
207
  it('should validate boolean type', () => {
177
208
  expect(validateType('x', true, { type: 'boolean' })).toBeNull();
178
- expect(validateType('x', 'true', { type: 'boolean' })).toContain('must be a boolean');
209
+ expect(validateType('x', 'true', { type: 'boolean' })).toContain(
210
+ 'must be a boolean'
211
+ );
179
212
  });
180
213
 
181
214
  it('should validate array type', () => {
182
215
  expect(validateType('x', [1, 2], { type: 'array' })).toBeNull();
183
- expect(validateType('x', 'not-array', { type: 'array' })).toContain('must be an array');
216
+ expect(validateType('x', 'not-array', { type: 'array' })).toContain(
217
+ 'must be an array'
218
+ );
184
219
  });
185
220
 
186
221
  it('should validate object type', () => {
187
222
  expect(validateType('x', { a: 1 }, { type: 'object' })).toBeNull();
188
- expect(validateType('x', [1, 2], { type: 'object' })).toContain('must be an object');
189
- expect(validateType('x', 'string', { type: 'object' })).toContain('must be an object');
223
+ expect(validateType('x', [1, 2], { type: 'object' })).toContain(
224
+ 'must be an object'
225
+ );
226
+ expect(validateType('x', 'string', { type: 'object' })).toContain(
227
+ 'must be an object'
228
+ );
190
229
  });
191
230
 
192
231
  it('should return null when no type specified', () => {
@@ -34,7 +34,9 @@ class AdminScriptContext {
34
34
 
35
35
  get integrationRepository() {
36
36
  if (!this._integrationRepository) {
37
- const { createIntegrationRepository } = require('@friggframework/core/integrations/repositories/integration-repository-factory');
37
+ const {
38
+ createIntegrationRepository,
39
+ } = require('@friggframework/core/integrations/repositories/integration-repository-factory');
38
40
  this._integrationRepository = createIntegrationRepository();
39
41
  }
40
42
  return this._integrationRepository;
@@ -42,7 +44,9 @@ class AdminScriptContext {
42
44
 
43
45
  get userRepository() {
44
46
  if (!this._userRepository) {
45
- const { createUserRepository } = require('@friggframework/core/user/repositories/user-repository-factory');
47
+ const {
48
+ createUserRepository,
49
+ } = require('@friggframework/core/user/repositories/user-repository-factory');
46
50
  this._userRepository = createUserRepository();
47
51
  }
48
52
  return this._userRepository;
@@ -50,7 +54,9 @@ class AdminScriptContext {
50
54
 
51
55
  get moduleRepository() {
52
56
  if (!this._moduleRepository) {
53
- const { createModuleRepository } = require('@friggframework/core/modules/repositories/module-repository-factory');
57
+ const {
58
+ createModuleRepository,
59
+ } = require('@friggframework/core/modules/repositories/module-repository-factory');
54
60
  this._moduleRepository = createModuleRepository();
55
61
  }
56
62
  return this._moduleRepository;
@@ -58,7 +64,9 @@ class AdminScriptContext {
58
64
 
59
65
  get credentialRepository() {
60
66
  if (!this._credentialRepository) {
61
- const { createCredentialRepository } = require('@friggframework/core/credential/repositories/credential-repository-factory');
67
+ const {
68
+ createCredentialRepository,
69
+ } = require('@friggframework/core/credential/repositories/credential-repository-factory');
62
70
  this._credentialRepository = createCredentialRepository();
63
71
  }
64
72
  return this._credentialRepository;
@@ -74,12 +82,12 @@ class AdminScriptContext {
74
82
  if (!this.integrationFactory) {
75
83
  throw new Error(
76
84
  'instantiate() requires integrationFactory. ' +
77
- 'Set Definition.config.requireIntegrationInstance = true'
85
+ 'Set Definition.config.requireIntegrationInstance = true'
78
86
  );
79
87
  }
80
88
  return this.integrationFactory.getInstanceFromIntegrationId({
81
89
  integrationId,
82
- _isAdminContext: true, // Bypass user ownership check
90
+ _isAdminContext: true, // Bypass user ownership check
83
91
  });
84
92
  }
85
93
 
@@ -88,7 +96,9 @@ class AdminScriptContext {
88
96
  async queueScript(scriptName, params = {}) {
89
97
  const queueUrl = process.env.ADMIN_SCRIPT_QUEUE_URL;
90
98
  if (!queueUrl) {
91
- throw new Error('ADMIN_SCRIPT_QUEUE_URL environment variable not set');
99
+ throw new Error(
100
+ 'ADMIN_SCRIPT_QUEUE_URL environment variable not set'
101
+ );
92
102
  }
93
103
 
94
104
  await QueuerUtil.send(
@@ -107,10 +117,12 @@ class AdminScriptContext {
107
117
  async queueScriptBatch(entries) {
108
118
  const queueUrl = process.env.ADMIN_SCRIPT_QUEUE_URL;
109
119
  if (!queueUrl) {
110
- throw new Error('ADMIN_SCRIPT_QUEUE_URL environment variable not set');
120
+ throw new Error(
121
+ 'ADMIN_SCRIPT_QUEUE_URL environment variable not set'
122
+ );
111
123
  }
112
124
 
113
- const messages = entries.map(entry => ({
125
+ const messages = entries.map((entry) => ({
114
126
  scriptName: entry.scriptName,
115
127
  trigger: 'QUEUE',
116
128
  params: entry.params || {},
@@ -15,7 +15,6 @@ class AdminScriptBase {
15
15
 
16
16
  config: {
17
17
  timeout: 300000,
18
- maxRetries: 0,
19
18
  requireIntegrationInstance: false,
20
19
  },
21
20
 
@@ -32,7 +31,9 @@ class AdminScriptBase {
32
31
  }
33
32
 
34
33
  async execute(params) {
35
- throw new Error('AdminScriptBase.execute() must be implemented by subclass');
34
+ throw new Error(
35
+ 'AdminScriptBase.execute() must be implemented by subclass'
36
+ );
36
37
  }
37
38
  }
38
39
 
@@ -26,7 +26,9 @@ class ScriptFactory {
26
26
  */
27
27
  register(ScriptClass) {
28
28
  if (!ScriptClass || !ScriptClass.Definition) {
29
- throw new Error('Script class must have a static Definition property');
29
+ throw new Error(
30
+ 'Script class must have a static Definition property'
31
+ );
30
32
  }
31
33
 
32
34
  const definition = ScriptClass.Definition;