Skip to content

Commit c1195e2

Browse files
committed
[docs] plugin documentation
1 parent 9c91b92 commit c1195e2

File tree

4 files changed

+363
-104
lines changed

4 files changed

+363
-104
lines changed

README.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,12 +101,18 @@ as well as a `package-lock.json` file and `node_modules` directory containing al
101101
### Run `mage.service` script
102102

103103
The `@ngageoint/mage.service` package includes a [`mage.service` [bin script](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#bin) for starting
104-
the server process. From the `instance` directory, you can run `npx mage.service --help` to see the configuration
105-
options. Because the `mage.service` script uses the [Commander](https://www.npmjs.com/package/commander) library, you
104+
the server process. From the `instance` directory, you can run `npx @ngageoint/mage.service --help` to see the configuration
105+
options.
106+
107+
#### Configuration merging
108+
109+
Because the `mage.service` script uses the [Commander](https://www.npmjs.com/package/commander) library, you
106110
can set most of the configuration options with environment variables and/or command line switches. You can also pass
107111
options from a JSON file or JSON string literal via the `-C (or --config, or MAGE_CONFIG environment variable)` command
108112
line switch. The `mage.service` script merges options from command line switches, environment variables, and JSON
109-
object, in descending order of precedence. The configuration object must have the form
113+
object, in descending order of precedence. This enables you to have a base configuration file, then override the
114+
file's entries with environment variables and/or command line switches as desired. The full configuration object for
115+
the `-C`/`MAGE_CONFIG` environment variable must have the following form.
110116
```json
111117
{
112118
"mage": {

plugins/README.md

Lines changed: 242 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,115 +1,256 @@
1-
## MAGE Plugins
2-
3-
### MAGE EPIC plugin
4-
5-
The mage-epic plugin will replicate MAGE observation to an ESRI server using [ESRI's REST API](http://resources.arcgis.com/en/help/rest/apiref/).
6-
7-
Configuration:
8-
9-
* esri - ESRI server configuration
10-
* url - ESRI url
11-
* host - ESRI root url
12-
* site - ESRI site partial url
13-
* restServices - ESRI rest services partial url
14-
* folder - ESRI folder partial url
15-
* serviceName - ESRI service name partial url
16-
* serviceType - ESRI service type partial url
17-
* layerId - ESRI layerId partial url
18-
* observations - MAGE observation mapping. Add one for each event id (i.e. 7 maps MAGE fields from eventId 7)
19-
* enable - turn on/off sync'ing of this event
20-
* interval - frequency (seconds) at which the plugin will sync this event to the ESRI server.
21-
* fields - array that maps each observation field to an ESRI fields
22-
* type - primitive type
23-
* mage - obseravtion field name in MAGE
24-
* esri - ESRI field name
25-
* attachments
26-
* enable - turn on/off attachment sync'ing
27-
* interval - frequency (seconds) at which the plugin will sync attachments to the ESRI server.
28-
* mongodb
29-
* url - url scheme for the mongodb database. This should be the same mongodb schema as used in your main MAGE configuration.
30-
* poolSize - mongodb connection pool size for this plugin
1+
# MAGE Plugins
312

32-
```json
3+
The MAGE server supports plugins for both the service and the web app. Both components define hooks which plugin
4+
packages can use to extend functionality.
5+
6+
## Service plugins
7+
8+
Service plugins can extend the capabilities the MAGE service by adding integrations with external services or
9+
manipulating data in the MAGE database itself in some useful way. The [Image](./image/) plugin, for example, queries
10+
the MAGE database for image attachments, rotates them such that they correctly oriented with respect to EXIF meta-data,
11+
and generates smaller thumbnail versions of the photos so clients do not need to download full-size images unless
12+
the users requests them. The [NGA-MSI](./nga-msi/) plugin adds an integration with NGA's Maritime Safety Information
13+
(MSI) web service to provide geospatial feeds of MSI data directly to MAGE clients.
14+
15+
### Hooks
16+
17+
The MAGE service defines several plugin [hook APIs](../service/src/plugins.api/). A service plugin starts with the
18+
aptly-named [`InitPluginHook`](../service/src/plugins.api/index.ts). Any service plugin must at least implement an
19+
init hook for the main service module to initialize the plugin. The init hook defines dependencies on core service
20+
components and performs any initialization tasks the plugin requires, such as database setup. Any Node module can be
21+
a plugin, as long as the module's export is an object conforming to the `InitPluginHook` type. For example,
22+
```typescript
23+
export = {
24+
inject: {
25+
mageEventRepository: MageEventRepositoryToken
26+
},
27+
async init({ mageEventRepository: MageEventRepository }): Promise<any> {
28+
// initialize the plugin ...
29+
}
30+
}
31+
```
32+
To inform the MAGE service that a given module is a plugin, you must list the module in the configuration you pass to
33+
the [`mage.service`](../service/bin/mage.service.js) script. For example, you could create a package called
34+
`@examples/mage-plugins`. Within the package you can export an `InitPluginHook` object from the root `index.js` module,
35+
or you could export the hook object from a nested module such as `/plugin1`, or both. For the MAGE service to register
36+
those plugin hooks, you would start the service with switches like
37+
```bash
38+
npx mage.service --plugin @examples/mage-plugins
39+
# or
40+
npx mage.service --plugin @examples/mage-plugins/plugin1
41+
# or
42+
npx mage.service --plugin @examples/mage-plugins --plugin @examples/mage-plugins/plugin1
43+
```
44+
45+
For a relatively robust example of dependency injection and initialization, see the [Image plugin's](./image/service/src/index.ts)
46+
init hook, which injects a plugin-scoped repository to persist plugin configuration, and integrates with the MAGE
47+
service's web layer to add plugin web routes.
48+
49+
### Dependency injection
50+
51+
As mentioned above, a plugin's init hook defines dependencies on core service components, such as repositories that
52+
can interact with the database. During startup, the [main service module](../service/src/app.ts) injects the
53+
dependencies the plugin requests. The object a plugin module exports can include an `inject` key whose value is
54+
another object hash whose values are injection token symbols the MAGE service defines. When present, the MAGE service
55+
inspects a plugin's `inject` object and passes an object to the plugin's `init` function with the same keys as the
56+
`inject` object whose values are the components corresponding to the injection token values. For example, if a plugin
57+
module exports the following init hook
58+
```typescript
59+
export = {
60+
inject: {
61+
eventRepo: MageEventRepositoryToken,
62+
observationRepoForEvent: ObservationRepositoryToken,
63+
userRepo: UserRepositoryToken
64+
},
65+
async init(injection): Promise<any> {
66+
// initialize the plugin ...
67+
}
68+
}
69+
```
70+
... the `injection` argument the MAGE service passes to the `init` function will be an object with the shape
71+
```typescript
3372
{
34-
"enable": false,
35-
"esri": {
36-
"url": {
37-
"host": "<root ERSI server>",
38-
"site": "<site partial>",
39-
"restServices": "<rest services partial>",
40-
"folder": "<folder partial>",
41-
"serviceName": "<service name partial>",
42-
"serviceType": "<service type partial>",
43-
"layerId": "<layer id partial>"
44-
},
45-
"observations": {
46-
"7": {
47-
"enable": true,
48-
"interval": 30,
49-
"fields": [{
50-
"type": "Date",
51-
"mage": "timestamp",
52-
"esri": "EVENTDATE"
53-
},{
54-
"type": "Type",
55-
"mage": "type",
56-
"esri": "TYPE"
57-
},{
58-
"type": "String",
59-
"mage": "EVENTLEVEL",
60-
"esri": "EVENTLEVEL"
61-
},{
62-
"type": "String",
63-
"mage": "TEAM",
64-
"esri": "TEAM"
65-
},{
66-
"type": "String",
67-
"mage": "DESCRIPTION",
68-
"esri": "DESCRIPTION"
69-
}]
70-
}
71-
},
72-
"attachments": {
73-
"enable":true,
74-
"interval": 60
75-
}
73+
eventRepo: instanceOfMageEventRepository,
74+
observationRepoForEvent: instanceOfObservationRepositoryFactory,
75+
userRepo: instanceOfUserRepository
76+
}
77+
```
78+
This dependency injection token concept is based on [Angular's](https://angular.io/guide/dependency-injection-overview)
79+
mechanism.
80+
81+
### Creating a service plugin
82+
83+
A service plugin is a Node module contained within an NPM package. To begin developing a plugin, create a new NPM
84+
package.
85+
```bash
86+
mkdir mage-service-plugins
87+
cd mage-service-plugins
88+
npm init --scope @examples
89+
```
90+
Follow the prompts to finish initializing your package.
91+
92+
Manually edit the `package.json` file to include a peer dependency on `@ngageoint/mage.service` at your chosen version.
93+
```json
94+
"peerDependencies": {
95+
"@ngageoint/mage.service": "^6.2.0"
96+
}
97+
```
98+
99+
Then, add the MAGE service package to your plugin package. The easiest way is to globally install a release package
100+
tarball from [GitHub](https://github.com/ngageoint/mage-server/releases).
101+
```bash
102+
npm i -g https://github.com/ngageoint/mage-server/releases/download/6.2.0/ngageoint-mage.service-6.2.0.tgz
103+
npm link @ngageoint/mage.service
104+
```
105+
This installs the package tarball from the given GitHub release URL under NPM's global prefix on you system, then links
106+
the package from the global prefix location to your plugin package's `node_modules` directory to satisfy the peer
107+
dependency on `@ngageoint/mage.service`. Now, you should get clean output from `npm ls`.
108+
```bash
109+
$ npm ls
110+
@examples/[email protected] <...>/mage/examples/mage-service-plugins
111+
└── @ngageoint/[email protected] -> ./../../../../.nvm/versions/node/v16.15.1/lib/node_modules/@ngageoint/mage.service
112+
```
113+
114+
Now, add TypeScript to take advantage type checking and code completion from the MAGE service types. Ideally you'll
115+
install the same version as the MAGE service's dependency to ensure compatibility with MAGE's .
116+
```bash
117+
npm i --save-dev typescript@^4.6.0
118+
```
119+
Then you add a [`tsconfig.json`](https://www.typescriptlang.org/tsconfig) to configure TypeScript. You can use
120+
TypeScript's `tsc` command to generate the file.
121+
```bash
122+
npx tsc --init
123+
```
124+
Alternatively, you can copy the configuration from one of the MAGE server [plugin](./nga-msi/tsconfig.json)
125+
[packages](./image/service/tsconfig.json). Adjust the TypeScript configuration to your liking, but generally you may
126+
find that placing your `.ts` source files into a `src` directory, and outputting transpiled JavaScript to a separate
127+
directory like `lib` or `dist` is a manageable arrangement. This is what the TypeScript configurations for the MAGE
128+
service and open source plugins specify.
129+
130+
Finally, you can start writing the code for your plugin. Begin with a module file that exports the main
131+
`InitPluginHook`. This will likely be the [main module](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#main)
132+
of your plugin package. If you configured TypeScript to transpile from the `src` directory and output to the `lib`
133+
directory as described above, create the file `src/index.ts`, and add the entry ``"main": "lib/index.js"` to
134+
`package.json`. In `src/index.ts`, add the following.
135+
```typescript
136+
export = {
137+
inject: {
76138
},
77-
"mongodb": {
78-
"url": "mongodb://localhost/magedb",
79-
"poolSize": 1
80-
}
139+
async init(injection): Promise<any> {
140+
}
81141
}
82142
```
143+
Then you can fill in the functionality of your plugin.
144+
145+
### Deploying a service plugin
83146

84-
### MAGE RAGE plugin
147+
#### Create the plugin package
85148

86-
The mage-rage plugin will replicate all MAGE data to another mage server
149+
To get your plugin running in a MAGE server, you'll first need a [running instance](../README.md#running-a-mage-server).
150+
Once you've setup a server instance, you can package your plugin to add to the server instance. If you used TypeScript
151+
to code your plugin as described above, your plugin should transpile JavaScript to a directory, such as `lib`. Ensure
152+
your plugin's `package.json` defines the plugin's entry point using the `main` entry as above. You could also use
153+
[package exports](https://nodejs.org/api/packages.html#package-entry-points). Also ensure that the `package.json`
154+
[includes](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#files) all of the transpiled JavaScript by adding
155+
an entry such as
156+
```json
157+
"files": [
158+
"lib"
159+
]
160+
```
161+
Now, from the base directory of your plugin package, run the [`npm pack`](https://docs.npmjs.com/cli/v8/commands/npm-pack)
162+
command to create a package tarball of your plugin. This will create a file like `examples-mage-service-plugins-1.0.0.tgz`.
163+
Copy the package tarball to a persistent location, like where you have [installed](../README.md#install-mage-server-packages)
164+
the MAGE server packages.
165+
166+
#### Enable the plugin
87167

88-
Configuration:
168+
To enable your plugin, add the ID of the module that exports your `PluginInitHook` to the `servicePlugins` array in your [configuration object](../README.md#configuration-merging). You can do this a few ways.
169+
1. Use the `--plugin` command line switch:
170+
```bash
171+
mage.service --plugin @examples/mage-service-plugins
172+
```
173+
2. Use a plugin JSON object from a file:
174+
```bash
175+
mage.service -P plugins.json
176+
```
177+
where `plugins.json` contains
178+
```json
179+
{
180+
"servicePlugins": [
181+
"@examples/mage-service-plugins"
182+
]
183+
}
184+
```
185+
3. Use a full MAGE configuration object that references the plugin that exports the full configuration object:
186+
```bash
187+
mage.service -C /etc/mage.js
188+
```
189+
where `mage.json` contains something like
190+
```javascript
191+
module.exports = {
192+
mage: {
193+
// other config options
194+
plugins: {
195+
servicePlugins: [
196+
'@examples/mage-service-plugins'
197+
]
198+
}
199+
}
200+
}
201+
```
202+
4. Use an environment variable:
203+
```bash
204+
MAGE_PLUGINS='{ "servicePlugins": [ "@examples/mage-service-plugins" ] }' mage.service
205+
```
89206

90-
* url - MAGE server url to replicate data to.
91-
* credentials - MAGE replication server credentials used for authentication.
92-
* username - username on MAGE replication server
93-
* uid - device uid
94-
* password - password
95-
* interval - frequency (seconds) at which the plugin will sync data to the replication server.
96-
* mongodb
97-
* url - url scheme for the mongodb database. This should be the same mongodb schema as used in your main MAGE configuration.
98-
* poolSize - mongodb connection pool size for this plugin
207+
#### Multiple package plugins
99208

209+
You can provide multiple `PluginInitHook` modules in one package if you wish. To enable them, include the ID of each
210+
module that exports a `PluginInitHook` in the `servicePlugins` array:
211+
```bash
212+
mage.service --plugin @examples/mage-service-plugins/plugin1 --plugin @examples/mage-service-plugins/plugin2 # ...
213+
```
214+
or with plugins JSON file such as `mage.service -P plugins.json`:
215+
```json
216+
{
217+
"servicePlugins": [
218+
"@examples/mage-service-plugins/plugin1",
219+
"@examples/mage-service-plugins/plugin2"
220+
]
221+
}
222+
```
223+
Be sure that the module IDs you list are [resolvable](https://nodejs.org/api/modules.html#all-together) through your
224+
package structure. If your plugin package code resides in the `lib` directory and the `package.json` only has a `main`
225+
entry, such as
226+
```json
227+
{
228+
"main": "lib/index.js"
229+
}
230+
```
231+
then the registered plugin module IDs will need to include your package's top-level directory:
100232
```json
101233
{
102-
"enable": false,
103-
"url": "",
104-
"credentials": {
105-
"username": "",
106-
"uid": "",
107-
"password": ""
108-
},
109-
"interval": 60,
110-
"mongodb": {
111-
"url": "mongodb://localhost/magedb",
112-
"poolSize": 1
113-
}
234+
"servicePlugins": [
235+
"@examples/mage-service-plugins/lib/plugin1",
236+
"@examples/mage-service-plugins/lib/plugin2"
237+
]
114238
}
115239
```
240+
If your `package.json` includes your plugin modules as [entry points](https://nodejs.org/api/packages.html#package-entry-points)
241+
in the `exports` entry, as below, then you will not need to include the top-level directory in the module IDs. Be sure
242+
to also include an entry point to allow the MAGE service to load the `package.json` file from your plugin package.
243+
```json
244+
{
245+
"exports": {
246+
".": "./lib/index.js",
247+
"./plugin1.js": "./lib/plugin1.js",
248+
"./plugin2.js": "./lib/plugin2.js",
249+
"./package.json": "./package.json"
250+
}
251+
}
252+
```
253+
254+
## Web UI plugins
255+
256+
More to come soon.

0 commit comments

Comments
 (0)