@praxium/sdk 0.4.87 → 0.5.91
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/dist/webhooks.d.ts +63 -6
- package/dist/webhooks.js +67 -7
- package/package.json +1 -1
package/dist/webhooks.d.ts
CHANGED
|
@@ -70,6 +70,33 @@ interface WebhookFailure {
|
|
|
70
70
|
}
|
|
71
71
|
/** Result of processWebhook() */
|
|
72
72
|
type WebhookResult = WebhookSuccess | WebhookFailure;
|
|
73
|
+
/**
|
|
74
|
+
* Pluggable structured logger for createRevalidationHandler.
|
|
75
|
+
*
|
|
76
|
+
* Each method receives a string message + an optional structured context
|
|
77
|
+
* object. The handler NEVER logs the secret, signature value, or request
|
|
78
|
+
* body — only structural facts (HTTP status, entity name, path counts,
|
|
79
|
+
* error class names, duration).
|
|
80
|
+
*
|
|
81
|
+
* Default: routes to `console.{log,warn,error}` — the correct sink for
|
|
82
|
+
* Vercel/Next.js where function logs surface in the Vercel dashboard.
|
|
83
|
+
* Override to wire Pino / OpenTelemetry, or a no-op logger for tests.
|
|
84
|
+
*/
|
|
85
|
+
interface RevalidationLogger {
|
|
86
|
+
info: (message: string, context?: Record<string, unknown>) => void;
|
|
87
|
+
warn: (message: string, context?: Record<string, unknown>) => void;
|
|
88
|
+
error: (message: string, context?: Record<string, unknown>) => void;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Per-path revalidation failure. Returned in the response body for both
|
|
92
|
+
* 200 partial-success and 500 total-failure cases. `errorName` is the
|
|
93
|
+
* thrown error's class name only — never the message (which could leak
|
|
94
|
+
* PII or implementation details).
|
|
95
|
+
*/
|
|
96
|
+
interface PathFailure {
|
|
97
|
+
path: string;
|
|
98
|
+
errorName: string;
|
|
99
|
+
}
|
|
73
100
|
/** Configuration for createRevalidationHandler() */
|
|
74
101
|
interface RevalidationConfig {
|
|
75
102
|
/** Shared secret for HMAC signature verification */
|
|
@@ -93,6 +120,13 @@ interface RevalidationConfig {
|
|
|
93
120
|
* }
|
|
94
121
|
*/
|
|
95
122
|
pathMap: Record<string, string[]>;
|
|
123
|
+
/**
|
|
124
|
+
* Pluggable structured logger. Defaults to `console.{log,warn,error}`,
|
|
125
|
+
* which is the right sink for Vercel/Next.js (function logs surface in
|
|
126
|
+
* the Vercel dashboard). Pass a no-op logger for tests, or wire in
|
|
127
|
+
* Pino/OpenTelemetry for custom observability stacks.
|
|
128
|
+
*/
|
|
129
|
+
logger?: RevalidationLogger;
|
|
96
130
|
}
|
|
97
131
|
/**
|
|
98
132
|
* Process and verify a Praxium platform webhook.
|
|
@@ -132,11 +166,34 @@ declare function resolveRevalidationPaths(entity: string | undefined, pathMap: R
|
|
|
132
166
|
fallback: boolean;
|
|
133
167
|
};
|
|
134
168
|
/**
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
169
|
+
* Production-grade Next.js route handler for Praxium revalidation webhooks.
|
|
170
|
+
*
|
|
171
|
+
* Wraps `processWebhook()` + `resolveRevalidationPaths()` with three
|
|
172
|
+
* tenant-grade concerns the lower-level primitives don't own:
|
|
173
|
+
*
|
|
174
|
+
* 1. **PII-safe structural logging at every branch** — missing header,
|
|
175
|
+
* bad signature format, expired timestamp, signature mismatch,
|
|
176
|
+
* unknown entity (fallback), per-path failures, total success. Logs
|
|
177
|
+
* NEVER include the secret, signature value, or request body. They
|
|
178
|
+
* DO include: HTTP status, entity name, path counts, error class
|
|
179
|
+
* names, duration. Sink defaults to `console.*` (Vercel/Next.js);
|
|
180
|
+
* override via `config.logger` for tests / Pino / OpenTelemetry.
|
|
181
|
+
*
|
|
182
|
+
* 2. **Per-path try/catch + partial-success semantics** — one failing
|
|
183
|
+
* `revalidatePath()` call does NOT abort the rest of the batch:
|
|
184
|
+
* - all paths succeed → 200, info log, no warnings
|
|
185
|
+
* - partial (N/M failed) → 200 with `failures[]` in body, warn log;
|
|
186
|
+
* the dispatcher sees OK and does NOT retry, because retrying
|
|
187
|
+
* would re-revalidate the paths that already succeeded
|
|
188
|
+
* - all paths fail → 500 with `failures[]`, error log; dispatcher
|
|
189
|
+
* retries the whole webhook
|
|
190
|
+
*
|
|
191
|
+
* 3. **Unknown-entity fallback warning** — `resolveRevalidationPaths`
|
|
192
|
+
* falls back to revalidating every registered path when it doesn't
|
|
193
|
+
* recognise the entity; this emits a warn so drift between the
|
|
194
|
+
* monolith's entity enum and the tenant's `pathMap` is visible.
|
|
195
|
+
*
|
|
196
|
+
* Returns a Fetch-API handler `(request: Request) => Promise<Response>`
|
|
140
197
|
* compatible with Next.js App Router route exports.
|
|
141
198
|
*
|
|
142
199
|
* @example
|
|
@@ -157,4 +214,4 @@ declare function resolveRevalidationPaths(entity: string | undefined, pathMap: R
|
|
|
157
214
|
*/
|
|
158
215
|
declare function createRevalidationHandler(config: RevalidationConfig): (request: Request) => Promise<Response>;
|
|
159
216
|
|
|
160
|
-
export { type ProcessWebhookInput, type RevalidationConfig, WEBHOOK_SIGNATURE_HEADER, type WebhookFailure, type WebhookResult, type WebhookSuccess, createRevalidationHandler, processWebhook, resolveRevalidationPaths };
|
|
217
|
+
export { type PathFailure, type ProcessWebhookInput, type RevalidationConfig, type RevalidationLogger, WEBHOOK_SIGNATURE_HEADER, type WebhookFailure, type WebhookResult, type WebhookSuccess, createRevalidationHandler, processWebhook, resolveRevalidationPaths };
|
package/dist/webhooks.js
CHANGED
|
@@ -83,10 +83,20 @@ function resolveRevalidationPaths(entity, pathMap) {
|
|
|
83
83
|
const allPaths = [...new Set(Object.values(pathMap).flat())];
|
|
84
84
|
return { paths: allPaths, fallback: true };
|
|
85
85
|
}
|
|
86
|
+
var DEFAULT_LOGGER = {
|
|
87
|
+
info: (message, context) => context !== void 0 ? console.log(message, context) : console.log(message),
|
|
88
|
+
warn: (message, context) => context !== void 0 ? console.warn(message, context) : console.warn(message),
|
|
89
|
+
error: (message, context) => context !== void 0 ? console.error(message, context) : console.error(message)
|
|
90
|
+
};
|
|
86
91
|
function createRevalidationHandler(config) {
|
|
92
|
+
const log = config.logger ?? DEFAULT_LOGGER;
|
|
87
93
|
return async (request) => {
|
|
94
|
+
const start = Date.now();
|
|
88
95
|
const signatureHeader = request.headers.get(WEBHOOK_SIGNATURE_HEADER);
|
|
89
96
|
if (!signatureHeader) {
|
|
97
|
+
log.warn(
|
|
98
|
+
`[revalidate] 401 \u2014 missing ${WEBHOOK_SIGNATURE_HEADER} header (${Date.now() - start}ms)`
|
|
99
|
+
);
|
|
90
100
|
return Response.json(
|
|
91
101
|
{ error: `Missing ${WEBHOOK_SIGNATURE_HEADER} header` },
|
|
92
102
|
{ status: 401 }
|
|
@@ -101,6 +111,9 @@ function createRevalidationHandler(config) {
|
|
|
101
111
|
});
|
|
102
112
|
if (!result.valid) {
|
|
103
113
|
const status = result.error === "Invalid JSON body" ? 400 : 401;
|
|
114
|
+
log.warn(
|
|
115
|
+
`[revalidate] ${status} \u2014 ${result.error} (bodyBytes=${rawBody.length}, ${Date.now() - start}ms)`
|
|
116
|
+
);
|
|
104
117
|
return Response.json({ error: result.error }, { status });
|
|
105
118
|
}
|
|
106
119
|
const { paths, fallback } = resolveRevalidationPaths(
|
|
@@ -108,22 +121,69 @@ function createRevalidationHandler(config) {
|
|
|
108
121
|
config.pathMap
|
|
109
122
|
);
|
|
110
123
|
if (fallback) {
|
|
111
|
-
|
|
112
|
-
`[
|
|
124
|
+
log.warn(
|
|
125
|
+
`[revalidate] unknown entity "${result.entity ?? "(none)"}" \u2014 falling back to full revalidation (${paths.length} paths)`
|
|
113
126
|
);
|
|
114
127
|
}
|
|
128
|
+
let revalidatePath;
|
|
115
129
|
try {
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
revalidatePath(path);
|
|
119
|
-
}
|
|
130
|
+
const mod = await import("next/cache");
|
|
131
|
+
revalidatePath = mod.revalidatePath;
|
|
120
132
|
} catch {
|
|
133
|
+
log.error(
|
|
134
|
+
`[revalidate] 500 \u2014 next/cache module not available (${Date.now() - start}ms)`
|
|
135
|
+
);
|
|
121
136
|
return Response.json(
|
|
122
137
|
{ error: "Revalidation failed \u2014 next/cache not available" },
|
|
123
138
|
{ status: 500 }
|
|
124
139
|
);
|
|
125
140
|
}
|
|
126
|
-
|
|
141
|
+
const failures = [];
|
|
142
|
+
for (const path of paths) {
|
|
143
|
+
try {
|
|
144
|
+
revalidatePath(path);
|
|
145
|
+
} catch (error) {
|
|
146
|
+
failures.push({
|
|
147
|
+
path,
|
|
148
|
+
errorName: error instanceof Error ? error.name : "unknown"
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const succeeded = paths.length - failures.length;
|
|
153
|
+
const durationMs = Date.now() - start;
|
|
154
|
+
if (failures.length === paths.length && paths.length > 0) {
|
|
155
|
+
log.error("[revalidate] 500 \u2014 all revalidatePath calls threw", {
|
|
156
|
+
entity: result.entity,
|
|
157
|
+
pathCount: paths.length,
|
|
158
|
+
firstErrorName: failures[0].errorName,
|
|
159
|
+
durationMs
|
|
160
|
+
});
|
|
161
|
+
return Response.json(
|
|
162
|
+
{ error: "Revalidation failed", failures },
|
|
163
|
+
{ status: 500 }
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
if (failures.length > 0) {
|
|
167
|
+
log.warn(
|
|
168
|
+
"[revalidate] partial success \u2014 some revalidatePath calls threw",
|
|
169
|
+
{
|
|
170
|
+
entity: result.entity,
|
|
171
|
+
succeeded,
|
|
172
|
+
failed: failures.length,
|
|
173
|
+
totalPaths: paths.length,
|
|
174
|
+
failures,
|
|
175
|
+
durationMs
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
log.info(
|
|
180
|
+
`[revalidate] 200 \u2014 entity="${result.entity ?? "(none)"}" fallback=${fallback} succeeded=${succeeded}/${paths.length} (${durationMs}ms)`
|
|
181
|
+
);
|
|
182
|
+
return Response.json({
|
|
183
|
+
revalidated: true,
|
|
184
|
+
paths,
|
|
185
|
+
...failures.length > 0 && { failures }
|
|
186
|
+
});
|
|
127
187
|
};
|
|
128
188
|
}
|
|
129
189
|
export {
|