Skip to content

Commit 0ee7422

Browse files
authored
Update Workflow Delay (#1217)
Closes #1216 Also do a big cleanup of dependencies to fix all Depandabot issues.
1 parent 3f0ae0f commit 0ee7422

9 files changed

Lines changed: 834 additions & 1783 deletions

File tree

package-lock.json

Lines changed: 763 additions & 1762 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/confluent-kafka-receive/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"test": "tsx --test tests/*.test.ts"
1818
},
1919
"dependencies": {
20-
"@confluentinc/kafka-javascript": "^1.4.0"
20+
"@confluentinc/kafka-javascript": "^1.8.2"
2121
},
2222
"peerDependencies": {
2323
"@dbos-inc/dbos-sdk": "*"

packages/drizzle-datasource/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
"devDependencies": {
2727
"@types/jest": "^29.5.14",
2828
"@types/pg": "^8.15.2",
29-
"drizzle-kit": "^0.31.5",
3029
"drizzle-orm": "^0.44.5",
3130
"jest": "^29.7.0",
3231
"typescript": "^5.4.5"

packages/drizzle-datasource/tests/custom-pool.test.ts

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ import { dropDB, ensureDB } from './test-helpers';
55
import { randomUUID } from 'crypto';
66
import SuperJSON from 'superjson';
77
import { pgTable, text, integer } from 'drizzle-orm/pg-core';
8-
import { drizzle } from 'drizzle-orm/node-postgres';
9-
import { pushSchema } from 'drizzle-kit/api';
108
import { sql } from 'drizzle-orm';
119

1210
const config = { user: 'postgres', database: 'drizzle_pool_test_userdb' };
@@ -69,14 +67,9 @@ describe('DrizzleDataSource with custom Pool', () => {
6967
poolClient.release();
7068
}
7169

72-
const drizzlePool = new Pool(config);
73-
const db = drizzle(drizzlePool);
74-
try {
75-
const res = await pushSchema({ greetingsTable }, db);
76-
await res.apply();
77-
} finally {
78-
await drizzlePool.end();
79-
}
70+
await customPool.query(
71+
`CREATE TABLE IF NOT EXISTS greetings (name TEXT PRIMARY KEY NOT NULL, greet_count INTEGER DEFAULT 0)`,
72+
);
8073
});
8174

8275
afterAll(async () => {

packages/drizzle-datasource/tests/datasource.test.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ import { dropDB, ensureDB } from './test-helpers';
55
import { randomUUID } from 'crypto';
66
import SuperJSON from 'superjson';
77
import { pgTable, text, integer } from 'drizzle-orm/pg-core';
8-
import { drizzle } from 'drizzle-orm/node-postgres';
9-
import { pushSchema } from 'drizzle-kit/api';
108
import { eq, sql } from 'drizzle-orm';
119

1210
const config = { user: 'postgres', database: 'drizzle_ds_test_userdb' };
@@ -19,14 +17,14 @@ interface transaction_completion {
1917
error: string | null;
2018
}
2119

22-
async function createSchema(config: PoolConfig, entities: { [key: string]: object }) {
23-
const drizzlePool = new Pool(config);
24-
const db = drizzle(drizzlePool);
20+
async function createSchema(config: PoolConfig) {
21+
const pool = new Pool(config);
2522
try {
26-
const res = await pushSchema(entities, db);
27-
await res.apply();
23+
await pool.query(
24+
`CREATE TABLE IF NOT EXISTS greetings (name TEXT PRIMARY KEY NOT NULL, greet_count INTEGER DEFAULT 0)`,
25+
);
2826
} finally {
29-
await drizzlePool.end();
27+
await pool.end();
3028
}
3129
}
3230

@@ -344,7 +342,7 @@ async function createDatabases(createTxCompletions: boolean) {
344342
if (createTxCompletions) {
345343
await DrizzleDataSource.initializeDBOSSchema(config);
346344
}
347-
await createSchema(config, { greetingsTable });
345+
await createSchema(config);
348346
}
349347

350348
async function insertFunction(user: string) {

src/client.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,10 @@ export class DBOSClient {
399399
return this.systemDatabase.setWorkflowPriority(workflowID, priority);
400400
}
401401

402+
setWorkflowDelay(workflowID: string, delaySeconds: number): Promise<void> {
403+
return this.systemDatabase.setWorkflowDelay(workflowID, delaySeconds);
404+
}
405+
402406
deleteWorkflow(workflowID: string, deleteChildren: boolean = false): Promise<void> {
403407
return this.systemDatabase.deleteWorkflows([workflowID], deleteChildren);
404408
}

src/dbos.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -816,6 +816,23 @@ export class DBOS {
816816
}, 'DBOS.setWorkflowPriority');
817817
}
818818

819+
/**
820+
* Update the delay on a DELAYED workflow. The new delay is calculated from when this method is called.
821+
* The workflow will remain in DELAYED status until the new delay expires, then transition to ENQUEUED.
822+
* Only affects workflows with DELAYED status.
823+
* @param workflowID - ID of the workflow
824+
* @param delaySeconds - Number of seconds to delay the workflow from now.
825+
*/
826+
static async setWorkflowDelay(workflowID: string, delaySeconds: number): Promise<void> {
827+
ensureDBOSIsLaunched('setWorkflowDelay');
828+
if (delaySeconds < 0) {
829+
throw new DBOSError('delaySeconds must be non-negative');
830+
}
831+
return runInternalStep(async () => {
832+
return DBOSExecutor.globalInstance!.systemDatabase.setWorkflowDelay(workflowID, delaySeconds);
833+
}, 'DBOS.setWorkflowDelay');
834+
}
835+
819836
/**
820837
* Retrieve the steps of a workflow
821838
* @param workflowID - ID of the workflow

src/system_database.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -994,6 +994,17 @@ export class SystemDatabase {
994994
);
995995
}
996996

997+
async setWorkflowDelay(workflowID: string, delaySeconds: number): Promise<void> {
998+
const delayUntilEpochMS = Date.now() + delaySeconds * 1000;
999+
await this.pool.query(
1000+
`UPDATE "${this.schemaName}".workflow_status
1001+
SET delay_until_epoch_ms = $1, updated_at = $2
1002+
WHERE workflow_uuid = $3
1003+
AND status = $4`,
1004+
[delayUntilEpochMS, Date.now(), workflowID, StatusString.DELAYED],
1005+
);
1006+
}
1007+
9971008
async getWorkflowChildren(workflowID: string): Promise<string[]> {
9981009
// BFS to find all descendant workflows
9991010
const visited = new Set<string>([workflowID]);

tests/wfqueue.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1955,4 +1955,32 @@ describe('delay-tests', () => {
19551955
expect(await resumeHandle.getResult()).toBe('done');
19561956
expect((await resumeHandle.getStatus())?.status).toBe(StatusString.SUCCESS);
19571957
}, 15000);
1958+
1959+
test('test_setWorkflowDelay', async () => {
1960+
// Start a workflow with a long delay
1961+
const handle = await DBOS.startWorkflow(TestDelayWFs, {
1962+
queueName: TestDelayWFs.queue.name,
1963+
enqueueOptions: { delaySeconds: 60 },
1964+
}).testWorkflowStr();
1965+
expect((await handle.getStatus())?.status).toBe(StatusString.DELAYED);
1966+
1967+
// Update the delay to a short value
1968+
const tBefore = Date.now();
1969+
await DBOS.setWorkflowDelay(handle.workflowID, 1);
1970+
const tAfter = Date.now();
1971+
1972+
// Verify the delay was updated
1973+
const status = await handle.getStatus();
1974+
expect(status?.status).toBe(StatusString.DELAYED);
1975+
expect(status?.delayUntilEpochMS).toBeDefined();
1976+
expect(status!.delayUntilEpochMS!).toBeGreaterThanOrEqual(tBefore + 1000);
1977+
expect(status!.delayUntilEpochMS!).toBeLessThanOrEqual(tAfter + 1000);
1978+
1979+
// Wait for the workflow to complete
1980+
expect(await handle.getResult()).toBe('done');
1981+
expect((await handle.getStatus())?.status).toBe(StatusString.SUCCESS);
1982+
1983+
// Test invalid delay
1984+
await expect(DBOS.setWorkflowDelay('some-id', -1)).rejects.toThrow('delaySeconds must be non-negative');
1985+
}, 30000);
19581986
});

0 commit comments

Comments
 (0)