@dbos-inc/create 1.31.5-preview → 1.31.8-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/create",
3
- "version": "1.31.5-preview",
3
+ "version": "1.31.8-preview",
4
4
  "description": "Tool for performing project initialization from template",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,4 +1,4 @@
1
- # DBOS Hello
1
+ # DBOS Background Job
2
2
 
3
3
  This is a [DBOS app](https://docs.dbos.dev/) bootstrapped with `npx @dbos-inc/create`, using [Express.js](https://expressjs.com/) and [Knex](https://docs.dbos.dev/tutorials/using-knex) to interact with postgres.
4
4
 
@@ -40,12 +40,22 @@ npm run start
40
40
 
41
41
  To see that it's working, visit this URL in your browser: [`http://localhost:3000/`](http://localhost:3000/).
42
42
 
43
- Click on the "Run DBOS Workflow" button.
43
+ Click on the "Start Background Job" button.
44
44
 
45
- You should get this message: `Hello, dbos! You have been greeted 1 times.`
46
- Each time you refresh the page, the counter should go up by one!
45
+ You should this message: `Your background task has completed step 0 of 9.`
46
+ As the background job completes steps, the step counter should go up by one!
47
47
 
48
- Congratulations! You just launched a DBOS application.
48
+ Click on "Crash the application" button.
49
+
50
+ The step counter stops increasing.
51
+ On the command line, you will see that the application has stopped.
52
+ Start the application again.
53
+ ```bash
54
+ npm run start
55
+ ```
56
+ In the browser, you will see that the execution of steps resumes where it left off and eventually completes.
57
+
58
+ Congratulations! You just run a DBOS application.
49
59
 
50
60
  ## The application
51
61
 
@@ -57,9 +67,9 @@ if (process.env.NEXT_PHASE !== "phase-production-build") {
57
67
  await DBOS.launch();
58
68
  }
59
69
  ```
60
- - The workflow is called by the POST method in app/greetings/route.ts.
70
+ - The workflow for the background job is called by the called the GET method in app/tasks/route.ts.
61
71
 
62
- - The POST is called by the component in src/components/callDBOSWorkflow.tsx. It calls the route /greetings.
72
+ - The GET is called by the component in src/components/BackGroundTask.tsx. It calls the route /tasks.
63
73
 
64
74
  - The component is called from the main UI page.tsx.
65
75
 
@@ -72,7 +82,7 @@ To add more functionality to this application, modify `src/operations.ts`. If yo
72
82
 
73
83
  ## Running in DBOS Cloud
74
84
 
75
- To deploy this app to DBOS Cloud, first install the DBOS Cloud CLI (example with [npm](https://www.npmjs.com/)):
85
+ To deploy this app to DBOS Cloud, first install the DBOS Cloud CLI (example with [npm](https://www.npmjs.com/)):
76
86
 
77
87
  ```shell
78
88
  npm i -g @dbos-inc/dbos-cloud
@@ -87,5 +97,4 @@ dbos-cloud app deploy
87
97
  ## Next Steps
88
98
 
89
99
  - For a detailed tutorial, check out our [programming quickstart](https://docs.dbos.dev/getting-started/quickstart-programming).
90
- - To learn more about DBOS, take a look at [our documentation](https://docs.dbos.dev/) or our [source code](https://github.com/dbos-inc/dbos-transact).
91
-
100
+ - To learn more about DBOS, take a look at [our documentation](https://docs.dbos.dev/) or our [source code](https://github.com/dbos-inc/dbos-transact).
@@ -20,6 +20,25 @@ class dbosWorkflowClass {
20
20
  const greeting = `Hello! You have been greeted ${greet_count} times.`;
21
21
  return greeting;
22
22
  }
23
+
24
+ @DBOS.transaction()
25
+ static async backgroundTaskStep(i : number) {
26
+ DBOS.logger.info(`Completed step ${i}`);
27
+ }
28
+
29
+ @DBOS.workflow()
30
+ static async backgroundTask(i: number) {
31
+ DBOS.logger.info("Hello from background task!");
32
+ for (let j = 1; j <= i; j++) {
33
+ await dbosWorkflowClass.backgroundTaskStep(j);
34
+ DBOS.logger.info("Sleeping for 2 seconds");
35
+ await DBOS.sleepSeconds(2);
36
+ await DBOS.setEvent("steps_event", j)
37
+ }
38
+ DBOS.logger.info("Background task complete!");
39
+ }
40
+
41
+
23
42
  }
24
43
 
25
44
  // Launch the DBOS runtime
@@ -34,4 +53,9 @@ if (process.env.NEXT_PHASE !== "phase-production-build") {
34
53
  export async function dbosWorkflow(userName: string) {
35
54
  DBOS.logger.info("Hello from DBOS!");
36
55
  return await dbosWorkflowClass.helloDBOS(userName);
56
+ }
57
+
58
+ export async function dbosBackgroundTask(workflowID: string) {
59
+ DBOS.logger.info("Hello from DBOS!");
60
+ return DBOS.startWorkflow(dbosWorkflowClass, {workflowID: workflowID}).backgroundTask(10);
37
61
  }
@@ -0,0 +1,8 @@
1
+
2
+ export async function GET(request: Request) {
3
+
4
+ console.log("Received request Crashing the app");
5
+
6
+ process.exit(1);
7
+
8
+ }
@@ -1,12 +1,20 @@
1
1
  import Image from "next/image";
2
- import CallDBOSWorkflow from "../components/client/callDBOSWorkflow";
2
+ import BackGroundTask from "@/components/client/BackGroundTask";
3
3
 
4
4
  export default function Home() {
5
5
  return (
6
6
  <div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
7
7
  <main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
8
+
9
+
10
+ <h1 className="text-xl font-semibold mb-4">Welcome to DBOS!</h1>
11
+
12
+
13
+ <p className="mb-4">
14
+ DBOS helps you build applications that are <strong>resilient to any failure</strong>&mdash;no matter how many times you crash this app, your background task will always recover from its last completed step in about ten seconds.
15
+ </p>
8
16
  <div>
9
- <CallDBOSWorkflow wfResult=""/>
17
+ <BackGroundTask />
10
18
  </div>
11
19
  </main>
12
20
  <footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
@@ -0,0 +1,15 @@
1
+ import { NextResponse } from "next/server";
2
+ import { DBOS } from "@dbos-inc/dbos-sdk";
3
+
4
+ export async function GET(request: Request, { params }: { params: Promise<{ slug: string }> }) {
5
+
6
+ const taskId = (await params).slug;
7
+ DBOS.logger.info(`Received request to check on taskId: ${taskId}`);
8
+
9
+ let step = await DBOS.getEvent(taskId, "steps_event");
10
+
11
+ DBOS.logger.info(`For taskId: ${taskId} we are done with ${step} steps`);
12
+
13
+ return NextResponse.json({ "stepsCompleted": step});
14
+
15
+ }
@@ -0,0 +1,18 @@
1
+ import { NextResponse } from "next/server";
2
+ import { DBOS } from "@dbos-inc/dbos-sdk";
3
+ import { dbosBackgroundTask } from "@/actions/dbosWorkflow";
4
+
5
+ export async function GET(request: Request, { params }: { params: Promise<{ slug: string }> }) {
6
+
7
+ const taskId = (await params).slug;
8
+
9
+ DBOS.logger.info(`Received request to start background task taskId: ${taskId}`);
10
+
11
+ DBOS.logger.info(`Started background task taskId: ${taskId}`);
12
+
13
+ await dbosBackgroundTask(taskId)
14
+
15
+ return NextResponse.json({ message: "Background task started" });
16
+
17
+ }
18
+
@@ -0,0 +1,180 @@
1
+ "use client";
2
+
3
+ import { useState, useEffect } from "react";
4
+ import { useRouter, useSearchParams } from "next/navigation";
5
+ import { dbosBackgroundTask } from "@/actions/dbosWorkflow";
6
+ import { Suspense } from 'react'
7
+
8
+ let intervalInitialized = false;
9
+
10
+ function generateRandomString(): string {
11
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
12
+ const array = new Uint8Array(6);
13
+ crypto.getRandomValues(array); // Fills the array with cryptographically random values
14
+
15
+ return Array.from(array)
16
+ .map(x => chars[x % chars.length])
17
+ .join('');
18
+ }
19
+
20
+ function BackGroundTask() {
21
+ const [isRunning, setIsRunning] = useState(false);
22
+ const [currentStep, setCurrentStep] = useState(0);
23
+ const [taskId, setTaskid] = useState("");
24
+ const [isReconnecting, setIsReconnecting] = useState(false);
25
+
26
+ const router = useRouter();
27
+ const searchParams = useSearchParams();
28
+
29
+ const startBackgroundJob = async () => {
30
+ setIsRunning(true);
31
+
32
+ setCurrentStep(0);
33
+
34
+ let task = taskId
35
+ if (taskId === "") {
36
+ task = generateRandomString();
37
+ setTaskid(task);
38
+ updateQueryParam("id", task);
39
+ }
40
+
41
+ // start the background job
42
+ try {
43
+ await fetch(`/tasks/${task}`, { method: "GET" });
44
+ } catch (error) {
45
+ console.error("Failed to start job", error);
46
+ setIsRunning(false);
47
+ }
48
+ };
49
+
50
+ const crashApp = async () => {
51
+
52
+ if(!isRunning) {
53
+ console.log("Not running, nothing to crash");
54
+ return;
55
+ }
56
+
57
+ setIsRunning(false);
58
+
59
+ console.log("Crashing the application");
60
+
61
+ // stop the background job
62
+ try {
63
+ await fetch("/crash", { method: "GET" });
64
+ } catch (error) {
65
+ console.error("Failed to start job", error);
66
+
67
+ }
68
+
69
+ };
70
+
71
+ // Update the URL query parameter
72
+ const updateQueryParam = (key: string, value: string) => {
73
+ const params = new URLSearchParams(searchParams.toString());
74
+ params.set(key, value);
75
+ const newUrl = `${window.location.pathname}?${params.toString()}`;
76
+ router.replace(newUrl);
77
+ };
78
+
79
+ // Remove the `id` query parameter from the URL
80
+ const clearQueryParam = (key: string) => {
81
+ const params = new URLSearchParams(searchParams.toString());
82
+ params.delete(key);
83
+ const newUrl = `${window.location.pathname}?${params.toString()}`;
84
+ router.replace(newUrl);
85
+ };
86
+
87
+ // fetch the current progress
88
+ const fetchProgress = async () => {
89
+ try {
90
+
91
+ if (taskId === "") {
92
+ console.log("No task to monitor");
93
+ return;
94
+ }
95
+
96
+ const response = await fetch(`/step/${taskId}`, { method: "GET" });
97
+ if (!response.ok) {
98
+ console.error("Failed to fetch job progress", response.statusText);
99
+ setIsReconnecting(true);
100
+ return;
101
+ }
102
+ setIsReconnecting(false);
103
+
104
+ const data = await response.json();
105
+
106
+ console.log("Step completed", data.stepsCompleted);
107
+
108
+ if (data.stepsCompleted) {
109
+ setIsRunning(true);
110
+ setCurrentStep(data.stepsCompleted);
111
+
112
+
113
+ if (data.stepsCompleted === 10) {
114
+ clearQueryParam("id");
115
+ setIsRunning(false);
116
+ setTaskid("");
117
+ setCurrentStep(0);
118
+
119
+ }
120
+ }
121
+ } catch (error) {
122
+ console.error("Failed to fetch job progress", error);
123
+ setIsRunning(false);
124
+ setTaskid("");
125
+ setIsReconnecting(true);
126
+ }
127
+ };
128
+
129
+ // Polling the progress every 2 seconds while the job is running
130
+ useEffect(() => {
131
+ const idFromUrl = searchParams.get("id");
132
+ if (idFromUrl) {
133
+ setTaskid(idFromUrl);
134
+ setIsRunning(true); // Assume the job is already running if there's an ID
135
+ }
136
+
137
+ if (!intervalInitialized) {
138
+ const interval = setInterval(fetchProgress, 2000);
139
+ intervalInitialized = true;
140
+ return () => {
141
+ clearInterval(interval);
142
+ intervalInitialized = false;
143
+ };
144
+ }
145
+ }, [searchParams]);
146
+
147
+
148
+ return (
149
+ <div>
150
+ <div className="flex flex-row gap-2">
151
+ <button onClick={startBackgroundJob} disabled={isRunning} className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
152
+ {isRunning ? "Job in Progress..." : "Start Background Job"}
153
+ </button>
154
+ <button onClick={crashApp} disabled={!isRunning} className="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600">
155
+ { isRunning ? "Crash the application" : "Not Running"}
156
+ </button>
157
+ </div>
158
+
159
+ <p>
160
+ {currentStep < 10
161
+ ? `Your background task has completed step ${currentStep} of 10.`
162
+ : "Background task completed successfully!"}
163
+ </p>
164
+ <p>
165
+ {isReconnecting ? "Reconnecting..." : ""}
166
+ </p>
167
+
168
+
169
+ </div>
170
+ );
171
+
172
+ }
173
+
174
+ const WrappedBackgroundJobComponent = () => (
175
+ <Suspense fallback={<p>Loading...</p>}>
176
+ <BackGroundTask />
177
+ </Suspense>
178
+ );
179
+
180
+ export default WrappedBackgroundJobComponent;