deadpipe 1.0.0
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 +179 -0
- package/dist/index.d.mts +105 -0
- package/dist/index.d.ts +105 -0
- package/dist/index.js +164 -0
- package/dist/index.mjs +137 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# Deadpipe Node.js SDK
|
|
2
|
+
|
|
3
|
+
Dead simple pipeline monitoring. Know when your pipelines die.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install deadpipe
|
|
9
|
+
# or
|
|
10
|
+
yarn add deadpipe
|
|
11
|
+
# or
|
|
12
|
+
pnpm add deadpipe
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
### Option 1: Wrapper (Recommended)
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { Deadpipe } from 'deadpipe';
|
|
21
|
+
|
|
22
|
+
const dp = new Deadpipe('your-api-key');
|
|
23
|
+
|
|
24
|
+
// Wrap any async function
|
|
25
|
+
await dp.run('daily-sales-etl', async () => {
|
|
26
|
+
await processData();
|
|
27
|
+
return { recordsProcessed: 1500 }; // Optional: track records
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Deadpipe automatically sends success/failed heartbeat when done
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Option 2: Create a Wrapped Function
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { Deadpipe } from 'deadpipe';
|
|
37
|
+
|
|
38
|
+
const dp = new Deadpipe('your-api-key');
|
|
39
|
+
|
|
40
|
+
// Create a wrapped version of your function
|
|
41
|
+
const myPipeline = dp.wrap('hourly-sync', async () => {
|
|
42
|
+
await syncData();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Call it later
|
|
46
|
+
await myPipeline();
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Option 3: Manual Ping
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { Deadpipe } from 'deadpipe';
|
|
53
|
+
|
|
54
|
+
const dp = new Deadpipe('your-api-key');
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
await runMyJob();
|
|
58
|
+
await dp.ping('my-job', { status: 'success', recordsProcessed: 1000 });
|
|
59
|
+
} catch (error) {
|
|
60
|
+
await dp.ping('my-job', { status: 'failed' });
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Option 4: Environment Variable
|
|
66
|
+
|
|
67
|
+
Set `DEADPIPE_API_KEY` and use module-level functions:
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { run, ping } from 'deadpipe';
|
|
71
|
+
|
|
72
|
+
// Uses DEADPIPE_API_KEY from environment
|
|
73
|
+
await run('my-pipeline', async () => {
|
|
74
|
+
await doWork();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Or manual ping
|
|
78
|
+
await ping('my-pipeline', { status: 'success' });
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Express.js / API Routes
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { Deadpipe } from 'deadpipe';
|
|
85
|
+
|
|
86
|
+
const dp = new Deadpipe(process.env.DEADPIPE_API_KEY);
|
|
87
|
+
|
|
88
|
+
// Wrap a cron job handler
|
|
89
|
+
export const handler = dp.wrap('daily-report', async () => {
|
|
90
|
+
const report = await generateReport();
|
|
91
|
+
await sendReport(report);
|
|
92
|
+
return { recordsProcessed: report.rows.length };
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Next.js API Routes
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import { Deadpipe } from 'deadpipe';
|
|
100
|
+
|
|
101
|
+
const dp = new Deadpipe(process.env.DEADPIPE_API_KEY);
|
|
102
|
+
|
|
103
|
+
export async function POST(request: Request) {
|
|
104
|
+
return dp.run('webhook-processor', async () => {
|
|
105
|
+
const data = await request.json();
|
|
106
|
+
await processWebhook(data);
|
|
107
|
+
return Response.json({ success: true });
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## GitHub Actions
|
|
113
|
+
|
|
114
|
+
```yaml
|
|
115
|
+
- name: Run ETL
|
|
116
|
+
env:
|
|
117
|
+
DEADPIPE_API_KEY: ${{ secrets.DEADPIPE_API_KEY }}
|
|
118
|
+
run: node scripts/etl.js
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
```javascript
|
|
122
|
+
// scripts/etl.js
|
|
123
|
+
import { run } from 'deadpipe';
|
|
124
|
+
|
|
125
|
+
await run('github-actions-etl', async () => {
|
|
126
|
+
// Your ETL code
|
|
127
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## API Reference
|
|
131
|
+
|
|
132
|
+
### `new Deadpipe(apiKey, options)`
|
|
133
|
+
|
|
134
|
+
Create a client instance.
|
|
135
|
+
|
|
136
|
+
- `apiKey`: Your API key (or set `DEADPIPE_API_KEY` env var)
|
|
137
|
+
- `options.baseUrl`: Override for self-hosted (default: `https://www.deadpipe.com/api/v1`)
|
|
138
|
+
- `options.timeout`: Request timeout in ms (default: 10000)
|
|
139
|
+
|
|
140
|
+
### `dp.ping(pipelineId, options)`
|
|
141
|
+
|
|
142
|
+
Send a heartbeat.
|
|
143
|
+
|
|
144
|
+
- `pipelineId`: Unique identifier for this pipeline
|
|
145
|
+
- `options.status`: `'success'` or `'failed'` (default: `'success'`)
|
|
146
|
+
- `options.durationMs`: How long the run took
|
|
147
|
+
- `options.recordsProcessed`: Number of records
|
|
148
|
+
- `options.appName`: Group pipelines under an app
|
|
149
|
+
|
|
150
|
+
Returns: `Promise<HeartbeatResponse | null>`
|
|
151
|
+
|
|
152
|
+
### `dp.run(pipelineId, fn, options)`
|
|
153
|
+
|
|
154
|
+
Run a function with automatic heartbeat.
|
|
155
|
+
|
|
156
|
+
- `pipelineId`: Unique identifier for this pipeline
|
|
157
|
+
- `fn`: Async function to run
|
|
158
|
+
- `options.appName`: Group pipelines under an app
|
|
159
|
+
|
|
160
|
+
Returns: `Promise<T>` (result of fn)
|
|
161
|
+
|
|
162
|
+
### `dp.wrap(pipelineId, fn, options)`
|
|
163
|
+
|
|
164
|
+
Create a wrapped version of a function.
|
|
165
|
+
|
|
166
|
+
Returns: A new function that auto-sends heartbeats.
|
|
167
|
+
|
|
168
|
+
## Zero Dependencies
|
|
169
|
+
|
|
170
|
+
This SDK has zero runtime dependencies. It uses native `fetch` (Node 18+).
|
|
171
|
+
|
|
172
|
+
## TypeScript
|
|
173
|
+
|
|
174
|
+
Full TypeScript support with type definitions included.
|
|
175
|
+
|
|
176
|
+
## License
|
|
177
|
+
|
|
178
|
+
MIT
|
|
179
|
+
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deadpipe - Dead simple pipeline monitoring.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* import { Deadpipe } from 'deadpipe';
|
|
6
|
+
*
|
|
7
|
+
* const dp = new Deadpipe('your-api-key');
|
|
8
|
+
*
|
|
9
|
+
* // Option 1: Wrapper
|
|
10
|
+
* await dp.run('my-pipeline', async () => {
|
|
11
|
+
* // your code here
|
|
12
|
+
* });
|
|
13
|
+
*
|
|
14
|
+
* // Option 2: Manual
|
|
15
|
+
* await dp.ping('my-pipeline', { status: 'success' });
|
|
16
|
+
*/
|
|
17
|
+
type Status = 'success' | 'failed';
|
|
18
|
+
interface PingOptions {
|
|
19
|
+
status?: Status;
|
|
20
|
+
durationMs?: number;
|
|
21
|
+
recordsProcessed?: number;
|
|
22
|
+
appName?: string;
|
|
23
|
+
}
|
|
24
|
+
interface DeadpipeOptions {
|
|
25
|
+
apiKey?: string;
|
|
26
|
+
baseUrl?: string;
|
|
27
|
+
timeout?: number;
|
|
28
|
+
}
|
|
29
|
+
interface HeartbeatResponse {
|
|
30
|
+
received: boolean;
|
|
31
|
+
pipeline_id: string;
|
|
32
|
+
timestamp: string;
|
|
33
|
+
freshness: 'fresh' | 'stale' | 'critical';
|
|
34
|
+
next_expected: string;
|
|
35
|
+
created?: boolean;
|
|
36
|
+
message?: string;
|
|
37
|
+
}
|
|
38
|
+
declare class Deadpipe {
|
|
39
|
+
private apiKey;
|
|
40
|
+
private baseUrl;
|
|
41
|
+
private timeout;
|
|
42
|
+
/**
|
|
43
|
+
* Create a Deadpipe client.
|
|
44
|
+
*
|
|
45
|
+
* @param apiKey - Your Deadpipe API key. Falls back to DEADPIPE_API_KEY env var.
|
|
46
|
+
* @param options - Configuration options.
|
|
47
|
+
*/
|
|
48
|
+
constructor(apiKey?: string, options?: Omit<DeadpipeOptions, 'apiKey'>);
|
|
49
|
+
/**
|
|
50
|
+
* Send a heartbeat ping for a pipeline.
|
|
51
|
+
*
|
|
52
|
+
* @param pipelineId - Unique identifier for this pipeline.
|
|
53
|
+
* @param options - Ping options.
|
|
54
|
+
* @returns The heartbeat response, or null if the request failed.
|
|
55
|
+
*/
|
|
56
|
+
ping(pipelineId: string, options?: PingOptions): Promise<HeartbeatResponse | null>;
|
|
57
|
+
/**
|
|
58
|
+
* Run a function with automatic heartbeat on completion.
|
|
59
|
+
*
|
|
60
|
+
* @param pipelineId - Unique identifier for this pipeline.
|
|
61
|
+
* @param fn - The function to run.
|
|
62
|
+
* @param options - Additional options.
|
|
63
|
+
* @returns The result of the function.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* const result = await dp.run('daily-etl', async () => {
|
|
67
|
+
* const records = await processData();
|
|
68
|
+
* return { recordsProcessed: records.length };
|
|
69
|
+
* });
|
|
70
|
+
*/
|
|
71
|
+
run<T>(pipelineId: string, fn: () => T | Promise<T>, options?: {
|
|
72
|
+
appName?: string;
|
|
73
|
+
}): Promise<T>;
|
|
74
|
+
/**
|
|
75
|
+
* Create a wrapper function that auto-sends heartbeats.
|
|
76
|
+
*
|
|
77
|
+
* @param pipelineId - Unique identifier for this pipeline.
|
|
78
|
+
* @param fn - The function to wrap.
|
|
79
|
+
* @param options - Additional options.
|
|
80
|
+
* @returns A wrapped function.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* const myPipeline = dp.wrap('daily-etl', async () => {
|
|
84
|
+
* await processData();
|
|
85
|
+
* });
|
|
86
|
+
*
|
|
87
|
+
* // Later...
|
|
88
|
+
* await myPipeline();
|
|
89
|
+
*/
|
|
90
|
+
wrap<T extends (...args: unknown[]) => unknown>(pipelineId: string, fn: T, options?: {
|
|
91
|
+
appName?: string;
|
|
92
|
+
}): (...args: Parameters<T>) => Promise<Awaited<ReturnType<T>>>;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Send a heartbeat using DEADPIPE_API_KEY from environment.
|
|
96
|
+
*/
|
|
97
|
+
declare function ping(pipelineId: string, options?: PingOptions): Promise<HeartbeatResponse | null>;
|
|
98
|
+
/**
|
|
99
|
+
* Run a function with automatic heartbeat using DEADPIPE_API_KEY from environment.
|
|
100
|
+
*/
|
|
101
|
+
declare function run<T>(pipelineId: string, fn: () => T | Promise<T>, options?: {
|
|
102
|
+
appName?: string;
|
|
103
|
+
}): Promise<T>;
|
|
104
|
+
|
|
105
|
+
export { Deadpipe, type DeadpipeOptions, type HeartbeatResponse, type PingOptions, type Status, Deadpipe as default, ping, run };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deadpipe - Dead simple pipeline monitoring.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* import { Deadpipe } from 'deadpipe';
|
|
6
|
+
*
|
|
7
|
+
* const dp = new Deadpipe('your-api-key');
|
|
8
|
+
*
|
|
9
|
+
* // Option 1: Wrapper
|
|
10
|
+
* await dp.run('my-pipeline', async () => {
|
|
11
|
+
* // your code here
|
|
12
|
+
* });
|
|
13
|
+
*
|
|
14
|
+
* // Option 2: Manual
|
|
15
|
+
* await dp.ping('my-pipeline', { status: 'success' });
|
|
16
|
+
*/
|
|
17
|
+
type Status = 'success' | 'failed';
|
|
18
|
+
interface PingOptions {
|
|
19
|
+
status?: Status;
|
|
20
|
+
durationMs?: number;
|
|
21
|
+
recordsProcessed?: number;
|
|
22
|
+
appName?: string;
|
|
23
|
+
}
|
|
24
|
+
interface DeadpipeOptions {
|
|
25
|
+
apiKey?: string;
|
|
26
|
+
baseUrl?: string;
|
|
27
|
+
timeout?: number;
|
|
28
|
+
}
|
|
29
|
+
interface HeartbeatResponse {
|
|
30
|
+
received: boolean;
|
|
31
|
+
pipeline_id: string;
|
|
32
|
+
timestamp: string;
|
|
33
|
+
freshness: 'fresh' | 'stale' | 'critical';
|
|
34
|
+
next_expected: string;
|
|
35
|
+
created?: boolean;
|
|
36
|
+
message?: string;
|
|
37
|
+
}
|
|
38
|
+
declare class Deadpipe {
|
|
39
|
+
private apiKey;
|
|
40
|
+
private baseUrl;
|
|
41
|
+
private timeout;
|
|
42
|
+
/**
|
|
43
|
+
* Create a Deadpipe client.
|
|
44
|
+
*
|
|
45
|
+
* @param apiKey - Your Deadpipe API key. Falls back to DEADPIPE_API_KEY env var.
|
|
46
|
+
* @param options - Configuration options.
|
|
47
|
+
*/
|
|
48
|
+
constructor(apiKey?: string, options?: Omit<DeadpipeOptions, 'apiKey'>);
|
|
49
|
+
/**
|
|
50
|
+
* Send a heartbeat ping for a pipeline.
|
|
51
|
+
*
|
|
52
|
+
* @param pipelineId - Unique identifier for this pipeline.
|
|
53
|
+
* @param options - Ping options.
|
|
54
|
+
* @returns The heartbeat response, or null if the request failed.
|
|
55
|
+
*/
|
|
56
|
+
ping(pipelineId: string, options?: PingOptions): Promise<HeartbeatResponse | null>;
|
|
57
|
+
/**
|
|
58
|
+
* Run a function with automatic heartbeat on completion.
|
|
59
|
+
*
|
|
60
|
+
* @param pipelineId - Unique identifier for this pipeline.
|
|
61
|
+
* @param fn - The function to run.
|
|
62
|
+
* @param options - Additional options.
|
|
63
|
+
* @returns The result of the function.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* const result = await dp.run('daily-etl', async () => {
|
|
67
|
+
* const records = await processData();
|
|
68
|
+
* return { recordsProcessed: records.length };
|
|
69
|
+
* });
|
|
70
|
+
*/
|
|
71
|
+
run<T>(pipelineId: string, fn: () => T | Promise<T>, options?: {
|
|
72
|
+
appName?: string;
|
|
73
|
+
}): Promise<T>;
|
|
74
|
+
/**
|
|
75
|
+
* Create a wrapper function that auto-sends heartbeats.
|
|
76
|
+
*
|
|
77
|
+
* @param pipelineId - Unique identifier for this pipeline.
|
|
78
|
+
* @param fn - The function to wrap.
|
|
79
|
+
* @param options - Additional options.
|
|
80
|
+
* @returns A wrapped function.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* const myPipeline = dp.wrap('daily-etl', async () => {
|
|
84
|
+
* await processData();
|
|
85
|
+
* });
|
|
86
|
+
*
|
|
87
|
+
* // Later...
|
|
88
|
+
* await myPipeline();
|
|
89
|
+
*/
|
|
90
|
+
wrap<T extends (...args: unknown[]) => unknown>(pipelineId: string, fn: T, options?: {
|
|
91
|
+
appName?: string;
|
|
92
|
+
}): (...args: Parameters<T>) => Promise<Awaited<ReturnType<T>>>;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Send a heartbeat using DEADPIPE_API_KEY from environment.
|
|
96
|
+
*/
|
|
97
|
+
declare function ping(pipelineId: string, options?: PingOptions): Promise<HeartbeatResponse | null>;
|
|
98
|
+
/**
|
|
99
|
+
* Run a function with automatic heartbeat using DEADPIPE_API_KEY from environment.
|
|
100
|
+
*/
|
|
101
|
+
declare function run<T>(pipelineId: string, fn: () => T | Promise<T>, options?: {
|
|
102
|
+
appName?: string;
|
|
103
|
+
}): Promise<T>;
|
|
104
|
+
|
|
105
|
+
export { Deadpipe, type DeadpipeOptions, type HeartbeatResponse, type PingOptions, type Status, Deadpipe as default, ping, run };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
Deadpipe: () => Deadpipe,
|
|
24
|
+
default: () => index_default,
|
|
25
|
+
ping: () => ping,
|
|
26
|
+
run: () => run
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(index_exports);
|
|
29
|
+
var Deadpipe = class {
|
|
30
|
+
apiKey;
|
|
31
|
+
baseUrl;
|
|
32
|
+
timeout;
|
|
33
|
+
/**
|
|
34
|
+
* Create a Deadpipe client.
|
|
35
|
+
*
|
|
36
|
+
* @param apiKey - Your Deadpipe API key. Falls back to DEADPIPE_API_KEY env var.
|
|
37
|
+
* @param options - Configuration options.
|
|
38
|
+
*/
|
|
39
|
+
constructor(apiKey, options = {}) {
|
|
40
|
+
this.apiKey = apiKey || process.env.DEADPIPE_API_KEY || "";
|
|
41
|
+
if (!this.apiKey) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
"API key required. Pass apiKey or set DEADPIPE_API_KEY environment variable."
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
this.baseUrl = (options.baseUrl || "https://www.deadpipe.com/api/v1").replace(/\/$/, "");
|
|
47
|
+
this.timeout = options.timeout || 1e4;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Send a heartbeat ping for a pipeline.
|
|
51
|
+
*
|
|
52
|
+
* @param pipelineId - Unique identifier for this pipeline.
|
|
53
|
+
* @param options - Ping options.
|
|
54
|
+
* @returns The heartbeat response, or null if the request failed.
|
|
55
|
+
*/
|
|
56
|
+
async ping(pipelineId, options = {}) {
|
|
57
|
+
const { status = "success", durationMs, recordsProcessed, appName } = options;
|
|
58
|
+
const payload = {
|
|
59
|
+
pipeline_id: pipelineId,
|
|
60
|
+
status
|
|
61
|
+
};
|
|
62
|
+
if (durationMs !== void 0) payload.duration_ms = durationMs;
|
|
63
|
+
if (recordsProcessed !== void 0) payload.records_processed = recordsProcessed;
|
|
64
|
+
if (appName !== void 0) payload.app_name = appName;
|
|
65
|
+
try {
|
|
66
|
+
const controller = new AbortController();
|
|
67
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
68
|
+
const response = await fetch(`${this.baseUrl}/heartbeat`, {
|
|
69
|
+
method: "POST",
|
|
70
|
+
headers: {
|
|
71
|
+
"Content-Type": "application/json",
|
|
72
|
+
"X-API-Key": this.apiKey
|
|
73
|
+
},
|
|
74
|
+
body: JSON.stringify(payload),
|
|
75
|
+
signal: controller.signal
|
|
76
|
+
});
|
|
77
|
+
clearTimeout(timeoutId);
|
|
78
|
+
if (!response.ok) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
return await response.json();
|
|
82
|
+
} catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Run a function with automatic heartbeat on completion.
|
|
88
|
+
*
|
|
89
|
+
* @param pipelineId - Unique identifier for this pipeline.
|
|
90
|
+
* @param fn - The function to run.
|
|
91
|
+
* @param options - Additional options.
|
|
92
|
+
* @returns The result of the function.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* const result = await dp.run('daily-etl', async () => {
|
|
96
|
+
* const records = await processData();
|
|
97
|
+
* return { recordsProcessed: records.length };
|
|
98
|
+
* });
|
|
99
|
+
*/
|
|
100
|
+
async run(pipelineId, fn, options = {}) {
|
|
101
|
+
const startTime = Date.now();
|
|
102
|
+
let status = "success";
|
|
103
|
+
let recordsProcessed;
|
|
104
|
+
try {
|
|
105
|
+
const result = await fn();
|
|
106
|
+
if (result && typeof result === "object" && "recordsProcessed" in result) {
|
|
107
|
+
recordsProcessed = result.recordsProcessed;
|
|
108
|
+
}
|
|
109
|
+
return result;
|
|
110
|
+
} catch (error) {
|
|
111
|
+
status = "failed";
|
|
112
|
+
throw error;
|
|
113
|
+
} finally {
|
|
114
|
+
const durationMs = Date.now() - startTime;
|
|
115
|
+
await this.ping(pipelineId, {
|
|
116
|
+
status,
|
|
117
|
+
durationMs,
|
|
118
|
+
recordsProcessed,
|
|
119
|
+
appName: options.appName
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Create a wrapper function that auto-sends heartbeats.
|
|
125
|
+
*
|
|
126
|
+
* @param pipelineId - Unique identifier for this pipeline.
|
|
127
|
+
* @param fn - The function to wrap.
|
|
128
|
+
* @param options - Additional options.
|
|
129
|
+
* @returns A wrapped function.
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* const myPipeline = dp.wrap('daily-etl', async () => {
|
|
133
|
+
* await processData();
|
|
134
|
+
* });
|
|
135
|
+
*
|
|
136
|
+
* // Later...
|
|
137
|
+
* await myPipeline();
|
|
138
|
+
*/
|
|
139
|
+
wrap(pipelineId, fn, options = {}) {
|
|
140
|
+
return async (...args) => {
|
|
141
|
+
return this.run(pipelineId, () => fn(...args), options);
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
var defaultClient = null;
|
|
146
|
+
function getDefaultClient() {
|
|
147
|
+
if (!defaultClient) {
|
|
148
|
+
defaultClient = new Deadpipe();
|
|
149
|
+
}
|
|
150
|
+
return defaultClient;
|
|
151
|
+
}
|
|
152
|
+
async function ping(pipelineId, options = {}) {
|
|
153
|
+
return getDefaultClient().ping(pipelineId, options);
|
|
154
|
+
}
|
|
155
|
+
async function run(pipelineId, fn, options = {}) {
|
|
156
|
+
return getDefaultClient().run(pipelineId, fn, options);
|
|
157
|
+
}
|
|
158
|
+
var index_default = Deadpipe;
|
|
159
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
160
|
+
0 && (module.exports = {
|
|
161
|
+
Deadpipe,
|
|
162
|
+
ping,
|
|
163
|
+
run
|
|
164
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var Deadpipe = class {
|
|
3
|
+
apiKey;
|
|
4
|
+
baseUrl;
|
|
5
|
+
timeout;
|
|
6
|
+
/**
|
|
7
|
+
* Create a Deadpipe client.
|
|
8
|
+
*
|
|
9
|
+
* @param apiKey - Your Deadpipe API key. Falls back to DEADPIPE_API_KEY env var.
|
|
10
|
+
* @param options - Configuration options.
|
|
11
|
+
*/
|
|
12
|
+
constructor(apiKey, options = {}) {
|
|
13
|
+
this.apiKey = apiKey || process.env.DEADPIPE_API_KEY || "";
|
|
14
|
+
if (!this.apiKey) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
"API key required. Pass apiKey or set DEADPIPE_API_KEY environment variable."
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
this.baseUrl = (options.baseUrl || "https://www.deadpipe.com/api/v1").replace(/\/$/, "");
|
|
20
|
+
this.timeout = options.timeout || 1e4;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Send a heartbeat ping for a pipeline.
|
|
24
|
+
*
|
|
25
|
+
* @param pipelineId - Unique identifier for this pipeline.
|
|
26
|
+
* @param options - Ping options.
|
|
27
|
+
* @returns The heartbeat response, or null if the request failed.
|
|
28
|
+
*/
|
|
29
|
+
async ping(pipelineId, options = {}) {
|
|
30
|
+
const { status = "success", durationMs, recordsProcessed, appName } = options;
|
|
31
|
+
const payload = {
|
|
32
|
+
pipeline_id: pipelineId,
|
|
33
|
+
status
|
|
34
|
+
};
|
|
35
|
+
if (durationMs !== void 0) payload.duration_ms = durationMs;
|
|
36
|
+
if (recordsProcessed !== void 0) payload.records_processed = recordsProcessed;
|
|
37
|
+
if (appName !== void 0) payload.app_name = appName;
|
|
38
|
+
try {
|
|
39
|
+
const controller = new AbortController();
|
|
40
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
41
|
+
const response = await fetch(`${this.baseUrl}/heartbeat`, {
|
|
42
|
+
method: "POST",
|
|
43
|
+
headers: {
|
|
44
|
+
"Content-Type": "application/json",
|
|
45
|
+
"X-API-Key": this.apiKey
|
|
46
|
+
},
|
|
47
|
+
body: JSON.stringify(payload),
|
|
48
|
+
signal: controller.signal
|
|
49
|
+
});
|
|
50
|
+
clearTimeout(timeoutId);
|
|
51
|
+
if (!response.ok) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
return await response.json();
|
|
55
|
+
} catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Run a function with automatic heartbeat on completion.
|
|
61
|
+
*
|
|
62
|
+
* @param pipelineId - Unique identifier for this pipeline.
|
|
63
|
+
* @param fn - The function to run.
|
|
64
|
+
* @param options - Additional options.
|
|
65
|
+
* @returns The result of the function.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* const result = await dp.run('daily-etl', async () => {
|
|
69
|
+
* const records = await processData();
|
|
70
|
+
* return { recordsProcessed: records.length };
|
|
71
|
+
* });
|
|
72
|
+
*/
|
|
73
|
+
async run(pipelineId, fn, options = {}) {
|
|
74
|
+
const startTime = Date.now();
|
|
75
|
+
let status = "success";
|
|
76
|
+
let recordsProcessed;
|
|
77
|
+
try {
|
|
78
|
+
const result = await fn();
|
|
79
|
+
if (result && typeof result === "object" && "recordsProcessed" in result) {
|
|
80
|
+
recordsProcessed = result.recordsProcessed;
|
|
81
|
+
}
|
|
82
|
+
return result;
|
|
83
|
+
} catch (error) {
|
|
84
|
+
status = "failed";
|
|
85
|
+
throw error;
|
|
86
|
+
} finally {
|
|
87
|
+
const durationMs = Date.now() - startTime;
|
|
88
|
+
await this.ping(pipelineId, {
|
|
89
|
+
status,
|
|
90
|
+
durationMs,
|
|
91
|
+
recordsProcessed,
|
|
92
|
+
appName: options.appName
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Create a wrapper function that auto-sends heartbeats.
|
|
98
|
+
*
|
|
99
|
+
* @param pipelineId - Unique identifier for this pipeline.
|
|
100
|
+
* @param fn - The function to wrap.
|
|
101
|
+
* @param options - Additional options.
|
|
102
|
+
* @returns A wrapped function.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* const myPipeline = dp.wrap('daily-etl', async () => {
|
|
106
|
+
* await processData();
|
|
107
|
+
* });
|
|
108
|
+
*
|
|
109
|
+
* // Later...
|
|
110
|
+
* await myPipeline();
|
|
111
|
+
*/
|
|
112
|
+
wrap(pipelineId, fn, options = {}) {
|
|
113
|
+
return async (...args) => {
|
|
114
|
+
return this.run(pipelineId, () => fn(...args), options);
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
var defaultClient = null;
|
|
119
|
+
function getDefaultClient() {
|
|
120
|
+
if (!defaultClient) {
|
|
121
|
+
defaultClient = new Deadpipe();
|
|
122
|
+
}
|
|
123
|
+
return defaultClient;
|
|
124
|
+
}
|
|
125
|
+
async function ping(pipelineId, options = {}) {
|
|
126
|
+
return getDefaultClient().ping(pipelineId, options);
|
|
127
|
+
}
|
|
128
|
+
async function run(pipelineId, fn, options = {}) {
|
|
129
|
+
return getDefaultClient().run(pipelineId, fn, options);
|
|
130
|
+
}
|
|
131
|
+
var index_default = Deadpipe;
|
|
132
|
+
export {
|
|
133
|
+
Deadpipe,
|
|
134
|
+
index_default as default,
|
|
135
|
+
ping,
|
|
136
|
+
run
|
|
137
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "deadpipe",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Dead simple pipeline monitoring. Know when your pipelines die.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"monitoring",
|
|
7
|
+
"pipelines",
|
|
8
|
+
"etl",
|
|
9
|
+
"observability",
|
|
10
|
+
"heartbeat",
|
|
11
|
+
"dead-mans-switch",
|
|
12
|
+
"alerting"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://deadpipe.com",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/deadpipe/deadpipe-node/issues"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/deadpipe/deadpipe-node.git"
|
|
21
|
+
},
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"author": "Deadpipe <hello@deadpipe.com>",
|
|
24
|
+
"type": "commonjs",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"import": "./dist/index.mjs",
|
|
28
|
+
"require": "./dist/index.js",
|
|
29
|
+
"types": "./dist/index.d.ts"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"main": "dist/index.js",
|
|
33
|
+
"types": "dist/index.d.ts",
|
|
34
|
+
"files": [
|
|
35
|
+
"dist"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
39
|
+
"prepublishOnly": "npm run build"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"tsup": "^8.5.1",
|
|
43
|
+
"typescript": "^5.9.3"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=18.0.0"
|
|
47
|
+
},
|
|
48
|
+
"module": "dist/index.mjs"
|
|
49
|
+
}
|