@dbos-inc/koa-serve 3.0.27-preview → 3.0.35-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.0.27-preview",
3
+ "version": "3.0.35-preview",
4
4
  "description": "DBOS HTTP Package for serving workflows with Koa",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -30,7 +30,8 @@
30
30
  "typescript": "^5.3.3"
31
31
  },
32
32
  "peerDependencies": {
33
- "@dbos-inc/dbos-sdk": "*"
33
+ "@dbos-inc/dbos-sdk": "*",
34
+ "@dbos-inc/knex-datasource": "*"
34
35
  },
35
36
  "dependencies": {
36
37
  "@koa/bodyparser": "5.0.0",
package/src/dboshttp.ts CHANGED
@@ -150,7 +150,7 @@ export class DBOSHTTPBase implements DBOSLifecycleCallback {
150
150
 
151
151
  /** Parameter decorator indicating which source to use (URL, BODY, etc) for arg data */
152
152
  static argSource(source: ArgSources) {
153
- return function (target: object, propertyKey: string | symbol, parameterIndex: number) {
153
+ return function (target: object, propertyKey: PropertyKey, parameterIndex: number) {
154
154
  const curParam = DBOS.associateParamWithInfo(
155
155
  DBOSHTTP,
156
156
  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
@@ -190,11 +190,11 @@ export class DBOSHTTPBase implements DBOSLifecycleCallback {
190
190
  }
191
191
  }
192
192
 
193
- static argRequired(target: object, propertyKey: string | symbol, parameterIndex: number) {
193
+ static argRequired(target: object, propertyKey: PropertyKey, parameterIndex: number) {
194
194
  ArgRequired(target, propertyKey, parameterIndex);
195
195
  }
196
196
 
197
- static argOptional(target: object, propertyKey: string | symbol, parameterIndex: number) {
197
+ static argOptional(target: object, propertyKey: PropertyKey, parameterIndex: number) {
198
198
  ArgOptional(target, propertyKey, parameterIndex);
199
199
  }
200
200
 
package/src/dboskoa.ts CHANGED
@@ -72,6 +72,11 @@ function assertCurrentKoaContextStore(): DBOSKoaLocalCtx {
72
72
  }
73
73
 
74
74
  export class DBOSKoa extends DBOSHTTPBase {
75
+ constructor() {
76
+ super();
77
+ DBOS.registerLifecycleCallback(this);
78
+ }
79
+
75
80
  static get koaContext(): Koa.Context {
76
81
  return assertCurrentKoaContextStore().koaCtxt;
77
82
  }
@@ -23,7 +23,6 @@ describe('httpserver-argsource-tests', () => {
23
23
  });
24
24
 
25
25
  beforeEach(async () => {
26
- DBOS.registerLifecycleCallback(dhttp);
27
26
  const _classes = [ArgTestEndpoints];
28
27
  await DBOS.launch();
29
28
  app = new Koa();
@@ -29,7 +29,6 @@ describe('httpserver-defsec-tests', () => {
29
29
  });
30
30
 
31
31
  beforeEach(async () => {
32
- DBOS.registerLifecycleCallback(dhttp);
33
32
  const _classes = [TestEndpointDefSec, SecondClass];
34
33
  await DBOS.launch();
35
34
  await DBOS.queryUserDB(`DROP TABLE IF EXISTS ${testTableName};`);
@@ -50,7 +50,6 @@ describe('decoratorless-api-tests', () => {
50
50
 
51
51
  beforeEach(async () => {
52
52
  middlewareCounter = middlewareCounter2 = middlewareCounterG = 0;
53
- DBOS.registerLifecycleCallback(dhttp);
54
53
  await DBOS.launch();
55
54
  DBOS.logRegisteredEndpoints();
56
55
  app = new Koa();
@@ -20,7 +20,6 @@ describe('http-cors-tests', () => {
20
20
  });
21
21
 
22
22
  beforeEach(async () => {
23
- DBOS.registerLifecycleCallback(dhttp);
24
23
  await DBOS.launch();
25
24
  app = new Koa();
26
25
  appRouter = new Router();
@@ -40,7 +40,6 @@ describe('httpserver-tests', () => {
40
40
  });
41
41
 
42
42
  beforeEach(async () => {
43
- DBOS.registerLifecycleCallback(dhttp);
44
43
  const _classes = [TestEndpoints];
45
44
  await DBOS.launch();
46
45
  await DBOS.queryUserDB(`DROP TABLE IF EXISTS ${testTableName};`);
@@ -0,0 +1,79 @@
1
+ import { DBOS } from '@dbos-inc/dbos-sdk';
2
+ import Koa from 'koa';
3
+ import Router from '@koa/router';
4
+ import { DBOSKoa } from '../src';
5
+
6
+ import request from 'supertest';
7
+
8
+ const dhttp = new DBOSKoa();
9
+
10
+ function customstep() {
11
+ return function decorator<This, Args extends unknown[], Return>(
12
+ target: object,
13
+ propertyKey: PropertyKey,
14
+ descriptor: TypedPropertyDescriptor<(this: This, ...args: Args) => Promise<Return>>,
15
+ ) {
16
+ if (!descriptor.value) {
17
+ throw Error('Use of decorator when original method is undefined');
18
+ }
19
+
20
+ descriptor.value = DBOS.registerStep(descriptor.value, {
21
+ name: String(propertyKey),
22
+ ctorOrProto: target,
23
+ });
24
+
25
+ return descriptor;
26
+ };
27
+ }
28
+
29
+ describe('registerstep', () => {
30
+ let app: Koa;
31
+ let appRouter: Router;
32
+
33
+ beforeAll(async () => {});
34
+
35
+ afterAll(async () => {});
36
+
37
+ beforeEach(async () => {
38
+ DBOS.setConfig({ name: 'koa-step-test' });
39
+ await DBOS.launch();
40
+ app = new Koa();
41
+ appRouter = new Router();
42
+ dhttp.registerWithApp(app, appRouter);
43
+ });
44
+
45
+ afterEach(async () => {
46
+ await DBOS.shutdown();
47
+ });
48
+
49
+ test('use-customstep-and-koa', async () => {
50
+ const u1 = await KnexKoa.insertOneWay('joe');
51
+ expect(u1.user).toBe('joe');
52
+ const u2 = await KnexKoa.insertTheOtherWay('jack');
53
+ expect(u2.user).toBe('jack');
54
+
55
+ const response1 = await request(app.callback()).get('/api/i1?user=john');
56
+ expect(response1.statusCode).toBe(200);
57
+ const response2 = await request(app.callback()).get('/api/i2?user=jeremy');
58
+ expect(response2.statusCode).toBe(200);
59
+ const response3 = await request(app.callback()).get('/api/i1');
60
+ expect(response3.statusCode).toBe(400);
61
+ const response4 = await request(app.callback()).get('/api/i2');
62
+ expect(response4.statusCode).toBe(400);
63
+ });
64
+ });
65
+
66
+ @DBOSKoa.defaultArgValidate
67
+ class KnexKoa {
68
+ @customstep()
69
+ @dhttp.getApi('/api/i2')
70
+ static async insertTheOtherWay(user: string) {
71
+ return Promise.resolve({ user, now: Date.now() });
72
+ }
73
+
74
+ @dhttp.getApi('/api/i1')
75
+ @customstep()
76
+ static async insertOneWay(user: string) {
77
+ return Promise.resolve({ user, now: Date.now() });
78
+ }
79
+ }
@@ -0,0 +1,13 @@
1
+ import { Client } from 'pg';
2
+
3
+ export async function ensureDB(client: Client, name: string) {
4
+ const results = await client.query('SELECT 1 FROM pg_database WHERE datname = $1', [name]);
5
+ if (results.rows.length === 0) {
6
+ await client.query(`CREATE DATABASE ${name}`);
7
+ }
8
+ }
9
+
10
+ export async function dropDB(client: Client, name: string, force: boolean = false) {
11
+ const withForce = force ? ' WITH (FORCE)' : '';
12
+ await client.query(`DROP DATABASE IF EXISTS ${name} ${withForce}`);
13
+ }
@@ -0,0 +1,117 @@
1
+ import { DBOS } from '@dbos-inc/dbos-sdk';
2
+ import { Client, Pool } from 'pg';
3
+ import { KnexDataSource } from '@dbos-inc/knex-datasource';
4
+ import { dropDB, ensureDB } from './test-helpers';
5
+ import Koa from 'koa';
6
+ import Router from '@koa/router';
7
+ import { DBOSKoa } from '../src';
8
+
9
+ import request from 'supertest';
10
+
11
+ const config = { client: 'pg', connection: { user: 'postgres', database: 'koa_knex_ds_test_userdb' } };
12
+ const knexds = new KnexDataSource('app-db', config);
13
+
14
+ const dhttp = new DBOSKoa();
15
+
16
+ interface greetings {
17
+ name: string;
18
+ greet_count: number;
19
+ }
20
+
21
+ describe('KnexDataSource', () => {
22
+ const userDB = new Pool(config.connection);
23
+ let app: Koa;
24
+ let appRouter: Router;
25
+
26
+ beforeAll(async () => {
27
+ {
28
+ const client = new Client({ ...config.connection, database: 'postgres' });
29
+ try {
30
+ await client.connect();
31
+ await dropDB(client, 'koa_knex_ds_test', true);
32
+ await dropDB(client, 'koa_knex_ds_test_dbos_sys', true);
33
+ await dropDB(client, config.connection.database, true);
34
+ await ensureDB(client, config.connection.database);
35
+ } finally {
36
+ await client.end();
37
+ }
38
+ }
39
+
40
+ {
41
+ const client = await userDB.connect();
42
+ try {
43
+ await client.query(
44
+ 'CREATE TABLE greetings(name text NOT NULL, greet_count integer DEFAULT 0, PRIMARY KEY(name))',
45
+ );
46
+ } finally {
47
+ client.release();
48
+ }
49
+ }
50
+
51
+ await KnexDataSource.initializeDBOSSchema(config);
52
+ });
53
+
54
+ afterAll(async () => {
55
+ await userDB.end();
56
+ });
57
+
58
+ beforeEach(async () => {
59
+ DBOS.setConfig({ name: 'koa-knex-ds-test' });
60
+ await DBOS.launch();
61
+ app = new Koa();
62
+ appRouter = new Router();
63
+ dhttp.registerWithApp(app, appRouter);
64
+ });
65
+
66
+ afterEach(async () => {
67
+ await DBOS.shutdown();
68
+ });
69
+
70
+ test('use-dstx-and-koa', async () => {
71
+ const u1 = await KnexKoa.insertOneWay('joe');
72
+ expect(u1.user).toBe('joe');
73
+ const u2 = await KnexKoa.insertTheOtherWay('jack');
74
+ expect(u2.user).toBe('jack');
75
+
76
+ const response1 = await request(app.callback()).get('/api/i1?user=john');
77
+ expect(response1.statusCode).toBe(200);
78
+ const response2 = await request(app.callback()).get('/api/i2?user=jeremy');
79
+ expect(response2.statusCode).toBe(200);
80
+ const response3 = await request(app.callback()).get('/api/i1');
81
+ expect(response3.statusCode).toBe(400);
82
+ const response4 = await request(app.callback()).get('/api/i2');
83
+ expect(response4.statusCode).toBe(400);
84
+ });
85
+ });
86
+
87
+ @DBOSKoa.defaultArgValidate
88
+ class KnexKoa {
89
+ @knexds.transaction()
90
+ @dhttp.getApi('/api/i2')
91
+ static async insertTheOtherWay(user: string) {
92
+ const rows = await knexds
93
+ .client<greetings>('greetings')
94
+ .insert({ name: user, greet_count: 1 })
95
+ .onConflict('name')
96
+ .merge({ greet_count: knexds.client.raw('greetings.greet_count + 1') })
97
+ .returning('greet_count');
98
+
99
+ const row = rows.length > 0 ? rows[0] : undefined;
100
+
101
+ return { user, greet_count: row?.greet_count, now: Date.now() };
102
+ }
103
+
104
+ @dhttp.getApi('/api/i1')
105
+ @knexds.transaction()
106
+ static async insertOneWay(user: string) {
107
+ const rows = await knexds
108
+ .client<greetings>('greetings')
109
+ .insert({ name: user, greet_count: 1 })
110
+ .onConflict('name')
111
+ .merge({ greet_count: knexds.client.raw('greetings.greet_count + 1') })
112
+ .returning('greet_count');
113
+ const row = rows.length > 0 ? rows[0] : undefined;
114
+
115
+ return { user, greet_count: row?.greet_count, now: Date.now() };
116
+ }
117
+ }