pipework 0.1.2 → 0.1.4
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 +26 -7
- package/dist/cli/commands/init.js +2 -2
- package/dist/cli/commands/migrate.d.ts +4 -2
- package/dist/cli/commands/migrate.d.ts.map +1 -1
- package/dist/cli/commands/migrate.js +21 -12
- package/dist/cli/commands/migrate.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +14 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/config/discover.d.ts +2 -2
- package/dist/config/discover.d.ts.map +1 -1
- package/dist/config/discover.js +13 -6
- package/dist/config/discover.js.map +1 -1
- package/dist/config/env.d.ts +2 -0
- package/dist/config/env.d.ts.map +1 -0
- package/dist/config/env.js +32 -0
- package/dist/config/env.js.map +1 -0
- package/dist/config/index.d.ts +2 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +2 -1
- package/dist/config/index.js.map +1 -1
- package/dist/config/namespace.d.ts +2 -2
- package/dist/config/namespace.d.ts.map +1 -1
- package/dist/config/namespace.js +2 -3
- package/dist/config/namespace.js.map +1 -1
- package/dist/di/builder.d.ts +4 -0
- package/dist/di/builder.d.ts.map +1 -1
- package/dist/di/builder.js +8 -0
- package/dist/di/builder.js.map +1 -1
- package/dist/di/resolve.d.ts +1 -0
- package/dist/di/resolve.d.ts.map +1 -1
- package/dist/di/resolve.js +7 -14
- package/dist/di/resolve.js.map +1 -1
- package/dist/di/types.d.ts +1 -0
- package/dist/di/types.d.ts.map +1 -1
- package/dist/http/server.d.ts.map +1 -1
- package/dist/http/server.js +21 -1
- package/dist/http/server.js.map +1 -1
- package/dist/http/types.d.ts +1 -0
- package/dist/http/types.d.ts.map +1 -1
- package/dist/idempotency/index.d.ts +4 -0
- package/dist/idempotency/index.d.ts.map +1 -0
- package/dist/idempotency/index.js +3 -0
- package/dist/idempotency/index.js.map +1 -0
- package/dist/idempotency/middleware.d.ts +21 -0
- package/dist/idempotency/middleware.d.ts.map +1 -0
- package/dist/idempotency/middleware.js +56 -0
- package/dist/idempotency/middleware.js.map +1 -0
- package/dist/idempotency/store.d.ts +3 -0
- package/dist/idempotency/store.d.ts.map +1 -0
- package/dist/idempotency/store.js +63 -0
- package/dist/idempotency/store.js.map +1 -0
- package/dist/idempotency/types.d.ts +28 -0
- package/dist/idempotency/types.d.ts.map +1 -0
- package/dist/idempotency/types.js +2 -0
- package/dist/idempotency/types.js.map +1 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/migrate/check.d.ts +14 -0
- package/dist/migrate/check.d.ts.map +1 -0
- package/dist/migrate/check.js +58 -0
- package/dist/migrate/check.js.map +1 -0
- package/dist/migrate/index.d.ts +3 -1
- package/dist/migrate/index.d.ts.map +1 -1
- package/dist/migrate/index.js +3 -1
- package/dist/migrate/index.js.map +1 -1
- package/dist/migrate/run.d.ts +13 -2
- package/dist/migrate/run.d.ts.map +1 -1
- package/dist/migrate/run.js +24 -3
- package/dist/migrate/run.js.map +1 -1
- package/dist/migrate/safety.d.ts +10 -0
- package/dist/migrate/safety.d.ts.map +1 -0
- package/dist/migrate/safety.js +115 -0
- package/dist/migrate/safety.js.map +1 -0
- package/dist/pipework.d.ts +1 -1
- package/dist/pipework.js +1 -1
- package/dist/test/auto-setup.js +4 -4
- package/dist/test/auto-setup.js.map +1 -1
- package/dist/test/vitest.js +2 -2
- package/dist/webhook/inbound.d.ts +16 -0
- package/dist/webhook/inbound.d.ts.map +1 -0
- package/dist/webhook/inbound.js +21 -0
- package/dist/webhook/inbound.js.map +1 -0
- package/dist/webhook/index.d.ts +5 -0
- package/dist/webhook/index.d.ts.map +1 -0
- package/dist/webhook/index.js +5 -0
- package/dist/webhook/index.js.map +1 -0
- package/dist/webhook/namespace.d.ts +15 -0
- package/dist/webhook/namespace.d.ts.map +1 -0
- package/dist/webhook/namespace.js +15 -0
- package/dist/webhook/namespace.js.map +1 -0
- package/dist/webhook/outbound.d.ts +48 -0
- package/dist/webhook/outbound.d.ts.map +1 -0
- package/dist/webhook/outbound.js +160 -0
- package/dist/webhook/outbound.js.map +1 -0
- package/dist/webhook/sign.d.ts +12 -0
- package/dist/webhook/sign.d.ts.map +1 -0
- package/dist/webhook/sign.js +12 -0
- package/dist/webhook/sign.js.map +1 -0
- package/dist/webhook/verify.d.ts +14 -0
- package/dist/webhook/verify.d.ts.map +1 -0
- package/dist/webhook/verify.js +61 -0
- package/dist/webhook/verify.js.map +1 -0
- package/package.json +20 -14
package/dist/migrate/run.js
CHANGED
|
@@ -1,17 +1,37 @@
|
|
|
1
1
|
import { migrate as drizzleMigrate } from 'drizzle-orm/postgres-js/migrator';
|
|
2
2
|
import { sql } from 'drizzle-orm';
|
|
3
3
|
import { quoteIdentifier } from '../db/identifiers.js';
|
|
4
|
-
|
|
4
|
+
import { checkMigrations } from './check.js';
|
|
5
|
+
export class UnsafeMigrationError extends Error {
|
|
6
|
+
check;
|
|
7
|
+
name = 'UnsafeMigrationError';
|
|
8
|
+
constructor(check, message) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.check = check;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export async function migrateAll(instance, options = {}) {
|
|
5
14
|
const results = [];
|
|
6
15
|
for (const [, dbConfig] of instance.config.databases()) {
|
|
7
16
|
if (dbConfig.migrations === undefined)
|
|
8
17
|
continue;
|
|
9
|
-
const result = await migrateOne(instance, dbConfig);
|
|
18
|
+
const result = await migrateOne(instance, dbConfig, options);
|
|
10
19
|
results.push(result);
|
|
11
20
|
}
|
|
12
21
|
return results;
|
|
13
22
|
}
|
|
14
|
-
export async function migrateOne(instance, dbConfig) {
|
|
23
|
+
export async function migrateOne(instance, dbConfig, options = {}) {
|
|
24
|
+
const safetyCheck = dbConfig.migrations !== undefined
|
|
25
|
+
? checkMigrations(dbConfig.migrations)
|
|
26
|
+
: { safe: true, hasDestructive: false, hasLocking: false, fileHazards: [] };
|
|
27
|
+
if (safetyCheck.hasDestructive && options.allowDestructive !== true) {
|
|
28
|
+
throw new UnsafeMigrationError(safetyCheck, `Migration for "${dbConfig.name}" contains destructive operations (DROP TABLE, DROP COLUMN, etc.). ` +
|
|
29
|
+
`Pass { allowDestructive: true } or use --allow-destructive on the CLI to proceed.`);
|
|
30
|
+
}
|
|
31
|
+
if (safetyCheck.hasLocking && options.acknowledgeLock !== true) {
|
|
32
|
+
throw new UnsafeMigrationError(safetyCheck, `Migration for "${dbConfig.name}" contains operations that acquire heavy locks. ` +
|
|
33
|
+
`Pass { acknowledgeLock: true } or use --acknowledge-lock on the CLI to proceed.`);
|
|
34
|
+
}
|
|
15
35
|
const conn = instance.pool.getOrCreate(dbConfig);
|
|
16
36
|
let extensionsCreated = [];
|
|
17
37
|
if (dbConfig.extensions.length > 0) {
|
|
@@ -30,6 +50,7 @@ export async function migrateOne(instance, dbConfig) {
|
|
|
30
50
|
database: dbConfig.name,
|
|
31
51
|
migrationsRun: afterTables - beforeTables,
|
|
32
52
|
extensions: extensionsCreated,
|
|
53
|
+
safetyCheck,
|
|
33
54
|
};
|
|
34
55
|
}
|
|
35
56
|
async function getMigrationCount(db, table) {
|
package/dist/migrate/run.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/migrate/run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,kCAAkC,CAAA;AAC5E,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAA;AAGjC,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/migrate/run.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type Severity = 'destructive' | 'locking' | 'safe';
|
|
2
|
+
export interface MigrationHazard {
|
|
3
|
+
severity: Severity;
|
|
4
|
+
operation: string;
|
|
5
|
+
statement: string;
|
|
6
|
+
suggestion: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function analyzeSQL(sql: string): MigrationHazard[];
|
|
9
|
+
export declare function formatHazards(hazards: MigrationHazard[], file: string): string;
|
|
10
|
+
//# sourceMappingURL=safety.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safety.d.ts","sourceRoot":"","sources":["../../src/migrate/safety.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,aAAa,GAAG,SAAS,GAAG,MAAM,CAAA;AAEzD,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,QAAQ,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;CACnB;AA8ED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,EAAE,CAmBzD;AASD,wBAAgB,aAAa,CAAC,OAAO,EAAE,eAAe,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAoB9E"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
const RULES = [
|
|
2
|
+
// ── Destructive (data loss / backward-incompatible) ───────────────────
|
|
3
|
+
{
|
|
4
|
+
pattern: /\bDROP\s+TABLE\b/i,
|
|
5
|
+
severity: 'destructive',
|
|
6
|
+
operation: 'DROP TABLE',
|
|
7
|
+
suggestion: 'Rename the table first, deploy, verify no access, then drop in a follow-up migration.',
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
pattern: /\bDROP\s+COLUMN\b/i,
|
|
11
|
+
severity: 'destructive',
|
|
12
|
+
operation: 'DROP COLUMN',
|
|
13
|
+
suggestion: 'Stop reading the column in application code first, then drop in a follow-up migration.',
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
pattern: /\bALTER\s+COLUMN\b[^;]*\bSET\s+NOT\s+NULL\b/i,
|
|
17
|
+
severity: 'destructive',
|
|
18
|
+
operation: 'SET NOT NULL',
|
|
19
|
+
suggestion: 'Add a CHECK constraint with NOT VALID, validate it separately, then convert to NOT NULL.',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
pattern: /\bALTER\s+COLUMN\b[^;]*\bTYPE\b/i,
|
|
23
|
+
severity: 'destructive',
|
|
24
|
+
operation: 'ALTER COLUMN TYPE',
|
|
25
|
+
suggestion: 'Add a new column with the target type, backfill, swap reads, then drop the old column.',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
pattern: /\bTRUNCATE\b/i,
|
|
29
|
+
severity: 'destructive',
|
|
30
|
+
operation: 'TRUNCATE',
|
|
31
|
+
suggestion: 'Use DELETE with a WHERE clause if you need to remove rows in a migration.',
|
|
32
|
+
},
|
|
33
|
+
// ── Locking (acquires heavy locks, risks downtime under load) ─────────
|
|
34
|
+
{
|
|
35
|
+
pattern: /\bCREATE\s+(?:UNIQUE\s+)?INDEX\b(?!\s+CONCURRENTLY)/i,
|
|
36
|
+
severity: 'locking',
|
|
37
|
+
operation: 'CREATE INDEX (non-concurrent)',
|
|
38
|
+
suggestion: 'Use CREATE INDEX CONCURRENTLY to avoid blocking writes.',
|
|
39
|
+
except: /\bCONCURRENTLY\b/i,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
pattern: /\bRENAME\s+COLUMN\b/i,
|
|
43
|
+
severity: 'locking',
|
|
44
|
+
operation: 'RENAME COLUMN',
|
|
45
|
+
suggestion: 'Add a new column, backfill, migrate reads, then drop the old column.',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
pattern: /\bRENAME\s+TABLE\b/i,
|
|
49
|
+
severity: 'locking',
|
|
50
|
+
operation: 'RENAME TABLE',
|
|
51
|
+
suggestion: 'Create a new table, migrate data, swap reads, then drop the old table.',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
pattern: /\bRENAME\s+TO\b/i,
|
|
55
|
+
severity: 'locking',
|
|
56
|
+
operation: 'RENAME TO',
|
|
57
|
+
suggestion: 'Renames acquire AccessExclusive locks. Consider a multi-step migration instead.',
|
|
58
|
+
except: /\bRENAME\s+COLUMN\b|\bRENAME\s+TABLE\b/i,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
pattern: /\bADD\s+CONSTRAINT\b[^;]*\bFOREIGN\s+KEY\b(?![^;]*\bNOT\s+VALID\b)/i,
|
|
62
|
+
severity: 'locking',
|
|
63
|
+
operation: 'ADD FOREIGN KEY (without NOT VALID)',
|
|
64
|
+
suggestion: 'Add the constraint with NOT VALID, then VALIDATE CONSTRAINT in a separate statement.',
|
|
65
|
+
},
|
|
66
|
+
];
|
|
67
|
+
export function analyzeSQL(sql) {
|
|
68
|
+
const statements = splitStatements(sql);
|
|
69
|
+
const hazards = [];
|
|
70
|
+
for (const stmt of statements) {
|
|
71
|
+
for (const rule of RULES) {
|
|
72
|
+
if (!rule.pattern.test(stmt))
|
|
73
|
+
continue;
|
|
74
|
+
if (rule.except !== undefined && rule.except.test(stmt))
|
|
75
|
+
continue;
|
|
76
|
+
hazards.push({
|
|
77
|
+
severity: rule.severity,
|
|
78
|
+
operation: rule.operation,
|
|
79
|
+
statement: stmt.trim().slice(0, 200),
|
|
80
|
+
suggestion: rule.suggestion,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return hazards;
|
|
85
|
+
}
|
|
86
|
+
function splitStatements(sql) {
|
|
87
|
+
return sql
|
|
88
|
+
.split(/;/)
|
|
89
|
+
.map(s => s.trim())
|
|
90
|
+
.filter(s => s.length > 0 && !s.startsWith('--'));
|
|
91
|
+
}
|
|
92
|
+
export function formatHazards(hazards, file) {
|
|
93
|
+
const destructive = hazards.filter(h => h.severity === 'destructive');
|
|
94
|
+
const locking = hazards.filter(h => h.severity === 'locking');
|
|
95
|
+
const lines = [];
|
|
96
|
+
lines.push(` ${file}:`);
|
|
97
|
+
for (const h of destructive) {
|
|
98
|
+
lines.push(` ✗ DESTRUCTIVE: ${h.operation}`);
|
|
99
|
+
lines.push(` ${truncate(h.statement, 120)}`);
|
|
100
|
+
lines.push(` → ${h.suggestion}`);
|
|
101
|
+
}
|
|
102
|
+
for (const h of locking) {
|
|
103
|
+
lines.push(` ⚠ LOCKING: ${h.operation}`);
|
|
104
|
+
lines.push(` ${truncate(h.statement, 120)}`);
|
|
105
|
+
lines.push(` → ${h.suggestion}`);
|
|
106
|
+
}
|
|
107
|
+
return lines.join('\n');
|
|
108
|
+
}
|
|
109
|
+
function truncate(s, max) {
|
|
110
|
+
const oneLine = s.replace(/\s+/g, ' ');
|
|
111
|
+
if (oneLine.length <= max)
|
|
112
|
+
return oneLine;
|
|
113
|
+
return oneLine.slice(0, max - 3) + '...';
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=safety.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safety.js","sourceRoot":"","sources":["../../src/migrate/safety.ts"],"names":[],"mappings":"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"}
|
package/dist/pipework.d.ts
CHANGED
|
@@ -31,5 +31,5 @@ export interface ConnectionTracker {
|
|
|
31
31
|
activeCount(): number;
|
|
32
32
|
assertNoLeaks(maxExpected: number): void;
|
|
33
33
|
}
|
|
34
|
-
export declare function
|
|
34
|
+
export declare function createManifold(raw: Blueprint): Manifold;
|
|
35
35
|
//# sourceMappingURL=pipework.d.ts.map
|
package/dist/pipework.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { loadConfig as parseConfig } from './config/load.js';
|
|
2
2
|
import { createManagedConnection } from './db/pool.js';
|
|
3
3
|
import { InvariantViolation } from './invariants/assert.js';
|
|
4
|
-
export function
|
|
4
|
+
export function createManifold(raw) {
|
|
5
5
|
const config = parseConfig(raw);
|
|
6
6
|
const pool = createPoolState();
|
|
7
7
|
const isolation = createIsolationState();
|
package/dist/test/auto-setup.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { beforeAll, afterAll, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { loadEnvFiles } from '../config/env.js';
|
|
3
|
+
import { discoverInstance } from '../config/discover.js';
|
|
4
4
|
import { setupTestDatabases, teardownTestDatabases } from './setup.js';
|
|
5
5
|
import { beginTestIsolation, endTestIsolation } from './plugin.js';
|
|
6
6
|
import { _setTestState } from './vitest.js';
|
|
7
|
+
loadEnvFiles();
|
|
7
8
|
let _testContext = null;
|
|
8
|
-
const
|
|
9
|
-
const instance = createPipework(config);
|
|
9
|
+
const instance = await discoverInstance();
|
|
10
10
|
_setTestState(instance);
|
|
11
11
|
beforeAll(async () => {
|
|
12
12
|
await setupTestDatabases(instance);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auto-setup.js","sourceRoot":"","sources":["../../src/test/auto-setup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AACnE,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"auto-setup.js","sourceRoot":"","sources":["../../src/test/auto-setup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AACnE,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AACtE,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAoB,MAAM,aAAa,CAAA;AACpF,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAE3C,YAAY,EAAE,CAAA;AAEd,IAAI,YAAY,GAAuB,IAAI,CAAA;AAE3C,MAAM,QAAQ,GAAG,MAAM,gBAAgB,EAAE,CAAA;AAEzC,aAAa,CAAC,QAAQ,CAAC,CAAA;AAEvB,SAAS,CAAC,KAAK,IAAI,EAAE;IACnB,MAAM,kBAAkB,CAAC,QAAQ,CAAC,CAAA;AACpC,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,KAAK,IAAI,EAAE;IAClB,MAAM,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAA;IAC9B,MAAM,qBAAqB,EAAE,CAAA;AAC/B,CAAC,CAAC,CAAA;AAEF,UAAU,CAAC,KAAK,IAAI,EAAE;IACpB,YAAY,GAAG,MAAM,kBAAkB,CAAC,QAAQ,CAAC,CAAA;IACjD,aAAa,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAA;AACvC,CAAC,CAAC,CAAA;AAEF,SAAS,CAAC,KAAK,IAAI,EAAE;IACnB,IAAI,YAAY,EAAE,CAAC;QACjB,MAAM,gBAAgB,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAA;QAC9C,YAAY,GAAG,IAAI,CAAA;QACnB,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;IAC/B,CAAC;AACH,CAAC,CAAC,CAAA"}
|
package/dist/test/vitest.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { beforeAll, afterAll, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import { createManifold } from '../pipework.js';
|
|
3
3
|
import { setupTestDatabases, teardownTestDatabases } from './setup.js';
|
|
4
4
|
import { beginTestIsolation, endTestIsolation, createTestContext, runInContext } from './plugin.js';
|
|
5
5
|
export {} from './plugin.js';
|
|
@@ -12,7 +12,7 @@ export function _setTestState(instance, testContext) {
|
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
export function setupPipeworkTests(configOrInstance) {
|
|
15
|
-
const instance = isInstance(configOrInstance) ? configOrInstance :
|
|
15
|
+
const instance = isInstance(configOrInstance) ? configOrInstance : createManifold(configOrInstance);
|
|
16
16
|
_instance = instance;
|
|
17
17
|
beforeAll(async () => {
|
|
18
18
|
await setupTestDatabases(instance);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Handler, HttpMethod } from '../di/types.js';
|
|
2
|
+
import type { SignatureVerifier } from './verify.js';
|
|
3
|
+
export interface InboundWebhookConfig<TPayload> {
|
|
4
|
+
path: string;
|
|
5
|
+
method?: HttpMethod;
|
|
6
|
+
verifier: SignatureVerifier;
|
|
7
|
+
parsePayload?: (rawBody: Buffer) => TPayload;
|
|
8
|
+
idempotencyKey?: (payload: TPayload) => string;
|
|
9
|
+
}
|
|
10
|
+
export interface InboundWebhookContext<TPayload> {
|
|
11
|
+
payload: TPayload;
|
|
12
|
+
rawBody: Buffer;
|
|
13
|
+
headers: Readonly<Record<string, string | undefined>>;
|
|
14
|
+
}
|
|
15
|
+
export declare function defineInboundWebhook<TPayload = unknown>(config: InboundWebhookConfig<TPayload>, handler: (ctx: InboundWebhookContext<TPayload>) => Promise<unknown>): Handler<Record<string, unknown>, unknown>;
|
|
16
|
+
//# sourceMappingURL=inbound.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inbound.d.ts","sourceRoot":"","sources":["../../src/webhook/inbound.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AACzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAIpD,MAAM,WAAW,oBAAoB,CAAC,QAAQ;IAC5C,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,QAAQ,EAAE,iBAAiB,CAAA;IAC3B,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,QAAQ,CAAA;IAC5C,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,MAAM,CAAA;CAC/C;AAED,MAAM,WAAW,qBAAqB,CAAC,QAAQ;IAC7C,OAAO,EAAE,QAAQ,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAA;CACtD;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,GAAG,OAAO,EACrD,MAAM,EAAE,oBAAoB,CAAC,QAAQ,CAAC,EACtC,OAAO,EAAE,CAAC,GAAG,EAAE,qBAAqB,CAAC,QAAQ,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,GAClE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,CAsB3C"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Builder } from '../di/builder.js';
|
|
2
|
+
import { UnauthorizedError } from '../errors/index.js';
|
|
3
|
+
export function defineInboundWebhook(config, handler) {
|
|
4
|
+
const method = config.method ?? 'POST';
|
|
5
|
+
const parse = config.parsePayload ?? ((raw) => JSON.parse(raw.toString('utf-8')));
|
|
6
|
+
return new Builder()
|
|
7
|
+
.public()
|
|
8
|
+
.rawBody()
|
|
9
|
+
.request()
|
|
10
|
+
.route(method, config.path)
|
|
11
|
+
.fit(async ({ rawBody, request }) => {
|
|
12
|
+
if (!config.verifier.verify(rawBody, request.headers)) {
|
|
13
|
+
throw new UnauthorizedError('Webhook signature verification failed.\n\n' +
|
|
14
|
+
' The request signature does not match the expected value.\n' +
|
|
15
|
+
' Check that the signing secret is correct and the raw body is unmodified.\n');
|
|
16
|
+
}
|
|
17
|
+
const payload = parse(rawBody);
|
|
18
|
+
return handler({ payload, rawBody, headers: request.headers });
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=inbound.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inbound.js","sourceRoot":"","sources":["../../src/webhook/inbound.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAgBtD,MAAM,UAAU,oBAAoB,CAClC,MAAsC,EACtC,OAAmE;IAEnE,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,MAAM,CAAA;IACtC,MAAM,KAAK,GAAG,MAAM,CAAC,YAAY,IAAI,CAAC,CAAC,GAAW,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAa,CAAC,CAAA;IAErG,OAAO,IAAI,OAAO,EAAE;SACjB,MAAM,EAAE;SACR,OAAO,EAAE;SACT,OAAO,EAAE;SACT,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC;SAC1B,GAAG,CAAC,KAAK,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE;QAClC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YACtD,MAAM,IAAI,iBAAiB,CACzB,4CAA4C;gBAC5C,8DAA8D;gBAC9D,8EAA8E,CAC/E,CAAA;QACH,CAAC;QAED,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,CAAA;QAE9B,OAAO,OAAO,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;IAChE,CAAC,CAAC,CAAA;AACN,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { defineInboundWebhook, type InboundWebhookConfig, type InboundWebhookContext } from './inbound.js';
|
|
2
|
+
export { hmacVerifier, stripeVerifier, githubVerifier, type SignatureVerifier } from './verify.js';
|
|
3
|
+
export { signPayload, type SignedPayload, type WebhookSignatureOptions } from './sign.js';
|
|
4
|
+
export { createOutboundWebhook, type OutboundWebhookConfig, type OutboundWebhook, type WebhookEndpoint, type DeliveryAttempt } from './outbound.js';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/webhook/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,KAAK,oBAAoB,EAAE,KAAK,qBAAqB,EAAE,MAAM,cAAc,CAAA;AAC1G,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAClG,OAAO,EAAE,WAAW,EAAE,KAAK,aAAa,EAAE,KAAK,uBAAuB,EAAE,MAAM,WAAW,CAAA;AACzF,OAAO,EAAE,qBAAqB,EAAE,KAAK,qBAAqB,EAAE,KAAK,eAAe,EAC9E,KAAK,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/webhook/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAyD,MAAM,cAAc,CAAA;AAC1G,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,cAAc,EAA0B,MAAM,aAAa,CAAA;AAClG,OAAO,EAAE,WAAW,EAAoD,MAAM,WAAW,CAAA;AACzF,OAAO,EAAE,qBAAqB,EACgB,MAAM,eAAe,CAAA"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineInboundWebhook } from './inbound.js';
|
|
2
|
+
import { hmacVerifier, stripeVerifier, githubVerifier } from './verify.js';
|
|
3
|
+
import { signPayload } from './sign.js';
|
|
4
|
+
import { createOutboundWebhook } from './outbound.js';
|
|
5
|
+
export declare const webhook: {
|
|
6
|
+
readonly defineInbound: typeof defineInboundWebhook;
|
|
7
|
+
readonly createOutbound: typeof createOutboundWebhook;
|
|
8
|
+
readonly verify: {
|
|
9
|
+
readonly hmac: typeof hmacVerifier;
|
|
10
|
+
readonly stripe: typeof stripeVerifier;
|
|
11
|
+
readonly github: typeof githubVerifier;
|
|
12
|
+
};
|
|
13
|
+
readonly sign: typeof signPayload;
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=namespace.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"namespace.d.ts","sourceRoot":"","sources":["../../src/webhook/namespace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AACnD,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC1E,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AACvC,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAErD,eAAO,MAAM,OAAO;;;;;;;;;CAWV,CAAA"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineInboundWebhook } from './inbound.js';
|
|
2
|
+
import { hmacVerifier, stripeVerifier, githubVerifier } from './verify.js';
|
|
3
|
+
import { signPayload } from './sign.js';
|
|
4
|
+
import { createOutboundWebhook } from './outbound.js';
|
|
5
|
+
export const webhook = {
|
|
6
|
+
defineInbound: defineInboundWebhook,
|
|
7
|
+
createOutbound: createOutboundWebhook,
|
|
8
|
+
verify: {
|
|
9
|
+
hmac: hmacVerifier,
|
|
10
|
+
stripe: stripeVerifier,
|
|
11
|
+
github: githubVerifier,
|
|
12
|
+
},
|
|
13
|
+
sign: signPayload,
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=namespace.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"namespace.js","sourceRoot":"","sources":["../../src/webhook/namespace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AACnD,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC1E,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AACvC,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAErD,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,aAAa,EAAE,oBAAoB;IACnC,cAAc,EAAE,qBAAqB;IAErC,MAAM,EAAE;QACN,IAAI,EAAE,YAAY;QAClB,MAAM,EAAE,cAAc;QACtB,MAAM,EAAE,cAAc;KACvB;IAED,IAAI,EAAE,WAAW;CACT,CAAA"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { DB } from '../db/index.js';
|
|
2
|
+
import { type WebhookSignatureOptions } from './sign.js';
|
|
3
|
+
export interface OutboundWebhookConfig {
|
|
4
|
+
table?: string;
|
|
5
|
+
deliveryTable?: string;
|
|
6
|
+
signing?: WebhookSignatureOptions;
|
|
7
|
+
retry?: {
|
|
8
|
+
maxAttempts?: number;
|
|
9
|
+
initialDelayMs?: number;
|
|
10
|
+
maxDelayMs?: number;
|
|
11
|
+
};
|
|
12
|
+
timeoutMs?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface WebhookEndpoint {
|
|
15
|
+
id: string;
|
|
16
|
+
tenantId: string;
|
|
17
|
+
url: string;
|
|
18
|
+
secret: string;
|
|
19
|
+
events: string[];
|
|
20
|
+
active: boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface DeliveryAttempt {
|
|
23
|
+
id: string;
|
|
24
|
+
endpointId: string;
|
|
25
|
+
tenantId: string;
|
|
26
|
+
eventType: string;
|
|
27
|
+
payload: unknown;
|
|
28
|
+
statusCode: number | null;
|
|
29
|
+
responseBody: string | null;
|
|
30
|
+
attempt: number;
|
|
31
|
+
maxAttempts: number;
|
|
32
|
+
nextRetryAt: Date | null;
|
|
33
|
+
createdAt: Date;
|
|
34
|
+
}
|
|
35
|
+
export interface OutboundWebhook {
|
|
36
|
+
ensureTables(db: DB): Promise<void>;
|
|
37
|
+
send(db: DB, tenantId: string, eventType: string, payload: unknown): Promise<string[]>;
|
|
38
|
+
deliver(db: DB, deliveryId: string): Promise<{
|
|
39
|
+
success: boolean;
|
|
40
|
+
statusCode: number | null;
|
|
41
|
+
}>;
|
|
42
|
+
processPending(db: DB): Promise<number>;
|
|
43
|
+
registerEndpoint(db: DB, endpoint: Omit<WebhookEndpoint, 'id'>): Promise<string>;
|
|
44
|
+
listEndpoints(db: DB, tenantId: string): Promise<WebhookEndpoint[]>;
|
|
45
|
+
removeEndpoint(db: DB, tenantId: string, endpointId: string): Promise<boolean>;
|
|
46
|
+
}
|
|
47
|
+
export declare function createOutboundWebhook(config?: OutboundWebhookConfig): OutboundWebhook;
|
|
48
|
+
//# sourceMappingURL=outbound.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbound.d.ts","sourceRoot":"","sources":["../../src/webhook/outbound.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAA;AACxC,OAAO,EAAe,KAAK,uBAAuB,EAAE,MAAM,WAAW,CAAA;AAErE,MAAM,WAAW,qBAAqB;IACpC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,OAAO,CAAC,EAAE,uBAAuB,CAAA;IACjC,KAAK,CAAC,EAAE;QACN,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,cAAc,CAAC,EAAE,MAAM,CAAA;QACvB,UAAU,CAAC,EAAE,MAAM,CAAA;KACpB,CAAA;IACD,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,EAAE,MAAM,CAAA;IAChB,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,MAAM,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,OAAO,CAAA;IAChB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,IAAI,GAAG,IAAI,CAAA;IACxB,SAAS,EAAE,IAAI,CAAA;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,YAAY,CAAC,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACnC,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;IACtF,OAAO,CAAC,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAA;IAC7F,cAAc,CAAC,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACvC,gBAAgB,CAAC,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,eAAe,EAAE,IAAI,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAChF,aAAa,CAAC,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAAA;IACnE,cAAc,CAAC,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CAC/E;AAWD,wBAAgB,qBAAqB,CAAC,MAAM,GAAE,qBAA0B,GAAG,eAAe,CAqLzF"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { sql } from '../db/sql.js';
|
|
2
|
+
import { signPayload } from './sign.js';
|
|
3
|
+
const DEFAULTS = {
|
|
4
|
+
table: '__pipework_webhook_endpoints',
|
|
5
|
+
deliveryTable: '__pipework_webhook_deliveries',
|
|
6
|
+
maxAttempts: 5,
|
|
7
|
+
initialDelayMs: 1000,
|
|
8
|
+
maxDelayMs: 3_600_000,
|
|
9
|
+
timeoutMs: 30_000,
|
|
10
|
+
};
|
|
11
|
+
export function createOutboundWebhook(config = {}) {
|
|
12
|
+
const endpointTable = config.table ?? DEFAULTS.table;
|
|
13
|
+
const deliveryTable = config.deliveryTable ?? DEFAULTS.deliveryTable;
|
|
14
|
+
const maxAttempts = config.retry?.maxAttempts ?? DEFAULTS.maxAttempts;
|
|
15
|
+
const initialDelayMs = config.retry?.initialDelayMs ?? DEFAULTS.initialDelayMs;
|
|
16
|
+
const maxDelayMs = config.retry?.maxDelayMs ?? DEFAULTS.maxDelayMs;
|
|
17
|
+
const timeoutMs = config.timeoutMs ?? DEFAULTS.timeoutMs;
|
|
18
|
+
const signingOptions = config.signing ?? {};
|
|
19
|
+
return {
|
|
20
|
+
async ensureTables(db) {
|
|
21
|
+
await db.execute(sql.raw(`
|
|
22
|
+
CREATE TABLE IF NOT EXISTS ${endpointTable} (
|
|
23
|
+
id TEXT NOT NULL PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
24
|
+
tenant_id TEXT NOT NULL,
|
|
25
|
+
url TEXT NOT NULL,
|
|
26
|
+
secret TEXT NOT NULL,
|
|
27
|
+
events TEXT[] NOT NULL DEFAULT '{}',
|
|
28
|
+
active BOOLEAN NOT NULL DEFAULT true,
|
|
29
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
30
|
+
)
|
|
31
|
+
`));
|
|
32
|
+
await db.execute(sql.raw(`
|
|
33
|
+
CREATE TABLE IF NOT EXISTS ${deliveryTable} (
|
|
34
|
+
id TEXT NOT NULL PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
35
|
+
endpoint_id TEXT NOT NULL,
|
|
36
|
+
tenant_id TEXT NOT NULL,
|
|
37
|
+
event_type TEXT NOT NULL,
|
|
38
|
+
payload JSONB NOT NULL,
|
|
39
|
+
status_code INTEGER,
|
|
40
|
+
response_body TEXT,
|
|
41
|
+
attempt INTEGER NOT NULL DEFAULT 0,
|
|
42
|
+
max_attempts INTEGER NOT NULL,
|
|
43
|
+
next_retry_at TIMESTAMPTZ,
|
|
44
|
+
completed_at TIMESTAMPTZ,
|
|
45
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
46
|
+
)
|
|
47
|
+
`));
|
|
48
|
+
},
|
|
49
|
+
async send(db, tenantId, eventType, payload) {
|
|
50
|
+
const endpoints = await db.execute(sql `SELECT id, url, secret FROM ${sql.raw(endpointTable)}
|
|
51
|
+
WHERE tenant_id = ${tenantId}
|
|
52
|
+
AND active = true
|
|
53
|
+
AND (events = '{}' OR ${eventType} = ANY(events))`);
|
|
54
|
+
const deliveryIds = [];
|
|
55
|
+
for (const row of endpoints) {
|
|
56
|
+
const result = await db.execute(sql `INSERT INTO ${sql.raw(deliveryTable)} (endpoint_id, tenant_id, event_type, payload, max_attempts, next_retry_at)
|
|
57
|
+
VALUES (${row.id}, ${tenantId}, ${eventType}, ${JSON.stringify(payload)}, ${maxAttempts}, now())
|
|
58
|
+
RETURNING id`);
|
|
59
|
+
const inserted = result[0];
|
|
60
|
+
if (inserted !== undefined)
|
|
61
|
+
deliveryIds.push(inserted.id);
|
|
62
|
+
}
|
|
63
|
+
return deliveryIds;
|
|
64
|
+
},
|
|
65
|
+
async deliver(db, deliveryId) {
|
|
66
|
+
const rows = await db.execute(sql `SELECT d.id, d.endpoint_id, d.event_type, d.payload, d.attempt, d.max_attempts,
|
|
67
|
+
e.url, e.secret
|
|
68
|
+
FROM ${sql.raw(deliveryTable)} d
|
|
69
|
+
JOIN ${sql.raw(endpointTable)} e ON e.id = d.endpoint_id
|
|
70
|
+
WHERE d.id = ${deliveryId} AND d.completed_at IS NULL`);
|
|
71
|
+
const delivery = rows[0];
|
|
72
|
+
if (delivery === undefined)
|
|
73
|
+
return { success: false, statusCode: null };
|
|
74
|
+
const attempt = delivery['attempt'] + 1;
|
|
75
|
+
const signed = signPayload(delivery['payload'], delivery['secret'], signingOptions);
|
|
76
|
+
let statusCode = null;
|
|
77
|
+
let responseBody = null;
|
|
78
|
+
let success = false;
|
|
79
|
+
try {
|
|
80
|
+
const controller = new AbortController();
|
|
81
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
82
|
+
const response = await fetch(delivery['url'], {
|
|
83
|
+
method: 'POST',
|
|
84
|
+
headers: {
|
|
85
|
+
'Content-Type': 'application/json',
|
|
86
|
+
'X-Webhook-Id': signed.messageId,
|
|
87
|
+
'X-Webhook-Timestamp': signed.timestamp.toString(),
|
|
88
|
+
'X-Webhook-Signature': signed.signature,
|
|
89
|
+
},
|
|
90
|
+
body: signed.body,
|
|
91
|
+
signal: controller.signal,
|
|
92
|
+
});
|
|
93
|
+
clearTimeout(timer);
|
|
94
|
+
statusCode = response.status;
|
|
95
|
+
responseBody = await response.text().catch(() => null);
|
|
96
|
+
success = statusCode >= 200 && statusCode < 300;
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
statusCode = null;
|
|
100
|
+
responseBody = null;
|
|
101
|
+
success = false;
|
|
102
|
+
}
|
|
103
|
+
if (success || attempt >= delivery['max_attempts']) {
|
|
104
|
+
await db.execute(sql `UPDATE ${sql.raw(deliveryTable)}
|
|
105
|
+
SET attempt = ${attempt}, status_code = ${statusCode},
|
|
106
|
+
response_body = ${responseBody}, completed_at = now(), next_retry_at = NULL
|
|
107
|
+
WHERE id = ${deliveryId}`);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
const delayMs = Math.min(initialDelayMs * Math.pow(2, attempt - 1), maxDelayMs);
|
|
111
|
+
const nextRetry = new Date(Date.now() + delayMs);
|
|
112
|
+
await db.execute(sql `UPDATE ${sql.raw(deliveryTable)}
|
|
113
|
+
SET attempt = ${attempt}, status_code = ${statusCode},
|
|
114
|
+
response_body = ${responseBody}, next_retry_at = ${nextRetry.toISOString()}
|
|
115
|
+
WHERE id = ${deliveryId}`);
|
|
116
|
+
}
|
|
117
|
+
return { success, statusCode };
|
|
118
|
+
},
|
|
119
|
+
async processPending(db) {
|
|
120
|
+
const rows = await db.execute(sql `SELECT id FROM ${sql.raw(deliveryTable)}
|
|
121
|
+
WHERE completed_at IS NULL AND next_retry_at <= now()
|
|
122
|
+
ORDER BY next_retry_at ASC
|
|
123
|
+
LIMIT 100`);
|
|
124
|
+
let processed = 0;
|
|
125
|
+
for (const row of rows) {
|
|
126
|
+
await this.deliver(db, row.id);
|
|
127
|
+
processed++;
|
|
128
|
+
}
|
|
129
|
+
return processed;
|
|
130
|
+
},
|
|
131
|
+
async registerEndpoint(db, endpoint) {
|
|
132
|
+
const rows = await db.execute(sql `INSERT INTO ${sql.raw(endpointTable)} (tenant_id, url, secret, events, active)
|
|
133
|
+
VALUES (${endpoint.tenantId}, ${endpoint.url}, ${endpoint.secret},
|
|
134
|
+
${`{${endpoint.events.join(',')}}`}, ${endpoint.active})
|
|
135
|
+
RETURNING id`);
|
|
136
|
+
return (rows[0]).id;
|
|
137
|
+
},
|
|
138
|
+
async listEndpoints(db, tenantId) {
|
|
139
|
+
const rows = await db.execute(sql `SELECT id, tenant_id, url, secret, events, active
|
|
140
|
+
FROM ${sql.raw(endpointTable)}
|
|
141
|
+
WHERE tenant_id = ${tenantId}
|
|
142
|
+
ORDER BY created_at ASC`);
|
|
143
|
+
return rows.map(row => ({
|
|
144
|
+
id: row['id'],
|
|
145
|
+
tenantId: row['tenant_id'],
|
|
146
|
+
url: row['url'],
|
|
147
|
+
secret: row['secret'],
|
|
148
|
+
events: row['events'],
|
|
149
|
+
active: row['active'],
|
|
150
|
+
}));
|
|
151
|
+
},
|
|
152
|
+
async removeEndpoint(db, tenantId, endpointId) {
|
|
153
|
+
const rows = await db.execute(sql `DELETE FROM ${sql.raw(endpointTable)}
|
|
154
|
+
WHERE id = ${endpointId} AND tenant_id = ${tenantId}
|
|
155
|
+
RETURNING id`);
|
|
156
|
+
return rows.length > 0;
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=outbound.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbound.js","sourceRoot":"","sources":["../../src/webhook/outbound.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface WebhookSignatureOptions {
|
|
2
|
+
algorithm?: string;
|
|
3
|
+
encoding?: 'hex' | 'base64';
|
|
4
|
+
}
|
|
5
|
+
export interface SignedPayload {
|
|
6
|
+
body: string;
|
|
7
|
+
signature: string;
|
|
8
|
+
timestamp: number;
|
|
9
|
+
messageId: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function signPayload(payload: unknown, secret: string, options?: WebhookSignatureOptions): SignedPayload;
|
|
12
|
+
//# sourceMappingURL=sign.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sign.d.ts","sourceRoot":"","sources":["../../src/webhook/sign.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,uBAAuB;IACtC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAA;CAC5B;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,WAAW,CACzB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,uBAA4B,GACpC,aAAa,CAWf"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createHmac, randomUUID } from 'node:crypto';
|
|
2
|
+
export function signPayload(payload, secret, options = {}) {
|
|
3
|
+
const algorithm = options.algorithm ?? 'sha256';
|
|
4
|
+
const encoding = options.encoding ?? 'hex';
|
|
5
|
+
const messageId = randomUUID();
|
|
6
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
7
|
+
const body = typeof payload === 'string' ? payload : JSON.stringify(payload);
|
|
8
|
+
const toSign = `${messageId}.${timestamp}.${body}`;
|
|
9
|
+
const signature = createHmac(algorithm, secret).update(toSign).digest(encoding);
|
|
10
|
+
return { body, signature, timestamp, messageId };
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=sign.js.map
|