Building a more complex extension

Ops Studio extension skeleton

We created an open source repository to share with everyone how we engineered SQL Search for SQL Operations Studio:

Is this the way to architect an extension in Ops Studio?

We don’t know, we hope we will get feedback on this and change it to follow the recommended way.

This work was heavily inspired by some existing vscode extension

2. Architecture

In the case of SQL Search and the sqlops-extension-skeleton, we divided the architecture of the extension into 3 components:

Backend

Extension

Frontend

SQL Search architecture

2.1. Backend

The Backend of our SQL Search extension is a .NET console application that wraps the SQL Search core engine.

In the case of the sqlops-extension-skeleton example it looks like this:

├── Backend.sln

└── src

└── Backend.Console

├── Backend.Console.csproj

└── Program.cs

2.2. Extension

The Extension is the entry point of our extension, it registers the commands that the user can trigger from Ops Studio ( search , reindex ).

In the case of sqlops-extension-skeleton it looks like this:

├── README.md

├── images

│ └── gatebase.png

├── package-lock.json

├── package.json

├── src

│ ├── extension.ts

│ ├── extensionContentProvider.ts

│ ├── extensionContext.ts

│ ├── extensionFacade.ts

│ ├── extensionUri.ts

│ ├── proxy

│ │ ├── baseUri.ts

│ │ ├── extensionProxy.ts

│ │ └── localWebServer.ts

│ └── versionResult.ts

└── tsconfig.json

It also serves an API using a local express server in order to get and dispatch different actions from the user:

SQL Search extension internal API

2.3. Frontend

The Frontend is a React / Redux application written in TypeScript and it serves two views, the results view and the object definition view.

Triggering results view and object definition view

In the case of the sqlops-extension-skeleton it is a JavaScript React application:

.

├── package-lock.json

├── package.json

├── public

│ ├── favicon.ico

│ ├── index.html

│ └── manifest.json

├── src

│ ├── App.css

│ ├── App.js

│ ├── http

│ │ └── queryStringParameters.js

│ ├── index.css

│ └── index.js

3. Communication

These 3 components communicate between each other via different mechanisms.

Communication across all the extension parts

3.1 Backend — Extension

3.1.1 Extension side — spawning Backend process

The Backend .NET console application is called by spawning the Backend console using the dotnet command.

Where version() is the following

The runCli function will be the one that spawns the dotnet with our Backend.Console.dll .

In addition, we need to retrieve the output from the process in order to return the response. Notice how we track data with process.stdout.on('data' and resolve the Promise on close process.stdout.on('close' .

3.1.2 Backend side

Our console application expects a ‘version’ command and writes the version in the console as a JSON object.

3.2 Extension — Frontend

There are 2 ways the Extension and the Frontend communicate. The first one is to serve the views. The second one is to expose a local API for the Frontend to request and trigger actions.

3.2.1 Serve views from Frontend

In the case of our skeleton example this is done by registering a TextDocumentContentProvider

This TextDocumentContentProvider is then requested when the extension sendMessage is triggered.

That is Ctrl / Cmd + Shift + p and followed by Hello World .

In this case uri is equal to rg://my-extension/myextension?message%3Dhello which matches the scheme that was used to register the TextDocumentContentProvider .

The ExtensionContentProvider implements vscode.TextDocumentContentProvider .

But how does this serve the Frontend?

We use a local Express server to serve our Frontend application.

Where this.app is an express application defined as:

And extensionPath is path-to-repo/sqlops-extension-skeleton/extension/out .

We serve the out/frontend as a static folder in our Express application.

Later, when the document is loaded we set the URL to be http://localhost:50364/myextension?message=hello .

3.2.2 Serve API for communication between Frontend and Extension

In order to allow communication between the Frontend and the Extension we also expose a local API for the Frontend to request and trigger actions on this extension.

This is done by using the same Express application we were using in the step above.

In the sqlops-extension-skeleton we define 3 API routes that can be used by our Frontend application.

This is defined in our ExtensionProxy.ts

This will allow the Frontend application to use this REST API how it see’s fit:

Here is an example that fetch the active connections.