@lde/distribution-monitor 0.1.1
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/README.md +180 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +96 -0
- package/dist/config.d.ts +86 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +60 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/monitor.d.ts +22 -0
- package/dist/monitor.d.ts.map +1 -0
- package/dist/monitor.js +92 -0
- package/dist/schema.d.ts +200 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +36 -0
- package/dist/service.d.ts +72 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +134 -0
- package/dist/store.d.ts +23 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +84 -0
- package/dist/types.d.ts +71 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Distribution Monitor
|
|
2
|
+
|
|
3
|
+
Monitor DCAT distributions (SPARQL endpoints and data dumps) with periodic probes, storing observations in PostgreSQL. Uses [`@lde/distribution-probe`](../distribution-probe) for the actual health check.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @lde/distribution-monitor
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## CLI Usage
|
|
12
|
+
|
|
13
|
+
The easiest way to run the monitor is via the CLI with a configuration file.
|
|
14
|
+
|
|
15
|
+
### Quick Start
|
|
16
|
+
|
|
17
|
+
1. Create a configuration file (TypeScript, JavaScript, JSON, or YAML)
|
|
18
|
+
2. Run the monitor
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Start continuous monitoring
|
|
22
|
+
npx distribution-monitor start
|
|
23
|
+
|
|
24
|
+
# Run a one-off check for all monitors
|
|
25
|
+
npx distribution-monitor check
|
|
26
|
+
|
|
27
|
+
# Check a specific monitor
|
|
28
|
+
npx distribution-monitor check dbpedia
|
|
29
|
+
|
|
30
|
+
# Use a custom config path
|
|
31
|
+
npx distribution-monitor start --config ./configs/production.config.ts
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### TypeScript Config (`distribution-monitor.config.ts`)
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { defineConfig } from '@lde/distribution-monitor';
|
|
38
|
+
|
|
39
|
+
export default defineConfig({
|
|
40
|
+
databaseUrl: process.env.DATABASE_URL,
|
|
41
|
+
intervalSeconds: 300,
|
|
42
|
+
timeoutMs: 30_000,
|
|
43
|
+
monitors: [
|
|
44
|
+
{
|
|
45
|
+
identifier: 'dbpedia',
|
|
46
|
+
distribution: {
|
|
47
|
+
accessUrl: 'https://dbpedia.org/sparql',
|
|
48
|
+
conformsTo: 'https://www.w3.org/TR/sparql11-protocol/',
|
|
49
|
+
},
|
|
50
|
+
sparqlQuery: 'ASK { ?s ?p ?o }',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
identifier: 'wikidata',
|
|
54
|
+
distribution: {
|
|
55
|
+
accessUrl: 'https://query.wikidata.org/sparql',
|
|
56
|
+
conformsTo: 'https://www.w3.org/TR/sparql11-protocol/',
|
|
57
|
+
},
|
|
58
|
+
sparqlQuery: 'SELECT * WHERE { ?s ?p ?o } LIMIT 1',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
identifier: 'my-dump',
|
|
62
|
+
distribution: {
|
|
63
|
+
accessUrl: 'https://example.org/data.nt',
|
|
64
|
+
mediaType: 'application/n-triples',
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### YAML Config (`distribution-monitor.config.yaml`)
|
|
72
|
+
|
|
73
|
+
```yaml
|
|
74
|
+
databaseUrl: ${DATABASE_URL}
|
|
75
|
+
intervalSeconds: 300
|
|
76
|
+
monitors:
|
|
77
|
+
- identifier: dbpedia
|
|
78
|
+
distribution:
|
|
79
|
+
accessUrl: https://dbpedia.org/sparql
|
|
80
|
+
conformsTo: https://www.w3.org/TR/sparql11-protocol/
|
|
81
|
+
sparqlQuery: ASK { ?s ?p ?o }
|
|
82
|
+
- identifier: my-dump
|
|
83
|
+
distribution:
|
|
84
|
+
accessUrl: https://example.org/data.nt
|
|
85
|
+
mediaType: application/n-triples
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Environment Variables
|
|
89
|
+
|
|
90
|
+
Create a `.env` file for sensitive configuration:
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
DATABASE_URL=postgres://user:pass@localhost:5432/monitoring
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The CLI automatically loads `.env` files.
|
|
97
|
+
|
|
98
|
+
### Config Auto-Discovery
|
|
99
|
+
|
|
100
|
+
The CLI searches for configuration in this order:
|
|
101
|
+
|
|
102
|
+
1. `distribution-monitor.config.{ts,mts,js,mjs,json,yaml,yml}`
|
|
103
|
+
2. `.distribution-monitorrc`
|
|
104
|
+
3. `package.json` → `"distribution-monitor"` key
|
|
105
|
+
|
|
106
|
+
## Programmatic Usage
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
import { Distribution } from '@lde/dataset';
|
|
110
|
+
import {
|
|
111
|
+
MonitorService,
|
|
112
|
+
PostgresObservationStore,
|
|
113
|
+
type MonitorConfig,
|
|
114
|
+
} from '@lde/distribution-monitor';
|
|
115
|
+
|
|
116
|
+
const monitors: MonitorConfig[] = [
|
|
117
|
+
{
|
|
118
|
+
identifier: 'dbpedia',
|
|
119
|
+
distribution: Distribution.sparql(new URL('https://dbpedia.org/sparql')),
|
|
120
|
+
sparqlQuery: 'ASK { ?s ?p ?o }',
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
identifier: 'my-dump',
|
|
124
|
+
distribution: new Distribution(
|
|
125
|
+
new URL('https://example.org/data.nt'),
|
|
126
|
+
'application/n-triples',
|
|
127
|
+
),
|
|
128
|
+
},
|
|
129
|
+
];
|
|
130
|
+
|
|
131
|
+
const store = await PostgresObservationStore.create(
|
|
132
|
+
'postgres://user:pass@localhost:5432/db',
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
const service = new MonitorService({
|
|
136
|
+
store,
|
|
137
|
+
monitors,
|
|
138
|
+
intervalSeconds: 300,
|
|
139
|
+
timeoutMs: 30_000,
|
|
140
|
+
headers: new Headers({ 'User-Agent': 'my-monitor/1.0' }),
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
service.start();
|
|
144
|
+
// …or run immediate checks
|
|
145
|
+
await service.checkAll();
|
|
146
|
+
await service.checkNow('dbpedia');
|
|
147
|
+
|
|
148
|
+
const observations = await store.getLatest();
|
|
149
|
+
for (const [identifier, observation] of observations) {
|
|
150
|
+
console.log(
|
|
151
|
+
`${identifier}: ${observation.success ? 'OK' : 'FAIL'} (${
|
|
152
|
+
observation.responseTimeMs
|
|
153
|
+
}ms)`,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
service.stop();
|
|
158
|
+
await store.close();
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Distribution shape
|
|
162
|
+
|
|
163
|
+
Each monitor targets a DCAT `Distribution`. Supply:
|
|
164
|
+
|
|
165
|
+
- `accessUrl` — required. The URL to probe.
|
|
166
|
+
- `mediaType` (optional) — plain content-type (e.g. `application/n-triples`) or DCAT-AP 3.0 IANA URI. Omit for SPARQL endpoints that only serve the protocol.
|
|
167
|
+
- `conformsTo` (optional) — use `https://www.w3.org/TR/sparql11-protocol/` to mark a distribution as a SPARQL endpoint. Required when `accessUrl` doesn’t already imply SPARQL via `mediaType`.
|
|
168
|
+
- `sparqlQuery` (optional) — for SPARQL endpoints. Query type (ASK / SELECT / CONSTRUCT / DESCRIBE) is autodetected. Defaults to a minimal `SELECT` availability probe.
|
|
169
|
+
|
|
170
|
+
Distributions with embedded credentials (`https://user:pass@host/path`) are supported: the credentials are stripped from the URL and forwarded as an `Authorization: Basic` header.
|
|
171
|
+
|
|
172
|
+
## Database Initialisation
|
|
173
|
+
|
|
174
|
+
`PostgresObservationStore.create()` automatically initializes the database schema:
|
|
175
|
+
|
|
176
|
+
- `observations` table for storing check results
|
|
177
|
+
- `latest_observations` materialized view for efficient queries
|
|
178
|
+
- Required indexes
|
|
179
|
+
|
|
180
|
+
This is idempotent and safe to call on every startup.
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { loadConfig } from 'c12';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
5
|
+
import { MonitorService } from './service.js';
|
|
6
|
+
import { PostgresObservationStore } from './store.js';
|
|
7
|
+
import { normalizeConfig } from './config.js';
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
const { version } = require('../package.json');
|
|
10
|
+
async function loadMonitorContext(configFile) {
|
|
11
|
+
const { config: rawConfig } = await loadConfig({
|
|
12
|
+
name: 'distribution-monitor',
|
|
13
|
+
configFile,
|
|
14
|
+
dotenv: true,
|
|
15
|
+
});
|
|
16
|
+
if (!rawConfig) {
|
|
17
|
+
console.error('Error: No configuration found.');
|
|
18
|
+
console.error('Create a distribution-monitor.config.ts file or specify --config.');
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
const config = normalizeConfig(rawConfig);
|
|
22
|
+
const databaseUrl = config.databaseUrl ?? process.env.DATABASE_URL;
|
|
23
|
+
if (!databaseUrl) {
|
|
24
|
+
console.error('Error: databaseUrl required (set in config or DATABASE_URL env).');
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
if (config.monitors.length === 0) {
|
|
28
|
+
console.error('Error: No monitors configured.');
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
const store = await PostgresObservationStore.create(databaseUrl);
|
|
32
|
+
const service = new MonitorService({
|
|
33
|
+
store,
|
|
34
|
+
monitors: config.monitors,
|
|
35
|
+
intervalSeconds: config.intervalSeconds,
|
|
36
|
+
timeoutMs: config.timeoutMs,
|
|
37
|
+
});
|
|
38
|
+
return {
|
|
39
|
+
config: { ...config, databaseUrl },
|
|
40
|
+
store,
|
|
41
|
+
service,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const program = new Command();
|
|
45
|
+
program
|
|
46
|
+
.name('distribution-monitor')
|
|
47
|
+
.description('Monitor DCAT distributions (SPARQL endpoints and data dumps)')
|
|
48
|
+
.version(version);
|
|
49
|
+
program
|
|
50
|
+
.command('start')
|
|
51
|
+
.description('Start monitoring all configured distributions')
|
|
52
|
+
.option('-c, --config <path>', 'Config file path')
|
|
53
|
+
.action(async (options) => {
|
|
54
|
+
const { config, store, service } = await loadMonitorContext(options.config);
|
|
55
|
+
await service.checkAll();
|
|
56
|
+
service.start();
|
|
57
|
+
console.log(`Monitoring ${config.monitors.length} distribution(s)...`);
|
|
58
|
+
console.log(`Interval: ${config.intervalSeconds ?? 300} seconds`);
|
|
59
|
+
const shutdown = async () => {
|
|
60
|
+
console.log('\nShutting down...');
|
|
61
|
+
service.stop();
|
|
62
|
+
await store.close();
|
|
63
|
+
process.exit(0);
|
|
64
|
+
};
|
|
65
|
+
process.on('SIGINT', shutdown);
|
|
66
|
+
process.on('SIGTERM', shutdown);
|
|
67
|
+
});
|
|
68
|
+
program
|
|
69
|
+
.command('check [identifier]')
|
|
70
|
+
.description('Run immediate check (all monitors or specific one)')
|
|
71
|
+
.option('-c, --config <path>', 'Config file path')
|
|
72
|
+
.action(async (identifier, options) => {
|
|
73
|
+
const { config, store, service } = await loadMonitorContext(options.config);
|
|
74
|
+
try {
|
|
75
|
+
if (identifier) {
|
|
76
|
+
const monitor = config.monitors.find((m) => m.identifier === identifier);
|
|
77
|
+
if (!monitor) {
|
|
78
|
+
console.error(`Error: Monitor '${identifier}' not found.`);
|
|
79
|
+
console.error('Available monitors:', config.monitors.map((m) => m.identifier).join(', '));
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
console.log(`Checking ${identifier}...`);
|
|
83
|
+
await service.checkNow(identifier);
|
|
84
|
+
console.log(`Check completed for ${identifier}.`);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
console.log(`Checking ${config.monitors.length} distribution(s)...`);
|
|
88
|
+
await service.checkAll();
|
|
89
|
+
console.log('All checks completed.');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
await store.close();
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
program.parse();
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { MonitorConfig } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Shape of a single monitor entry in a configuration file. URLs may be
|
|
4
|
+
* supplied as strings for YAML/JSON ergonomics; they are converted to
|
|
5
|
+
* {@link URL} objects by {@link normalizeConfig}.
|
|
6
|
+
*/
|
|
7
|
+
export interface RawMonitorConfig {
|
|
8
|
+
/** Unique identifier for this monitor. */
|
|
9
|
+
identifier: string;
|
|
10
|
+
/** The distribution to probe. */
|
|
11
|
+
distribution: {
|
|
12
|
+
/** Distribution access URL. */
|
|
13
|
+
accessUrl: string | URL;
|
|
14
|
+
/**
|
|
15
|
+
* Plain content-type (e.g. `application/n-triples`) or DCAT-AP 3.0
|
|
16
|
+
* IANA media type URI.
|
|
17
|
+
*/
|
|
18
|
+
mediaType?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Specification the distribution conforms to, e.g.
|
|
21
|
+
* `https://www.w3.org/TR/sparql11-protocol/` for SPARQL endpoints.
|
|
22
|
+
*/
|
|
23
|
+
conformsTo?: string | URL;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* SPARQL query to run against SPARQL-endpoint distributions. Ignored for
|
|
27
|
+
* data-dump distributions.
|
|
28
|
+
*/
|
|
29
|
+
sparqlQuery?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Configuration for the distribution monitor.
|
|
33
|
+
*/
|
|
34
|
+
export interface DistributionMonitorConfig {
|
|
35
|
+
/** PostgreSQL connection string. */
|
|
36
|
+
databaseUrl?: string;
|
|
37
|
+
/** Polling interval in seconds (default: 300). */
|
|
38
|
+
intervalSeconds?: number;
|
|
39
|
+
/** Request timeout in milliseconds (default: 30 000). */
|
|
40
|
+
timeoutMs?: number;
|
|
41
|
+
/** Monitor definitions. */
|
|
42
|
+
monitors: RawMonitorConfig[];
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Type helper for TypeScript config files.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* // distribution-monitor.config.ts
|
|
50
|
+
* import { defineConfig } from '@lde/distribution-monitor';
|
|
51
|
+
*
|
|
52
|
+
* export default defineConfig({
|
|
53
|
+
* databaseUrl: process.env.DATABASE_URL,
|
|
54
|
+
* intervalSeconds: 300,
|
|
55
|
+
* monitors: [
|
|
56
|
+
* {
|
|
57
|
+
* identifier: 'dbpedia',
|
|
58
|
+
* distribution: {
|
|
59
|
+
* accessUrl: 'https://dbpedia.org/sparql',
|
|
60
|
+
* conformsTo: 'https://www.w3.org/TR/sparql11-protocol/',
|
|
61
|
+
* },
|
|
62
|
+
* sparqlQuery: 'ASK { ?s ?p ?o }',
|
|
63
|
+
* },
|
|
64
|
+
* {
|
|
65
|
+
* identifier: 'my-dump',
|
|
66
|
+
* distribution: {
|
|
67
|
+
* accessUrl: 'https://example.org/data.nt',
|
|
68
|
+
* mediaType: 'application/n-triples',
|
|
69
|
+
* },
|
|
70
|
+
* },
|
|
71
|
+
* ],
|
|
72
|
+
* });
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export declare function defineConfig(config: DistributionMonitorConfig): DistributionMonitorConfig;
|
|
76
|
+
/**
|
|
77
|
+
* Normalize config: convert string URLs to URL objects and construct
|
|
78
|
+
* {@link Distribution} instances for each monitor.
|
|
79
|
+
*/
|
|
80
|
+
export declare function normalizeConfig(raw: DistributionMonitorConfig): {
|
|
81
|
+
databaseUrl?: string;
|
|
82
|
+
intervalSeconds?: number;
|
|
83
|
+
timeoutMs?: number;
|
|
84
|
+
monitors: MonitorConfig[];
|
|
85
|
+
};
|
|
86
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,0CAA0C;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB,iCAAiC;IACjC,YAAY,EAAE;QACZ,+BAA+B;QAC/B,SAAS,EAAE,MAAM,GAAG,GAAG,CAAC;QACxB;;;WAGG;QACH,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB;;;WAGG;QACH,UAAU,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC;KAC3B,CAAC;IACF;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,oCAAoC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kDAAkD;IAClD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,yDAAyD;IACzD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2BAA2B;IAC3B,QAAQ,EAAE,gBAAgB,EAAE,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,yBAAyB,GAChC,yBAAyB,CAE3B;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,yBAAyB,GAAG;IAC/D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,aAAa,EAAE,CAAC;CAC3B,CAWA"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Distribution } from '@lde/dataset';
|
|
2
|
+
/**
|
|
3
|
+
* Type helper for TypeScript config files.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* // distribution-monitor.config.ts
|
|
8
|
+
* import { defineConfig } from '@lde/distribution-monitor';
|
|
9
|
+
*
|
|
10
|
+
* export default defineConfig({
|
|
11
|
+
* databaseUrl: process.env.DATABASE_URL,
|
|
12
|
+
* intervalSeconds: 300,
|
|
13
|
+
* monitors: [
|
|
14
|
+
* {
|
|
15
|
+
* identifier: 'dbpedia',
|
|
16
|
+
* distribution: {
|
|
17
|
+
* accessUrl: 'https://dbpedia.org/sparql',
|
|
18
|
+
* conformsTo: 'https://www.w3.org/TR/sparql11-protocol/',
|
|
19
|
+
* },
|
|
20
|
+
* sparqlQuery: 'ASK { ?s ?p ?o }',
|
|
21
|
+
* },
|
|
22
|
+
* {
|
|
23
|
+
* identifier: 'my-dump',
|
|
24
|
+
* distribution: {
|
|
25
|
+
* accessUrl: 'https://example.org/data.nt',
|
|
26
|
+
* mediaType: 'application/n-triples',
|
|
27
|
+
* },
|
|
28
|
+
* },
|
|
29
|
+
* ],
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export function defineConfig(config) {
|
|
34
|
+
return config;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Normalize config: convert string URLs to URL objects and construct
|
|
38
|
+
* {@link Distribution} instances for each monitor.
|
|
39
|
+
*/
|
|
40
|
+
export function normalizeConfig(raw) {
|
|
41
|
+
return {
|
|
42
|
+
databaseUrl: raw.databaseUrl,
|
|
43
|
+
intervalSeconds: raw.intervalSeconds,
|
|
44
|
+
timeoutMs: raw.timeoutMs,
|
|
45
|
+
monitors: raw.monitors.map((m) => ({
|
|
46
|
+
identifier: m.identifier,
|
|
47
|
+
distribution: toDistribution(m.distribution),
|
|
48
|
+
sparqlQuery: m.sparqlQuery,
|
|
49
|
+
})),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function toDistribution(raw) {
|
|
53
|
+
const accessUrl = typeof raw.accessUrl === 'string' ? new URL(raw.accessUrl) : raw.accessUrl;
|
|
54
|
+
const conformsTo = raw.conformsTo === undefined
|
|
55
|
+
? undefined
|
|
56
|
+
: typeof raw.conformsTo === 'string'
|
|
57
|
+
? new URL(raw.conformsTo)
|
|
58
|
+
: raw.conformsTo;
|
|
59
|
+
return new Distribution(accessUrl, raw.mediaType, conformsTo);
|
|
60
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { MonitorConfig, CheckResult, Observation, ObservationStore, } from './types.js';
|
|
2
|
+
export { PostgresObservationStore } from './store.js';
|
|
3
|
+
export { MonitorService, mapProbeResult, type MonitorServiceOptions, type Probe, } from './service.js';
|
|
4
|
+
export { defineConfig, normalizeConfig, type DistributionMonitorConfig, type RawMonitorConfig, } from './config.js';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,aAAa,EACb,WAAW,EACX,WAAW,EACX,gBAAgB,GACjB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,EACL,cAAc,EACd,cAAc,EACd,KAAK,qBAAqB,EAC1B,KAAK,KAAK,GACX,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,YAAY,EACZ,eAAe,EACf,KAAK,yBAAyB,EAC9B,KAAK,gBAAgB,GACtB,MAAM,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { CheckResult } from './types.js';
|
|
2
|
+
export interface SparqlMonitorOptions {
|
|
3
|
+
/** Timeout in milliseconds for the SPARQL request. */
|
|
4
|
+
timeoutMs?: number;
|
|
5
|
+
/** HTTP headers to include in requests (e.g., User-Agent). */
|
|
6
|
+
headers?: Headers;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Executes SPARQL queries against an endpoint and measures response time.
|
|
10
|
+
*/
|
|
11
|
+
export declare class SparqlMonitor {
|
|
12
|
+
private readonly fetcher;
|
|
13
|
+
private readonly options?;
|
|
14
|
+
constructor(options?: SparqlMonitorOptions);
|
|
15
|
+
/**
|
|
16
|
+
* Execute a SPARQL query against an endpoint and return the result.
|
|
17
|
+
*/
|
|
18
|
+
check(endpointUrl: URL, query: string): Promise<CheckResult>;
|
|
19
|
+
private prepareFetcherForUrl;
|
|
20
|
+
private consumeStream;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=monitor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"monitor.d.ts","sourceRoot":"","sources":["../src/monitor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AA8B9C,MAAM,WAAW,oBAAoB;IACnC,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwB;IAChD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAuB;gBAEpC,OAAO,CAAC,EAAE,oBAAoB;IAQ1C;;OAEG;IACG,KAAK,CAAC,WAAW,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAyClE,OAAO,CAAC,oBAAoB;YAuBd,aAAa;CAW5B"}
|
package/dist/monitor.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { SparqlEndpointFetcher } from 'fetch-sparql-endpoint';
|
|
2
|
+
/**
|
|
3
|
+
* Extract credentials from a URL and convert them to a Basic auth header.
|
|
4
|
+
* Returns a tuple of [URL without credentials, Headers with Authorization].
|
|
5
|
+
*/
|
|
6
|
+
function extractUrlCredentials(url, baseHeaders) {
|
|
7
|
+
const headers = new Headers(baseHeaders);
|
|
8
|
+
if (url.username || url.password) {
|
|
9
|
+
const credentials = `${decodeURIComponent(url.username)}:${decodeURIComponent(url.password)}`;
|
|
10
|
+
headers.set('Authorization', `Basic ${Buffer.from(credentials).toString('base64')}`);
|
|
11
|
+
const cleanUrl = new URL(url.toString());
|
|
12
|
+
cleanUrl.username = '';
|
|
13
|
+
cleanUrl.password = '';
|
|
14
|
+
return [cleanUrl, headers];
|
|
15
|
+
}
|
|
16
|
+
return [url, headers];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Executes SPARQL queries against an endpoint and measures response time.
|
|
20
|
+
*/
|
|
21
|
+
export class SparqlMonitor {
|
|
22
|
+
fetcher;
|
|
23
|
+
options;
|
|
24
|
+
constructor(options) {
|
|
25
|
+
this.options = options;
|
|
26
|
+
this.fetcher = new SparqlEndpointFetcher({
|
|
27
|
+
timeout: options?.timeoutMs ?? 30000,
|
|
28
|
+
defaultHeaders: options?.headers,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Execute a SPARQL query against an endpoint and return the result.
|
|
33
|
+
*/
|
|
34
|
+
async check(endpointUrl, query) {
|
|
35
|
+
const observedAt = new Date(); // UTC
|
|
36
|
+
const startTime = performance.now();
|
|
37
|
+
const [url, fetcher] = this.prepareFetcherForUrl(endpointUrl);
|
|
38
|
+
try {
|
|
39
|
+
const queryType = fetcher.getQueryType(query);
|
|
40
|
+
switch (queryType) {
|
|
41
|
+
case 'ASK':
|
|
42
|
+
await fetcher.fetchAsk(url, query);
|
|
43
|
+
break;
|
|
44
|
+
case 'SELECT':
|
|
45
|
+
await this.consumeStream(await fetcher.fetchBindings(url, query));
|
|
46
|
+
break;
|
|
47
|
+
case 'CONSTRUCT':
|
|
48
|
+
await this.consumeStream(await fetcher.fetchTriples(url, query));
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
const responseTimeMs = Math.round(performance.now() - startTime);
|
|
52
|
+
return {
|
|
53
|
+
success: true,
|
|
54
|
+
responseTimeMs,
|
|
55
|
+
errorMessage: null,
|
|
56
|
+
observedAt,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
const responseTimeMs = Math.round(performance.now() - startTime);
|
|
61
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
62
|
+
return {
|
|
63
|
+
success: false,
|
|
64
|
+
responseTimeMs,
|
|
65
|
+
errorMessage,
|
|
66
|
+
observedAt,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
prepareFetcherForUrl(endpointUrl) {
|
|
71
|
+
const [url, headers] = extractUrlCredentials(endpointUrl, this.options?.headers);
|
|
72
|
+
const hasCredentials = headers.has('Authorization') &&
|
|
73
|
+
!this.options?.headers?.has('Authorization');
|
|
74
|
+
if (!hasCredentials) {
|
|
75
|
+
return [url.toString(), this.fetcher];
|
|
76
|
+
}
|
|
77
|
+
const fetcher = new SparqlEndpointFetcher({
|
|
78
|
+
timeout: this.options?.timeoutMs ?? 30000,
|
|
79
|
+
defaultHeaders: headers,
|
|
80
|
+
});
|
|
81
|
+
return [url.toString(), fetcher];
|
|
82
|
+
}
|
|
83
|
+
async consumeStream(stream) {
|
|
84
|
+
return new Promise((resolve, reject) => {
|
|
85
|
+
stream.on('data', () => {
|
|
86
|
+
// Just consume the data
|
|
87
|
+
});
|
|
88
|
+
stream.on('end', () => resolve());
|
|
89
|
+
stream.on('error', reject);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Observations table — maps to SOSA Observation concept.
|
|
3
|
+
*/
|
|
4
|
+
export declare const observations: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
5
|
+
name: "observations";
|
|
6
|
+
schema: undefined;
|
|
7
|
+
columns: {
|
|
8
|
+
id: import("drizzle-orm/pg-core").PgBuildColumn<"observations", import("drizzle-orm/pg-core").SetHasDefault<import("drizzle-orm/pg-core").SetIsPrimaryKey<import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgUUIDBuilder>>>, {
|
|
9
|
+
name: string;
|
|
10
|
+
tableName: "observations";
|
|
11
|
+
dataType: "string uuid";
|
|
12
|
+
data: string;
|
|
13
|
+
driverParam: string;
|
|
14
|
+
notNull: true;
|
|
15
|
+
hasDefault: true;
|
|
16
|
+
isPrimaryKey: false;
|
|
17
|
+
isAutoincrement: false;
|
|
18
|
+
hasRuntimeDefault: false;
|
|
19
|
+
enumValues: undefined;
|
|
20
|
+
identity: undefined;
|
|
21
|
+
generated: undefined;
|
|
22
|
+
}>;
|
|
23
|
+
observedAt: import("drizzle-orm/pg-core").PgBuildColumn<"observations", import("drizzle-orm/pg-core").SetHasDefault<import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgTimestampBuilder>>, {
|
|
24
|
+
name: string;
|
|
25
|
+
tableName: "observations";
|
|
26
|
+
dataType: "object date";
|
|
27
|
+
data: Date;
|
|
28
|
+
driverParam: string;
|
|
29
|
+
notNull: true;
|
|
30
|
+
hasDefault: true;
|
|
31
|
+
isPrimaryKey: false;
|
|
32
|
+
isAutoincrement: false;
|
|
33
|
+
hasRuntimeDefault: false;
|
|
34
|
+
enumValues: undefined;
|
|
35
|
+
identity: undefined;
|
|
36
|
+
generated: undefined;
|
|
37
|
+
}>;
|
|
38
|
+
monitor: import("drizzle-orm/pg-core").PgBuildColumn<"observations", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgTextBuilder<[string, ...string[]]>>, {
|
|
39
|
+
name: string;
|
|
40
|
+
tableName: "observations";
|
|
41
|
+
dataType: "string";
|
|
42
|
+
data: string;
|
|
43
|
+
driverParam: string;
|
|
44
|
+
notNull: true;
|
|
45
|
+
hasDefault: false;
|
|
46
|
+
isPrimaryKey: false;
|
|
47
|
+
isAutoincrement: false;
|
|
48
|
+
hasRuntimeDefault: false;
|
|
49
|
+
enumValues: undefined;
|
|
50
|
+
identity: undefined;
|
|
51
|
+
generated: undefined;
|
|
52
|
+
}>;
|
|
53
|
+
success: import("drizzle-orm/pg-core").PgBuildColumn<"observations", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgBooleanBuilder>, {
|
|
54
|
+
name: string;
|
|
55
|
+
tableName: "observations";
|
|
56
|
+
dataType: "boolean";
|
|
57
|
+
data: boolean;
|
|
58
|
+
driverParam: boolean;
|
|
59
|
+
notNull: true;
|
|
60
|
+
hasDefault: false;
|
|
61
|
+
isPrimaryKey: false;
|
|
62
|
+
isAutoincrement: false;
|
|
63
|
+
hasRuntimeDefault: false;
|
|
64
|
+
enumValues: undefined;
|
|
65
|
+
identity: undefined;
|
|
66
|
+
generated: undefined;
|
|
67
|
+
}>;
|
|
68
|
+
responseTimeMs: import("drizzle-orm/pg-core").PgBuildColumn<"observations", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgIntegerBuilder>, {
|
|
69
|
+
name: string;
|
|
70
|
+
tableName: "observations";
|
|
71
|
+
dataType: "number int32";
|
|
72
|
+
data: number;
|
|
73
|
+
driverParam: string | number;
|
|
74
|
+
notNull: true;
|
|
75
|
+
hasDefault: false;
|
|
76
|
+
isPrimaryKey: false;
|
|
77
|
+
isAutoincrement: false;
|
|
78
|
+
hasRuntimeDefault: false;
|
|
79
|
+
enumValues: undefined;
|
|
80
|
+
identity: undefined;
|
|
81
|
+
generated: undefined;
|
|
82
|
+
}>;
|
|
83
|
+
errorMessage: import("drizzle-orm/pg-core").PgBuildColumn<"observations", import("drizzle-orm/pg-core").PgTextBuilder<[string, ...string[]]>, {
|
|
84
|
+
name: string;
|
|
85
|
+
tableName: "observations";
|
|
86
|
+
dataType: "string";
|
|
87
|
+
data: string;
|
|
88
|
+
driverParam: string;
|
|
89
|
+
notNull: false;
|
|
90
|
+
hasDefault: false;
|
|
91
|
+
isPrimaryKey: false;
|
|
92
|
+
isAutoincrement: false;
|
|
93
|
+
hasRuntimeDefault: false;
|
|
94
|
+
enumValues: undefined;
|
|
95
|
+
identity: undefined;
|
|
96
|
+
generated: undefined;
|
|
97
|
+
}>;
|
|
98
|
+
};
|
|
99
|
+
dialect: "pg";
|
|
100
|
+
}>;
|
|
101
|
+
/**
|
|
102
|
+
* SQL for refreshing the materialized view.
|
|
103
|
+
*/
|
|
104
|
+
export declare const refreshLatestObservationsViewSql: import("drizzle-orm").SQL<unknown>;
|
|
105
|
+
/**
|
|
106
|
+
* Materialized view for the latest observation per monitor.
|
|
107
|
+
*/
|
|
108
|
+
export declare const latestObservations: import("drizzle-orm/pg-core").PgMaterializedViewWithSelection<"latest_observations", false, {
|
|
109
|
+
id: import("drizzle-orm/pg-core").PgBuildColumn<"latest_observations", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgUUIDBuilder>, {
|
|
110
|
+
name: string;
|
|
111
|
+
tableName: "latest_observations";
|
|
112
|
+
dataType: "string uuid";
|
|
113
|
+
data: string;
|
|
114
|
+
driverParam: string;
|
|
115
|
+
notNull: true;
|
|
116
|
+
hasDefault: false;
|
|
117
|
+
isPrimaryKey: false;
|
|
118
|
+
isAutoincrement: false;
|
|
119
|
+
hasRuntimeDefault: false;
|
|
120
|
+
enumValues: undefined;
|
|
121
|
+
identity: undefined;
|
|
122
|
+
generated: undefined;
|
|
123
|
+
}>;
|
|
124
|
+
monitor: import("drizzle-orm/pg-core").PgBuildColumn<"latest_observations", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgTextBuilder<[string, ...string[]]>>, {
|
|
125
|
+
name: string;
|
|
126
|
+
tableName: "latest_observations";
|
|
127
|
+
dataType: "string";
|
|
128
|
+
data: string;
|
|
129
|
+
driverParam: string;
|
|
130
|
+
notNull: true;
|
|
131
|
+
hasDefault: false;
|
|
132
|
+
isPrimaryKey: false;
|
|
133
|
+
isAutoincrement: false;
|
|
134
|
+
hasRuntimeDefault: false;
|
|
135
|
+
enumValues: undefined;
|
|
136
|
+
identity: undefined;
|
|
137
|
+
generated: undefined;
|
|
138
|
+
}>;
|
|
139
|
+
observedAt: import("drizzle-orm/pg-core").PgBuildColumn<"latest_observations", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgTimestampBuilder>, {
|
|
140
|
+
name: string;
|
|
141
|
+
tableName: "latest_observations";
|
|
142
|
+
dataType: "object date";
|
|
143
|
+
data: Date;
|
|
144
|
+
driverParam: string;
|
|
145
|
+
notNull: true;
|
|
146
|
+
hasDefault: false;
|
|
147
|
+
isPrimaryKey: false;
|
|
148
|
+
isAutoincrement: false;
|
|
149
|
+
hasRuntimeDefault: false;
|
|
150
|
+
enumValues: undefined;
|
|
151
|
+
identity: undefined;
|
|
152
|
+
generated: undefined;
|
|
153
|
+
}>;
|
|
154
|
+
success: import("drizzle-orm/pg-core").PgBuildColumn<"latest_observations", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgBooleanBuilder>, {
|
|
155
|
+
name: string;
|
|
156
|
+
tableName: "latest_observations";
|
|
157
|
+
dataType: "boolean";
|
|
158
|
+
data: boolean;
|
|
159
|
+
driverParam: boolean;
|
|
160
|
+
notNull: true;
|
|
161
|
+
hasDefault: false;
|
|
162
|
+
isPrimaryKey: false;
|
|
163
|
+
isAutoincrement: false;
|
|
164
|
+
hasRuntimeDefault: false;
|
|
165
|
+
enumValues: undefined;
|
|
166
|
+
identity: undefined;
|
|
167
|
+
generated: undefined;
|
|
168
|
+
}>;
|
|
169
|
+
responseTimeMs: import("drizzle-orm/pg-core").PgBuildColumn<"latest_observations", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgIntegerBuilder>, {
|
|
170
|
+
name: string;
|
|
171
|
+
tableName: "latest_observations";
|
|
172
|
+
dataType: "number int32";
|
|
173
|
+
data: number;
|
|
174
|
+
driverParam: string | number;
|
|
175
|
+
notNull: true;
|
|
176
|
+
hasDefault: false;
|
|
177
|
+
isPrimaryKey: false;
|
|
178
|
+
isAutoincrement: false;
|
|
179
|
+
hasRuntimeDefault: false;
|
|
180
|
+
enumValues: undefined;
|
|
181
|
+
identity: undefined;
|
|
182
|
+
generated: undefined;
|
|
183
|
+
}>;
|
|
184
|
+
errorMessage: import("drizzle-orm/pg-core").PgBuildColumn<"latest_observations", import("drizzle-orm/pg-core").PgTextBuilder<[string, ...string[]]>, {
|
|
185
|
+
name: string;
|
|
186
|
+
tableName: "latest_observations";
|
|
187
|
+
dataType: "string";
|
|
188
|
+
data: string;
|
|
189
|
+
driverParam: string;
|
|
190
|
+
notNull: false;
|
|
191
|
+
hasDefault: false;
|
|
192
|
+
isPrimaryKey: false;
|
|
193
|
+
isAutoincrement: false;
|
|
194
|
+
hasRuntimeDefault: false;
|
|
195
|
+
enumValues: undefined;
|
|
196
|
+
identity: undefined;
|
|
197
|
+
generated: undefined;
|
|
198
|
+
}>;
|
|
199
|
+
}>;
|
|
200
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAqBA;;GAEG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAexB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,gCAAgC,oCAE5C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAO7B,CAAC"}
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { boolean, index, integer, pgMaterializedView, pgTable, text, timestamp, uuid, } from 'drizzle-orm/pg-core';
|
|
2
|
+
import { sql } from 'drizzle-orm';
|
|
3
|
+
const columns = {
|
|
4
|
+
id: uuid('id').notNull(),
|
|
5
|
+
monitor: text('monitor').notNull(),
|
|
6
|
+
observedAt: timestamp('observed_at', { mode: 'date' }).notNull(),
|
|
7
|
+
success: boolean('success').notNull(),
|
|
8
|
+
responseTimeMs: integer('response_time_ms').notNull(),
|
|
9
|
+
errorMessage: text('error_message'),
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Observations table — maps to SOSA Observation concept.
|
|
13
|
+
*/
|
|
14
|
+
export const observations = pgTable('observations', {
|
|
15
|
+
...columns,
|
|
16
|
+
id: columns.id.primaryKey().defaultRandom(),
|
|
17
|
+
observedAt: columns.observedAt.defaultNow(),
|
|
18
|
+
}, (table) => [
|
|
19
|
+
index('observations_monitor_idx').on(table.monitor),
|
|
20
|
+
index('observations_observed_at_idx').on(table.observedAt),
|
|
21
|
+
index('observations_monitor_observed_at_idx').on(table.monitor, sql `${table.observedAt} DESC`),
|
|
22
|
+
]);
|
|
23
|
+
/**
|
|
24
|
+
* SQL for refreshing the materialized view.
|
|
25
|
+
*/
|
|
26
|
+
export const refreshLatestObservationsViewSql = sql `
|
|
27
|
+
REFRESH MATERIALIZED VIEW CONCURRENTLY latest_observations
|
|
28
|
+
`;
|
|
29
|
+
/**
|
|
30
|
+
* Materialized view for the latest observation per monitor.
|
|
31
|
+
*/
|
|
32
|
+
export const latestObservations = pgMaterializedView('latest_observations', columns).as(sql `
|
|
33
|
+
SELECT DISTINCT ON (monitor) *
|
|
34
|
+
FROM ${observations}
|
|
35
|
+
ORDER BY monitor, observed_at DESC
|
|
36
|
+
`);
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { probe, type ProbeResultType } from '@lde/distribution-probe';
|
|
2
|
+
import type { CheckResult, MonitorConfig, ObservationStore } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Function signature for a probe. Matches `probe()` from
|
|
5
|
+
* `@lde/distribution-probe`; injectable so tests can stub it.
|
|
6
|
+
*/
|
|
7
|
+
export type Probe = typeof probe;
|
|
8
|
+
export interface MonitorServiceOptions {
|
|
9
|
+
/** Store for persisting observations. */
|
|
10
|
+
store: ObservationStore;
|
|
11
|
+
/** Monitor configurations. */
|
|
12
|
+
monitors: MonitorConfig[];
|
|
13
|
+
/** Polling interval in seconds (default: 300). */
|
|
14
|
+
intervalSeconds?: number;
|
|
15
|
+
/** Request timeout in milliseconds passed to the probe (default: 30 000). */
|
|
16
|
+
timeoutMs?: number;
|
|
17
|
+
/** HTTP headers forwarded to every probe request (e.g. User-Agent). */
|
|
18
|
+
headers?: Headers;
|
|
19
|
+
/**
|
|
20
|
+
* Override the probe function. Mostly useful for tests; defaults to
|
|
21
|
+
* {@link probe} from `@lde/distribution-probe`.
|
|
22
|
+
*/
|
|
23
|
+
probe?: Probe;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Orchestrates monitoring of multiple DCAT distributions.
|
|
27
|
+
*/
|
|
28
|
+
export declare class MonitorService {
|
|
29
|
+
private readonly store;
|
|
30
|
+
private readonly probe;
|
|
31
|
+
private readonly configs;
|
|
32
|
+
private readonly intervalSeconds;
|
|
33
|
+
private readonly timeoutMs;
|
|
34
|
+
private readonly headers?;
|
|
35
|
+
private job;
|
|
36
|
+
constructor(options: MonitorServiceOptions);
|
|
37
|
+
/**
|
|
38
|
+
* Perform an immediate check for a monitor.
|
|
39
|
+
*/
|
|
40
|
+
checkNow(identifier: string): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Perform an immediate check for all monitors.
|
|
43
|
+
*/
|
|
44
|
+
checkAll(): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Start monitoring all configured distributions.
|
|
47
|
+
*/
|
|
48
|
+
start(): void;
|
|
49
|
+
/**
|
|
50
|
+
* Stop monitoring.
|
|
51
|
+
*/
|
|
52
|
+
stop(): void;
|
|
53
|
+
/**
|
|
54
|
+
* Check whether monitoring is running.
|
|
55
|
+
*/
|
|
56
|
+
isRunning(): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Convert seconds to a cron expression.
|
|
59
|
+
*/
|
|
60
|
+
private secondsToCron;
|
|
61
|
+
private performCheck;
|
|
62
|
+
private refreshView;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Collapse a {@link ProbeResultType} into a {@link CheckResult}. Network
|
|
66
|
+
* errors become `success: false` with the network error message; HTTP or
|
|
67
|
+
* body-validation failures become `success: false` with the probe's
|
|
68
|
+
* failureReason (falling back to joined warnings or the HTTP status) as the
|
|
69
|
+
* error message; everything else is `success: true`.
|
|
70
|
+
*/
|
|
71
|
+
export declare function mapProbeResult(result: ProbeResultType, observedAt: Date): CheckResult;
|
|
72
|
+
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,EAEL,KAAK,eAAe,EAErB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE/E;;;GAGG;AACH,MAAM,MAAM,KAAK,GAAG,OAAO,KAAK,CAAC;AAEjC,MAAM,WAAW,qBAAqB;IACpC,yCAAyC;IACzC,KAAK,EAAE,gBAAgB,CAAC;IACxB,8BAA8B;IAC9B,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,kDAAkD;IAClD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;OAGG;IACH,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AAID;;GAEG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAmB;IACzC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkB;IAC1C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAU;IACnC,OAAO,CAAC,GAAG,CAAwB;gBAEvB,OAAO,EAAE,qBAAqB;IAS1C;;OAEG;IACG,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASjD;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAK/B;;OAEG;IACH,KAAK,IAAI,IAAI;IAQb;;OAEG;IACH,IAAI,IAAI,IAAI;IAOZ;;OAEG;IACH,SAAS,IAAI,OAAO;IAIpB;;OAEG;IACH,OAAO,CAAC,aAAa;YAYP,YAAY;YAWZ,WAAW;CAO1B;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,eAAe,EACvB,UAAU,EAAE,IAAI,GACf,WAAW,CA+Bb"}
|
package/dist/service.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { CronJob } from 'cron';
|
|
2
|
+
import { probe, NetworkError, } from '@lde/distribution-probe';
|
|
3
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
4
|
+
/**
|
|
5
|
+
* Orchestrates monitoring of multiple DCAT distributions.
|
|
6
|
+
*/
|
|
7
|
+
export class MonitorService {
|
|
8
|
+
store;
|
|
9
|
+
probe;
|
|
10
|
+
configs;
|
|
11
|
+
intervalSeconds;
|
|
12
|
+
timeoutMs;
|
|
13
|
+
headers;
|
|
14
|
+
job = null;
|
|
15
|
+
constructor(options) {
|
|
16
|
+
this.store = options.store;
|
|
17
|
+
this.probe = options.probe ?? probe;
|
|
18
|
+
this.configs = options.monitors;
|
|
19
|
+
this.intervalSeconds = options.intervalSeconds ?? 300;
|
|
20
|
+
this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
21
|
+
this.headers = options.headers;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Perform an immediate check for a monitor.
|
|
25
|
+
*/
|
|
26
|
+
async checkNow(identifier) {
|
|
27
|
+
const config = this.configs.find((c) => c.identifier === identifier);
|
|
28
|
+
if (!config) {
|
|
29
|
+
throw new Error(`Monitor not found: ${identifier}`);
|
|
30
|
+
}
|
|
31
|
+
await this.performCheck(config);
|
|
32
|
+
await this.refreshView();
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Perform an immediate check for all monitors.
|
|
36
|
+
*/
|
|
37
|
+
async checkAll() {
|
|
38
|
+
await Promise.all(this.configs.map((config) => this.performCheck(config)));
|
|
39
|
+
await this.refreshView();
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Start monitoring all configured distributions.
|
|
43
|
+
*/
|
|
44
|
+
start() {
|
|
45
|
+
if (!this.job) {
|
|
46
|
+
const cronExpression = this.secondsToCron(this.intervalSeconds);
|
|
47
|
+
this.job = new CronJob(cronExpression, () => this.checkAll());
|
|
48
|
+
this.job.start();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Stop monitoring.
|
|
53
|
+
*/
|
|
54
|
+
stop() {
|
|
55
|
+
if (this.job) {
|
|
56
|
+
this.job.stop();
|
|
57
|
+
this.job = null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Check whether monitoring is running.
|
|
62
|
+
*/
|
|
63
|
+
isRunning() {
|
|
64
|
+
return this.job !== null;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Convert seconds to a cron expression.
|
|
68
|
+
*/
|
|
69
|
+
secondsToCron(seconds) {
|
|
70
|
+
if (seconds < 60) {
|
|
71
|
+
return `*/${seconds} * * * * *`;
|
|
72
|
+
}
|
|
73
|
+
const minutes = Math.floor(seconds / 60);
|
|
74
|
+
if (minutes < 60) {
|
|
75
|
+
return `0 */${minutes} * * * *`;
|
|
76
|
+
}
|
|
77
|
+
const hours = Math.floor(minutes / 60);
|
|
78
|
+
return `0 0 */${hours} * * *`;
|
|
79
|
+
}
|
|
80
|
+
async performCheck(config) {
|
|
81
|
+
const observedAt = new Date();
|
|
82
|
+
const options = { timeoutMs: this.timeoutMs };
|
|
83
|
+
if (this.headers)
|
|
84
|
+
options.headers = this.headers;
|
|
85
|
+
if (config.sparqlQuery)
|
|
86
|
+
options.sparqlQuery = config.sparqlQuery;
|
|
87
|
+
const result = await this.probe(config.distribution, options);
|
|
88
|
+
const checkResult = mapProbeResult(result, observedAt);
|
|
89
|
+
await this.store.store({ monitor: config.identifier, ...checkResult });
|
|
90
|
+
}
|
|
91
|
+
async refreshView() {
|
|
92
|
+
try {
|
|
93
|
+
await this.store.refreshLatestObservationsView();
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// View refresh failure is not critical
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Collapse a {@link ProbeResultType} into a {@link CheckResult}. Network
|
|
102
|
+
* errors become `success: false` with the network error message; HTTP or
|
|
103
|
+
* body-validation failures become `success: false` with the probe's
|
|
104
|
+
* failureReason (falling back to joined warnings or the HTTP status) as the
|
|
105
|
+
* error message; everything else is `success: true`.
|
|
106
|
+
*/
|
|
107
|
+
export function mapProbeResult(result, observedAt) {
|
|
108
|
+
if (result instanceof NetworkError) {
|
|
109
|
+
return {
|
|
110
|
+
success: false,
|
|
111
|
+
responseTimeMs: result.responseTimeMs,
|
|
112
|
+
errorMessage: result.message,
|
|
113
|
+
observedAt,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
if (result.isSuccess()) {
|
|
117
|
+
return {
|
|
118
|
+
success: true,
|
|
119
|
+
responseTimeMs: result.responseTimeMs,
|
|
120
|
+
errorMessage: null,
|
|
121
|
+
observedAt,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
const errorMessage = result.failureReason ??
|
|
125
|
+
(result.warnings.length > 0
|
|
126
|
+
? result.warnings.join('; ')
|
|
127
|
+
: `HTTP ${result.statusCode} ${result.statusText}`);
|
|
128
|
+
return {
|
|
129
|
+
success: false,
|
|
130
|
+
responseTimeMs: result.responseTimeMs,
|
|
131
|
+
errorMessage,
|
|
132
|
+
observedAt,
|
|
133
|
+
};
|
|
134
|
+
}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ObservationStore, Observation } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* PostgreSQL implementation of the ObservationStore interface.
|
|
4
|
+
*/
|
|
5
|
+
export declare class PostgresObservationStore implements ObservationStore {
|
|
6
|
+
private db;
|
|
7
|
+
private constructor();
|
|
8
|
+
/**
|
|
9
|
+
* Create a new store and initialize the database schema.
|
|
10
|
+
*
|
|
11
|
+
* Uses drizzle-kit's generateMigration to create schema from code.
|
|
12
|
+
* This approach works around a bug in pushSchema where the execute()
|
|
13
|
+
* return format doesn't match what drizzle-kit expects.
|
|
14
|
+
* See: https://github.com/drizzle-team/drizzle-orm/issues/5293
|
|
15
|
+
*/
|
|
16
|
+
static create(connectionString: string): Promise<PostgresObservationStore>;
|
|
17
|
+
close(): Promise<void>;
|
|
18
|
+
getLatest(): Promise<Map<string, Observation>>;
|
|
19
|
+
get(id: string): Promise<Observation | null>;
|
|
20
|
+
store(observation: Omit<Observation, 'id'>): Promise<Observation>;
|
|
21
|
+
refreshLatestObservationsView(): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAKhE;;GAEG;AACH,qBAAa,wBAAyB,YAAW,gBAAgB;IAC/D,OAAO,CAAC,EAAE,CAAqB;IAE/B,OAAO;IAIP;;;;;;;OAOG;WACU,MAAM,CACjB,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAAC,wBAAwB,CAAC;IAwC9B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtB,SAAS,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAK9C,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAS5C,KAAK,CAAC,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC;IAQjE,6BAA6B,IAAI,OAAO,CAAC,IAAI,CAAC;CAGrD"}
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { drizzle } from 'drizzle-orm/postgres-js';
|
|
2
|
+
import { eq, sql } from 'drizzle-orm';
|
|
3
|
+
import * as schema from './schema.js';
|
|
4
|
+
const { observations, latestObservations, refreshLatestObservationsViewSql } = schema;
|
|
5
|
+
/**
|
|
6
|
+
* PostgreSQL implementation of the ObservationStore interface.
|
|
7
|
+
*/
|
|
8
|
+
export class PostgresObservationStore {
|
|
9
|
+
db;
|
|
10
|
+
constructor(connectionString) {
|
|
11
|
+
this.db = drizzle(connectionString);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Create a new store and initialize the database schema.
|
|
15
|
+
*
|
|
16
|
+
* Uses drizzle-kit's generateMigration to create schema from code.
|
|
17
|
+
* This approach works around a bug in pushSchema where the execute()
|
|
18
|
+
* return format doesn't match what drizzle-kit expects.
|
|
19
|
+
* See: https://github.com/drizzle-team/drizzle-orm/issues/5293
|
|
20
|
+
*/
|
|
21
|
+
static async create(connectionString) {
|
|
22
|
+
const store = new PostgresObservationStore(connectionString);
|
|
23
|
+
const { generateDrizzleJson, generateMigration } = await import('drizzle-kit/api-postgres');
|
|
24
|
+
// Generate migration from empty state to our schema
|
|
25
|
+
const empty = await generateDrizzleJson({});
|
|
26
|
+
const target = await generateDrizzleJson(schema, empty.id);
|
|
27
|
+
const migration = await generateMigration(empty, target);
|
|
28
|
+
// Execute each statement, ignoring "already exists" errors for idempotency
|
|
29
|
+
for (const statement of migration) {
|
|
30
|
+
try {
|
|
31
|
+
await store.db.execute(sql.raw(statement));
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
// Check both direct error and cause for "already exists"
|
|
35
|
+
const isAlreadyExists = (e) => {
|
|
36
|
+
if (!(e instanceof Error))
|
|
37
|
+
return false;
|
|
38
|
+
if (e.message.includes('already exists'))
|
|
39
|
+
return true;
|
|
40
|
+
if ('cause' in e)
|
|
41
|
+
return isAlreadyExists(e.cause);
|
|
42
|
+
return false;
|
|
43
|
+
};
|
|
44
|
+
if (!isAlreadyExists(error)) {
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Create unique index on materialized view for CONCURRENTLY refresh
|
|
50
|
+
try {
|
|
51
|
+
await store.db.execute(sql `CREATE UNIQUE INDEX latest_observations_monitor_idx ON latest_observations (monitor)`);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Index may already exist
|
|
55
|
+
}
|
|
56
|
+
return store;
|
|
57
|
+
}
|
|
58
|
+
async close() {
|
|
59
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
60
|
+
await this.db.$client.end();
|
|
61
|
+
}
|
|
62
|
+
async getLatest() {
|
|
63
|
+
const rows = await this.db.select().from(latestObservations);
|
|
64
|
+
return new Map(rows.map((row) => [row.monitor, row]));
|
|
65
|
+
}
|
|
66
|
+
async get(id) {
|
|
67
|
+
const rows = await this.db
|
|
68
|
+
.select()
|
|
69
|
+
.from(observations)
|
|
70
|
+
.where(eq(observations.id, id))
|
|
71
|
+
.limit(1);
|
|
72
|
+
return rows[0] ?? null;
|
|
73
|
+
}
|
|
74
|
+
async store(observation) {
|
|
75
|
+
const rows = await this.db
|
|
76
|
+
.insert(observations)
|
|
77
|
+
.values(observation)
|
|
78
|
+
.returning();
|
|
79
|
+
return rows[0];
|
|
80
|
+
}
|
|
81
|
+
async refreshLatestObservationsView() {
|
|
82
|
+
await this.db.execute(refreshLatestObservationsViewSql);
|
|
83
|
+
}
|
|
84
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Distribution } from '@lde/dataset';
|
|
2
|
+
/**
|
|
3
|
+
* Configuration for a single monitor.
|
|
4
|
+
*
|
|
5
|
+
* Monitors target any DCAT {@link Distribution}: a SPARQL endpoint (in which
|
|
6
|
+
* case `sparqlQuery` is used for the probe) or a data dump (in which case
|
|
7
|
+
* `sparqlQuery` is ignored and the distribution is fetched with HEAD/GET).
|
|
8
|
+
*/
|
|
9
|
+
export interface MonitorConfig {
|
|
10
|
+
/** Unique identifier for this monitor. */
|
|
11
|
+
identifier: string;
|
|
12
|
+
/** The DCAT distribution to probe. */
|
|
13
|
+
distribution: Distribution;
|
|
14
|
+
/**
|
|
15
|
+
* SPARQL query to run against the endpoint. Only meaningful when the
|
|
16
|
+
* distribution is a SPARQL endpoint. Defaults to a minimal availability
|
|
17
|
+
* probe (`SELECT * { ?s ?p ?o } LIMIT 1`).
|
|
18
|
+
*/
|
|
19
|
+
sparqlQuery?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Result of a single check against a distribution.
|
|
23
|
+
*/
|
|
24
|
+
export interface CheckResult {
|
|
25
|
+
/** Whether the distribution responded successfully. */
|
|
26
|
+
success: boolean;
|
|
27
|
+
/** Response time in milliseconds. */
|
|
28
|
+
responseTimeMs: number;
|
|
29
|
+
/** Error message if the check failed. */
|
|
30
|
+
errorMessage: string | null;
|
|
31
|
+
/** Timestamp when the response was received (UTC). */
|
|
32
|
+
observedAt: Date;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Observation record from the database.
|
|
36
|
+
*/
|
|
37
|
+
export interface Observation {
|
|
38
|
+
id: string;
|
|
39
|
+
monitor: string;
|
|
40
|
+
observedAt: Date;
|
|
41
|
+
success: boolean;
|
|
42
|
+
responseTimeMs: number;
|
|
43
|
+
errorMessage: string | null;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Store interface for persisting observations.
|
|
47
|
+
*/
|
|
48
|
+
export interface ObservationStore {
|
|
49
|
+
/**
|
|
50
|
+
* Get the latest observation for each identifier.
|
|
51
|
+
* Returns a map keyed by identifier.
|
|
52
|
+
*/
|
|
53
|
+
getLatest(): Promise<Map<string, Observation>>;
|
|
54
|
+
/**
|
|
55
|
+
* Get a specific observation by ID.
|
|
56
|
+
*/
|
|
57
|
+
get(id: string): Promise<Observation | null>;
|
|
58
|
+
/**
|
|
59
|
+
* Save a new observation.
|
|
60
|
+
*/
|
|
61
|
+
store(observation: Omit<Observation, 'id'>): Promise<Observation>;
|
|
62
|
+
/**
|
|
63
|
+
* Refresh the latest_observations materialized view.
|
|
64
|
+
*/
|
|
65
|
+
refreshLatestObservationsView(): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Close the database connection.
|
|
68
|
+
*/
|
|
69
|
+
close(): Promise<void>;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,0CAA0C;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB,sCAAsC;IACtC,YAAY,EAAE,YAAY,CAAC;IAC3B;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,uDAAuD;IACvD,OAAO,EAAE,OAAO,CAAC;IACjB,qCAAqC;IACrC,cAAc,EAAE,MAAM,CAAC;IACvB,yCAAyC;IACzC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,sDAAsD;IACtD,UAAU,EAAE,IAAI,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,IAAI,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,SAAS,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC;IAE/C;;OAEG;IACH,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAE7C;;OAEG;IACH,KAAK,CAAC,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAElE;;OAEG;IACH,6BAA6B,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAE/C;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lde/distribution-monitor",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Monitor DCAT distributions (SPARQL endpoints and data dumps) with periodic probes",
|
|
5
|
+
"repository": {
|
|
6
|
+
"url": "git+https://github.com/ldelements/lde.git",
|
|
7
|
+
"directory": "packages/distribution-monitor"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"type": "module",
|
|
11
|
+
"exports": {
|
|
12
|
+
"./package.json": "./package.json",
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js",
|
|
16
|
+
"development": "./src/index.ts",
|
|
17
|
+
"default": "./dist/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"main": "./dist/index.js",
|
|
21
|
+
"module": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"bin": {
|
|
24
|
+
"distribution-monitor": "dist/cli.js"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"!**/*.tsbuildinfo"
|
|
29
|
+
],
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@lde/dataset": "0.7.3",
|
|
32
|
+
"@lde/distribution-probe": "0.1.2",
|
|
33
|
+
"c12": "^3.3.4",
|
|
34
|
+
"commander": "^14.0.3",
|
|
35
|
+
"cron": "^4.1.0",
|
|
36
|
+
"drizzle-kit": "1.0.0-beta.20",
|
|
37
|
+
"drizzle-orm": "1.0.0-beta.22-41a7d21",
|
|
38
|
+
"postgres": "^3.4.9",
|
|
39
|
+
"tslib": "^2.3.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@testcontainers/postgresql": "^11.14.0"
|
|
43
|
+
}
|
|
44
|
+
}
|