@dbos-inc/koa-serve 3.6.3-preview → 3.6.7-preview

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dbos-inc/koa-serve",
3
- "version": "3.6.3-preview",
3
+ "version": "3.6.7-preview",
4
4
  "description": "DBOS HTTP Package for serving workflows with Koa",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -31,7 +31,8 @@
31
31
  },
32
32
  "peerDependencies": {
33
33
  "@dbos-inc/dbos-sdk": "*",
34
- "@dbos-inc/knex-datasource": "*"
34
+ "@dbos-inc/knex-datasource": "*",
35
+ "reflect-metadata": "^0.1.14 || ^0.2.0"
35
36
  },
36
37
  "dependencies": {
37
38
  "@koa/bodyparser": "5.0.0",
package/src/dboshttp.ts CHANGED
@@ -1,22 +1,470 @@
1
+ import 'reflect-metadata';
1
2
  import { IncomingHttpHeaders } from 'http';
2
3
  import { ParsedUrlQuery } from 'querystring';
3
4
  import { randomUUID } from 'node:crypto';
5
+ import { DBOSMethodMiddlewareInstaller, MethodRegistrationBase } from '@dbos-inc/dbos-sdk';
6
+ import crypto from 'node:crypto';
4
7
 
5
8
  import {
9
+ ArgDataType,
6
10
  DBOS,
11
+ DBOSDataType,
7
12
  DBOSLifecycleCallback,
8
13
  Error as DBOSErrors,
9
14
  MethodParameter,
10
- requestArgValidation,
11
- ArgRequired,
12
- ArgOptional,
13
- DefaultArgRequired,
14
- DefaultArgValidate,
15
- DefaultArgOptional,
16
- ArgDate,
17
- ArgVarchar,
18
15
  } from '@dbos-inc/dbos-sdk';
19
16
 
17
+ const VALIDATOR = 'validator';
18
+
19
+ export enum ArgRequiredOptions {
20
+ REQUIRED = 'REQUIRED',
21
+ OPTIONAL = 'OPTIONAL',
22
+ DEFAULT = 'DEFAULT',
23
+ }
24
+
25
+ interface ValidatorClassInfo {
26
+ defaultArgRequired?: ArgRequiredOptions;
27
+ defaultArgValidate?: boolean;
28
+ }
29
+
30
+ interface ValidatorFuncInfo {
31
+ performArgValidation?: boolean;
32
+ }
33
+
34
+ interface ValidatorArgInfo {
35
+ required?: ArgRequiredOptions;
36
+ }
37
+
38
+ function getValidatorClassInfo(methReg: MethodRegistrationBase) {
39
+ const valInfo = methReg.defaults?.getRegisteredInfo(VALIDATOR) as ValidatorClassInfo;
40
+ return {
41
+ defaultArgRequired: valInfo?.defaultArgRequired ?? ArgRequiredOptions.DEFAULT,
42
+ defaultArgValidate: valInfo?.defaultArgValidate ?? false,
43
+ } satisfies ValidatorClassInfo;
44
+ }
45
+
46
+ export function requestArgValidation(methReg: MethodRegistrationBase) {
47
+ (methReg.getRegisteredInfo(VALIDATOR) as ValidatorFuncInfo).performArgValidation = true;
48
+ }
49
+
50
+ function getValidatorFuncInfo(methReg: MethodRegistrationBase) {
51
+ const valInfo = methReg.getRegisteredInfo(VALIDATOR) as ValidatorFuncInfo;
52
+ return {
53
+ performArgValidation: valInfo.performArgValidation ?? false,
54
+ } satisfies ValidatorFuncInfo;
55
+ }
56
+
57
+ function getValidatorArgInfo(param: MethodParameter) {
58
+ const valInfo = param.getRegisteredInfo(VALIDATOR) as ValidatorArgInfo;
59
+ return {
60
+ required: valInfo.required ?? ArgRequiredOptions.DEFAULT,
61
+ };
62
+ }
63
+
64
+ class ValidationMiddleware implements DBOSMethodMiddlewareInstaller {
65
+ installMiddleware(methReg: MethodRegistrationBase): void {
66
+ const valInfo = getValidatorClassInfo(methReg);
67
+ const defaultArgRequired = valInfo.defaultArgRequired;
68
+ const defaultArgValidate = valInfo.defaultArgValidate;
69
+
70
+ let shouldValidate =
71
+ getValidatorFuncInfo(methReg).performArgValidation ||
72
+ defaultArgRequired === ArgRequiredOptions.REQUIRED ||
73
+ defaultArgValidate;
74
+
75
+ for (const a of methReg.args) {
76
+ if (getValidatorArgInfo(a).required === ArgRequiredOptions.REQUIRED) {
77
+ shouldValidate = true;
78
+ }
79
+ }
80
+
81
+ if (shouldValidate) {
82
+ requestArgValidation(methReg);
83
+ methReg.addEntryInterceptor(validateMethodArgs, 20);
84
+ }
85
+ }
86
+ }
87
+
88
+ const validationMiddleware = new ValidationMiddleware();
89
+
90
+ function validateMethodArgs<Args extends unknown[]>(methReg: MethodRegistrationBase, args: Args) {
91
+ const validationError = (msg: string) => {
92
+ const err = new DBOSErrors.DBOSDataValidationError(msg);
93
+ DBOS.span?.addEvent('DataValidationError', { message: err.message });
94
+ return err;
95
+ };
96
+
97
+ // Input validation
98
+ methReg.args.forEach((argDescriptor, idx) => {
99
+ let argValue = args[idx];
100
+
101
+ // So... there is such a thing as "undefined", and another thing called "null"
102
+ // We will fold this to "undefined" for our APIs. It's just a rule of ours.
103
+ if (argValue === null) {
104
+ argValue = undefined;
105
+ args[idx] = undefined;
106
+ }
107
+
108
+ if (argValue === undefined) {
109
+ const valInfo = getValidatorClassInfo(methReg);
110
+ const defaultArgRequired = valInfo.defaultArgRequired;
111
+ const defaultArgValidate = valInfo.defaultArgValidate;
112
+ const argRequired = getValidatorArgInfo(argDescriptor).required;
113
+ if (
114
+ argRequired === ArgRequiredOptions.REQUIRED ||
115
+ (argRequired === ArgRequiredOptions.DEFAULT &&
116
+ (defaultArgRequired === ArgRequiredOptions.REQUIRED || defaultArgValidate))
117
+ ) {
118
+ if (idx >= args.length) {
119
+ throw validationError(
120
+ `Insufficient number of arguments calling ${methReg.name} - ${args.length}/${methReg.args.length}`,
121
+ );
122
+ } else {
123
+ throw validationError(`Missing required argument ${argDescriptor.name} of ${methReg.name}`);
124
+ }
125
+ }
126
+ }
127
+
128
+ if (argValue === undefined) {
129
+ return;
130
+ }
131
+
132
+ if (argValue instanceof String) {
133
+ argValue = argValue.toString();
134
+ args[idx] = argValue;
135
+ }
136
+ if (argValue instanceof Boolean) {
137
+ argValue = argValue.valueOf();
138
+ args[idx] = argValue;
139
+ }
140
+ if (argValue instanceof Number) {
141
+ argValue = argValue.valueOf();
142
+ args[idx] = argValue;
143
+ }
144
+ if (argValue instanceof BigInt) {
145
+ // ES2020+
146
+ argValue = argValue.valueOf();
147
+ args[idx] = argValue;
148
+ }
149
+
150
+ // Argument validation - below - if we have any info about it
151
+ if (!argDescriptor.dataType) return;
152
+
153
+ // Maybe look into https://www.npmjs.com/package/validator
154
+ // We could support emails and other validations too with something like that...
155
+ if (argDescriptor.dataType.dataType === 'text' || argDescriptor.dataType.dataType === 'varchar') {
156
+ if (typeof argValue !== 'string') {
157
+ throw validationError(
158
+ `Argument ${argDescriptor.name} of ${methReg.name} is marked as type '${argDescriptor.dataType.dataType}' and should be a string`,
159
+ );
160
+ }
161
+ if (argDescriptor.dataType.length > 0) {
162
+ if (argValue.length > argDescriptor.dataType.length) {
163
+ throw validationError(
164
+ `Argument ${argDescriptor.name} of ${methReg.name} is marked as type '${argDescriptor.dataType.dataType}' with maximum length ${argDescriptor.dataType.length} but has length ${argValue.length}`,
165
+ );
166
+ }
167
+ }
168
+ }
169
+ if (argDescriptor.dataType.dataType === 'boolean') {
170
+ if (typeof argValue !== 'boolean') {
171
+ if (typeof argValue === 'number') {
172
+ if (argValue === 0 || argValue === 1) {
173
+ argValue = argValue !== 0 ? true : false;
174
+ args[idx] = argValue;
175
+ } else {
176
+ throw validationError(
177
+ `Argument ${argDescriptor.name} of ${methReg.name} is marked as type '${argDescriptor.dataType.dataType}' and may be a number (0 or 1) convertible to boolean, but was ${argValue}.`,
178
+ );
179
+ }
180
+ } else if (typeof argValue === 'string') {
181
+ if (argValue.toLowerCase() === 't' || argValue.toLowerCase() === 'true' || argValue === '1') {
182
+ argValue = true;
183
+ args[idx] = argValue;
184
+ } else if (argValue.toLowerCase() === 'f' || argValue.toLowerCase() === 'false' || argValue === '0') {
185
+ argValue = false;
186
+ args[idx] = argValue;
187
+ } else {
188
+ throw validationError(
189
+ `Argument ${argDescriptor.name} of ${methReg.name} is marked as type '${argDescriptor.dataType.dataType}' and may be a string convertible to boolean, but was ${argValue}.`,
190
+ );
191
+ }
192
+ } else {
193
+ throw validationError(
194
+ `Argument ${argDescriptor.name} of ${methReg.name} is marked as type '${argDescriptor.dataType.dataType}' and should be a boolean`,
195
+ );
196
+ }
197
+ }
198
+ }
199
+ if (argDescriptor.dataType.dataType === 'decimal') {
200
+ // Range check precision and scale... wishing there was a bigdecimal
201
+ // Floats don't really permit us to check the scale.
202
+ if (typeof argValue !== 'number') {
203
+ throw validationError(
204
+ `Argument ${argDescriptor.name} of ${methReg.name} is marked as type '${argDescriptor.dataType.dataType}' and should be a number`,
205
+ );
206
+ }
207
+ let prec = argDescriptor.dataType.precision;
208
+ if (prec > 0) {
209
+ if (argDescriptor.dataType.scale > 0) {
210
+ prec = prec - argDescriptor.dataType.scale;
211
+ }
212
+ if (Math.abs(argValue) >= Math.exp(prec)) {
213
+ throw validationError(
214
+ `Argument ${argDescriptor.name} of ${methReg.name} is out of range for type '${argDescriptor.dataType.formatAsString()}`,
215
+ );
216
+ }
217
+ }
218
+ }
219
+ if (argDescriptor.dataType.dataType === 'double' || argDescriptor.dataType.dataType === 'integer') {
220
+ if (typeof argValue !== 'number') {
221
+ if (typeof argValue === 'string') {
222
+ const n = parseFloat(argValue);
223
+ if (isNaN(n)) {
224
+ throw validationError(
225
+ `Argument ${argDescriptor.name} of ${methReg.name} is marked as type '${argDescriptor.dataType.dataType}' and should be a number`,
226
+ );
227
+ }
228
+ argValue = n;
229
+ args[idx] = argValue;
230
+ } else if (typeof argValue === 'bigint') {
231
+ // Hum, maybe we should allow bigint as a type, number won't even do 64-bit.
232
+ argValue = Number(argValue).valueOf();
233
+ args[idx] = argValue;
234
+ } else {
235
+ throw validationError(
236
+ `Argument ${argDescriptor.name} of ${methReg.name} is marked as type '${argDescriptor.dataType.dataType}' and should be a number`,
237
+ );
238
+ }
239
+ }
240
+ if (argDescriptor.dataType.dataType === 'integer') {
241
+ if (!Number.isInteger(argValue)) {
242
+ throw validationError(
243
+ `Argument ${argDescriptor.name} of ${methReg.name} is marked as type '${argDescriptor.dataType.dataType}' but has a fractional part`,
244
+ );
245
+ }
246
+ }
247
+ }
248
+ if (argDescriptor.dataType.dataType === 'timestamp') {
249
+ if (!(argValue instanceof Date)) {
250
+ if (typeof argValue === 'string') {
251
+ const d = Date.parse(argValue);
252
+ if (isNaN(d)) {
253
+ throw validationError(
254
+ `Argument ${argDescriptor.name} of ${methReg.name} is marked as type '${argDescriptor.dataType.dataType}' but is a string that will not parse as Date`,
255
+ );
256
+ }
257
+ argValue = new Date(d);
258
+ args[idx] = argValue;
259
+ } else {
260
+ throw validationError(
261
+ `Argument ${argDescriptor.name} of ${methReg.name} is marked as type '${argDescriptor.dataType.dataType}' but is not a date or time`,
262
+ );
263
+ }
264
+ }
265
+ }
266
+ if (argDescriptor.dataType.dataType === 'uuid') {
267
+ // This validation is loose. A tighter one would be:
268
+ // /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/
269
+ // That matches UUID version 1-5.
270
+ if (!/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(String(argValue))) {
271
+ throw validationError(
272
+ `Argument ${argDescriptor.name} of ${methReg.name} is marked as type '${argDescriptor.dataType.dataType}' but is not a valid UUID`,
273
+ );
274
+ }
275
+ }
276
+ // JSON can be anything. We can validate it against a schema at some later version...
277
+ });
278
+ return args;
279
+ }
280
+
281
+ export function ArgRequired(target: object, propertyKey: PropertyKey, param: number) {
282
+ const curParam = DBOS.associateParamWithInfo(
283
+ VALIDATOR,
284
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
285
+ Object.getOwnPropertyDescriptor(target, propertyKey)!.value,
286
+ {
287
+ ctorOrProto: target,
288
+ name: propertyKey.toString(),
289
+ param,
290
+ },
291
+ ) as ValidatorArgInfo;
292
+
293
+ curParam.required = ArgRequiredOptions.REQUIRED;
294
+
295
+ DBOS.registerMiddlewareInstaller(validationMiddleware);
296
+ }
297
+
298
+ export function ArgOptional(target: object, propertyKey: PropertyKey, param: number) {
299
+ const curParam = DBOS.associateParamWithInfo(
300
+ VALIDATOR,
301
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
302
+ Object.getOwnPropertyDescriptor(target, propertyKey)!.value,
303
+ {
304
+ ctorOrProto: target,
305
+ name: propertyKey.toString(),
306
+ param,
307
+ },
308
+ ) as ValidatorArgInfo;
309
+
310
+ curParam.required = ArgRequiredOptions.OPTIONAL;
311
+
312
+ DBOS.registerMiddlewareInstaller(validationMiddleware);
313
+ }
314
+
315
+ export function ArgDate() {
316
+ // TODO a little more info about it - is it a date or timestamp precision?
317
+ return function (target: object, propertyKey: PropertyKey, param: number) {
318
+ const curParam = DBOS.associateParamWithInfo(
319
+ 'type',
320
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
321
+ Object.getOwnPropertyDescriptor(target, propertyKey)!.value,
322
+ {
323
+ ctorOrProto: target,
324
+ name: propertyKey.toString(),
325
+ param,
326
+ },
327
+ ) as ArgDataType;
328
+
329
+ if (!curParam.dataType) curParam.dataType = new DBOSDataType();
330
+ curParam.dataType.dataType = 'timestamp';
331
+
332
+ DBOS.registerMiddlewareInstaller(validationMiddleware);
333
+ };
334
+ }
335
+
336
+ export function ArgVarchar(length: number) {
337
+ return function (target: object, propertyKey: PropertyKey, param: number) {
338
+ const curParam = DBOS.associateParamWithInfo(
339
+ 'type',
340
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
341
+ Object.getOwnPropertyDescriptor(target, propertyKey)!.value,
342
+ {
343
+ ctorOrProto: target,
344
+ name: propertyKey.toString(),
345
+ param,
346
+ },
347
+ ) as ArgDataType;
348
+
349
+ curParam.dataType = DBOSDataType.varchar(length);
350
+
351
+ DBOS.registerMiddlewareInstaller(validationMiddleware);
352
+ };
353
+ }
354
+
355
+ export function DefaultArgRequired<T extends { new (...args: unknown[]): object }>(ctor: T) {
356
+ const clsreg = DBOS.associateClassWithInfo(VALIDATOR, ctor) as ValidatorClassInfo;
357
+ clsreg.defaultArgRequired = ArgRequiredOptions.REQUIRED;
358
+
359
+ DBOS.registerMiddlewareInstaller(validationMiddleware);
360
+ }
361
+
362
+ export function DefaultArgValidate<T extends { new (...args: unknown[]): object }>(ctor: T) {
363
+ const clsreg = DBOS.associateClassWithInfo(VALIDATOR, ctor) as ValidatorClassInfo;
364
+ clsreg.defaultArgValidate = true;
365
+
366
+ DBOS.registerMiddlewareInstaller(validationMiddleware);
367
+ }
368
+
369
+ export function DefaultArgOptional<T extends { new (...args: unknown[]): object }>(ctor: T) {
370
+ const clsreg = DBOS.associateClassWithInfo(VALIDATOR, ctor) as ValidatorClassInfo;
371
+ clsreg.defaultArgRequired = ArgRequiredOptions.OPTIONAL;
372
+
373
+ DBOS.registerMiddlewareInstaller(validationMiddleware);
374
+ }
375
+
376
+ export enum LogMasks {
377
+ NONE = 'NONE',
378
+ HASH = 'HASH',
379
+ SKIP = 'SKIP',
380
+ }
381
+
382
+ interface LoggerArgInfo {
383
+ logMask?: LogMasks;
384
+ }
385
+
386
+ export const LOGGER = 'log';
387
+
388
+ export function SkipLogging(target: object, propertyKey: PropertyKey, param: number) {
389
+ const curParam = DBOS.associateParamWithInfo(
390
+ LOGGER,
391
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
392
+ Object.getOwnPropertyDescriptor(target, propertyKey)!.value,
393
+ {
394
+ ctorOrProto: target,
395
+ name: propertyKey.toString(),
396
+ param,
397
+ },
398
+ ) as LoggerArgInfo;
399
+
400
+ curParam.logMask = LogMasks.SKIP;
401
+ }
402
+
403
+ export function LogMask(mask: LogMasks) {
404
+ return function (target: object, propertyKey: PropertyKey, param: number) {
405
+ const curParam = DBOS.associateParamWithInfo(
406
+ LOGGER,
407
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
408
+ Object.getOwnPropertyDescriptor(target, propertyKey)!.value,
409
+ {
410
+ ctorOrProto: target,
411
+ name: propertyKey.toString(),
412
+ param,
413
+ },
414
+ ) as LoggerArgInfo;
415
+
416
+ curParam.logMask = mask;
417
+ };
418
+ }
419
+
420
+ function generateSaltedHash(data: string, salt: string): string {
421
+ const hash = crypto.createHash('sha256'); // You can use other algorithms like 'md5', 'sha512', etc.
422
+ hash.update(data + salt);
423
+ return hash.digest('hex');
424
+ }
425
+
426
+ function getLoggerArgInfo(param: MethodParameter) {
427
+ const valInfo = param.getRegisteredInfo(LOGGER) as LoggerArgInfo;
428
+ return {
429
+ logMask: valInfo.logMask ?? LogMasks.NONE,
430
+ };
431
+ }
432
+
433
+ class LoggingMiddleware implements DBOSMethodMiddlewareInstaller {
434
+ installMiddleware(methReg: MethodRegistrationBase): void {
435
+ methReg.addEntryInterceptor(logMethodArgs, 30);
436
+ }
437
+ }
438
+
439
+ const logMiddleware = new LoggingMiddleware();
440
+ DBOS.registerMiddlewareInstaller(logMiddleware);
441
+
442
+ export function logMethodArgs<Args extends unknown[]>(methReg: MethodRegistrationBase, args: Args) {
443
+ // Argument logging
444
+ args.forEach((argValue, idx) => {
445
+ let loggedArgValue = argValue;
446
+ const logMask = getLoggerArgInfo(methReg.args[idx]).logMask;
447
+
448
+ if (logMask === LogMasks.SKIP) {
449
+ return;
450
+ } else {
451
+ if (logMask !== LogMasks.NONE) {
452
+ // For now this means hash
453
+ if (methReg.args[idx].dataType?.dataType === 'json') {
454
+ loggedArgValue = generateSaltedHash(JSON.stringify(argValue), 'JSONSALT');
455
+ } else {
456
+ // Yes, we are doing the same as above for now.
457
+ // It can be better if we have verified the type of the data
458
+ loggedArgValue = generateSaltedHash(JSON.stringify(argValue), 'DBOSSALT');
459
+ }
460
+ }
461
+ DBOS.span?.setAttribute(methReg.args[idx].name, loggedArgValue as string);
462
+ }
463
+ });
464
+
465
+ return args;
466
+ }
467
+
20
468
  export enum APITypes {
21
469
  GET = 'GET',
22
470
  POST = 'POST',
@@ -50,6 +498,20 @@ export interface DBOSHTTPArgInfo {
50
498
  argSource?: ArgSources;
51
499
  }
52
500
 
501
+ /**
502
+ * This error can be thrown by DBOS applications to indicate
503
+ * the HTTP response code, in addition to the message.
504
+ * Note that any error with a 'status' field can be used.
505
+ */
506
+ export class DBOSResponseError extends Error {
507
+ constructor(
508
+ msg: string,
509
+ readonly status: number = 500,
510
+ ) {
511
+ super(msg);
512
+ }
513
+ }
514
+
53
515
  /**
54
516
  * HTTPRequest includes useful information from http.IncomingMessage and parsed body,
55
517
  * URL parameters, and parsed query string.
@@ -150,7 +612,7 @@ export class DBOSHTTPBase implements DBOSLifecycleCallback {
150
612
 
151
613
  /** Parameter decorator indicating which source to use (URL, BODY, etc) for arg data */
152
614
  static argSource(source: ArgSources) {
153
- return function (target: object, propertyKey: PropertyKey, parameterIndex: number) {
615
+ return function (target: object, propertyKey: PropertyKey, param: number) {
154
616
  const curParam = DBOS.associateParamWithInfo(
155
617
  DBOSHTTP,
156
618
  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
@@ -158,7 +620,7 @@ export class DBOSHTTPBase implements DBOSLifecycleCallback {
158
620
  {
159
621
  ctorOrProto: target,
160
622
  name: propertyKey.toString(),
161
- param: parameterIndex,
623
+ param,
162
624
  },
163
625
  ) as DBOSHTTPArgInfo;
164
626
 
package/src/dboskoa.ts CHANGED
@@ -344,7 +344,7 @@ export class DBOSKoa extends DBOSHTTPBase {
344
344
  } catch (e) {
345
345
  if (e instanceof Error) {
346
346
  span?.setStatus({ code: SpanStatusCode.ERROR, message: e.message });
347
- let st = (e as DBOSErrors.DBOSResponseError)?.status || 500;
347
+ let st = (e as { status?: number })?.status || 500;
348
348
  if (isClientRequestError(e)) {
349
349
  st = 400; // Set to 400: client-side error.
350
350
  }
package/src/index.ts CHANGED
@@ -9,6 +9,7 @@ export {
9
9
  DBOSHTTPMethodInfo,
10
10
  DBOSHTTPReg,
11
11
  DBOSHTTPRequest,
12
+ DBOSResponseError,
12
13
  RequestIDHeader,
13
14
  WorkflowIDHeader,
14
15
  } from './dboshttp';
@@ -17,7 +17,6 @@ describe('httpserver-argsource-tests', () => {
17
17
  beforeAll(async () => {
18
18
  DBOS.setConfig({
19
19
  name: 'dbos-koa-test',
20
- userDatabaseClient: 'pg-node',
21
20
  });
22
21
  return Promise.resolve();
23
22
  });
@@ -9,21 +9,13 @@ import request from 'supertest';
9
9
 
10
10
  const dhttp = new DBOSKoa();
11
11
 
12
- interface TestKvTable {
13
- id?: number;
14
- value?: string;
15
- }
16
-
17
12
  describe('httpserver-defsec-tests', () => {
18
13
  let app: Koa;
19
14
  let appRouter: Router;
20
15
 
21
- const testTableName = 'dbos_test_kv';
22
-
23
16
  beforeAll(async () => {
24
17
  DBOS.setConfig({
25
18
  name: 'dbos-koa-test',
26
- userDatabaseClient: 'pg-node',
27
19
  });
28
20
  return Promise.resolve();
29
21
  });
@@ -31,8 +23,6 @@ describe('httpserver-defsec-tests', () => {
31
23
  beforeEach(async () => {
32
24
  const _classes = [TestEndpointDefSec, SecondClass];
33
25
  await DBOS.launch();
34
- await DBOS.queryUserDB(`DROP TABLE IF EXISTS ${testTableName};`);
35
- await DBOS.queryUserDB(`CREATE TABLE IF NOT EXISTS ${testTableName} (id SERIAL PRIMARY KEY, value TEXT);`);
36
26
  middlewareCounter = 0;
37
27
  middlewareCounter2 = 0;
38
28
  middlewareCounterG = 0;
@@ -101,13 +91,13 @@ describe('httpserver-defsec-tests', () => {
101
91
  // We can directly test a transaction with passed in authorizedRoles.
102
92
  test('direct-transaction-test', async () => {
103
93
  await DBOS.withAuthedContext('user', ['user'], async () => {
104
- const res = await TestEndpointDefSec.testTranscation('alice');
94
+ const res = await TestEndpointDefSec.testStep('alice');
105
95
  expect(res).toBe('hello 1');
106
96
  });
107
97
 
108
98
  // Unauthorized.
109
- await expect(TestEndpointDefSec.testTranscation('alice')).rejects.toThrow(
110
- new DBOSError.DBOSNotAuthorizedError('User does not have a role with permission to call testTranscation', 403),
99
+ await expect(TestEndpointDefSec.testStep('alice')).rejects.toThrow(
100
+ new DBOSError.DBOSNotAuthorizedError('User does not have a role with permission to call testStep', 403),
111
101
  );
112
102
  });
113
103
 
@@ -171,18 +161,15 @@ describe('httpserver-defsec-tests', () => {
171
161
  return Promise.resolve(`Please say hello to ${name}`);
172
162
  }
173
163
 
174
- @DBOS.transaction()
175
- static async testTranscation(name: string) {
176
- const { rows } = await DBOS.pgClient.query<TestKvTable>(
177
- `INSERT INTO ${testTableName}(value) VALUES ($1) RETURNING id`,
178
- [name],
179
- );
180
- return `hello ${rows[0].id}`;
164
+ @DBOS.step()
165
+ static async testStep(name: string) {
166
+ void name;
167
+ return Promise.resolve(`hello 1`);
181
168
  }
182
169
 
183
170
  @DBOS.workflow()
184
171
  static async testWorkflow(name: string) {
185
- const res = await TestEndpointDefSec.testTranscation(name);
172
+ const res = await TestEndpointDefSec.testStep(name);
186
173
  return res;
187
174
  }
188
175
 
@@ -193,7 +180,7 @@ describe('httpserver-defsec-tests', () => {
193
180
 
194
181
  @dhttp.getApi('/transaction')
195
182
  static async testTxnEndpoint(name: string) {
196
- return await TestEndpointDefSec.testTranscation(name);
183
+ return await TestEndpointDefSec.testStep(name);
197
184
  }
198
185
  }
199
186