@praxium/sdk 0.4.85 → 0.5.89
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/index.d.ts +16 -2
- package/dist/index.js +8 -2
- package/dist/webhooks.d.ts +63 -6
- package/dist/webhooks.js +67 -7
- package/package.json +5 -1
package/dist/index.d.ts
CHANGED
|
@@ -674,7 +674,7 @@ type ClientLocale = SupportedLocale | '*';
|
|
|
674
674
|
/**
|
|
675
675
|
* Extract the tenant slug from a profile-scoped API key.
|
|
676
676
|
*
|
|
677
|
-
* Key format:
|
|
677
|
+
* Key format: `${API_KEY_PREFIX}_{tenantSlug}_{profileSlug}_{timestamp}_{signature}`
|
|
678
678
|
* Parts: [0]praxium [1]v1 [2]tenantSlug [3]profileSlug [4]timestamp [5]signature
|
|
679
679
|
*
|
|
680
680
|
* @throws Error if key format is invalid (wrong prefix or insufficient parts)
|
|
@@ -719,6 +719,20 @@ declare function createPraxiumClient(config: PraxiumClientConfig & {
|
|
|
719
719
|
/** Locale-specific mode: returns PublicTeamMember[] with resolved labels/values */
|
|
720
720
|
declare function createPraxiumClient(config: PraxiumClientConfig): PraxiumClient;
|
|
721
721
|
|
|
722
|
+
/** Version-prefixed identifier at the start of every Praxium API key. */
|
|
723
|
+
declare const API_KEY_PREFIX: "praxium_v1";
|
|
724
|
+
/** Separator used between all key segments. */
|
|
725
|
+
declare const API_KEY_SEPARATOR: "_";
|
|
726
|
+
/**
|
|
727
|
+
* Convenience composition: `praxium_v1_` (prefix + separator).
|
|
728
|
+
*
|
|
729
|
+
* Use this when validating that a string starts with the full Praxium API
|
|
730
|
+
* key prefix. Avoids re-composing `${API_KEY_PREFIX}${API_KEY_SEPARATOR}`
|
|
731
|
+
* at every call site. Compile-time `as const` so consumer Zod schemas can
|
|
732
|
+
* pass it to `.startsWith()` without widening to `string`.
|
|
733
|
+
*/
|
|
734
|
+
declare const API_KEY_PREFIX_WITH_SEPARATOR: "praxium_v1_";
|
|
735
|
+
|
|
722
736
|
/**
|
|
723
737
|
* Typed error hierarchy for the Praxium SDK.
|
|
724
738
|
*
|
|
@@ -849,4 +863,4 @@ declare function getCustomField(member: MultilingualTeamMember, identifier: stri
|
|
|
849
863
|
*/
|
|
850
864
|
declare function localizeText(text: MultilingualText | null | undefined, locale: SupportedLocale): string;
|
|
851
865
|
|
|
852
|
-
export { type ApiError, type BilingualText, type BookableService, type BookableServices, type BookableVariantInfo, type BusinessName, type ClientLocale, type ContactDetails, type ContactFormResult, type CustomField, type FaqCategory, type FaqContent, type FaqGroup, type FaqItem, type FeatureItem, type FeatureList, type InsuranceInfo, type InsuranceList, type Location, type MultilingualCustomField, type MultilingualPraxiumClient, type MultilingualTeamMember, type MultilingualText, type OpeningHours, type PaymentMethod, type PaymentMethodList, type PolicyInfo, type PolicyList, PraxiumAuthError, type PraxiumClient, type PraxiumClientConfig, PraxiumError, PraxiumForbiddenError, PraxiumNotFoundError, PraxiumRateLimitError, PraxiumValidationError, type PricingVariant, type PricingVariants, type PublicDaySchedule, type PublicTeamMember, type ServiceCategoryInfo, type SocialLinks, type SupportedLocale, type TeamMembers, type ValidationDetail, createPraxiumClient, extractTenantSlugFromApiKey, getCustomField, getCustomFieldValue, localizeText };
|
|
866
|
+
export { API_KEY_PREFIX, API_KEY_PREFIX_WITH_SEPARATOR, API_KEY_SEPARATOR, type ApiError, type BilingualText, type BookableService, type BookableServices, type BookableVariantInfo, type BusinessName, type ClientLocale, type ContactDetails, type ContactFormResult, type CustomField, type FaqCategory, type FaqContent, type FaqGroup, type FaqItem, type FeatureItem, type FeatureList, type InsuranceInfo, type InsuranceList, type Location, type MultilingualCustomField, type MultilingualPraxiumClient, type MultilingualTeamMember, type MultilingualText, type OpeningHours, type PaymentMethod, type PaymentMethodList, type PolicyInfo, type PolicyList, PraxiumAuthError, type PraxiumClient, type PraxiumClientConfig, PraxiumError, PraxiumForbiddenError, PraxiumNotFoundError, PraxiumRateLimitError, PraxiumValidationError, type PricingVariant, type PricingVariants, type PublicDaySchedule, type PublicTeamMember, type ServiceCategoryInfo, type SocialLinks, type SupportedLocale, type TeamMembers, type ValidationDetail, createPraxiumClient, extractTenantSlugFromApiKey, getCustomField, getCustomFieldValue, localizeText };
|
package/dist/index.js
CHANGED
|
@@ -928,16 +928,19 @@ var STATUS_ERROR_MAP = {
|
|
|
928
928
|
429: PraxiumRateLimitError
|
|
929
929
|
};
|
|
930
930
|
|
|
931
|
-
// src/
|
|
931
|
+
// src/api-key.constants.ts
|
|
932
932
|
var API_KEY_PREFIX = "praxium_v1";
|
|
933
933
|
var API_KEY_SEPARATOR = "_";
|
|
934
|
+
var API_KEY_PREFIX_WITH_SEPARATOR = `${API_KEY_PREFIX}${API_KEY_SEPARATOR}`;
|
|
935
|
+
|
|
936
|
+
// src/tenant-client.ts
|
|
934
937
|
function extractTenantSlugFromApiKey(apiKey) {
|
|
935
938
|
const parts = apiKey.split(API_KEY_SEPARATOR);
|
|
936
939
|
if (parts.length < 6 || `${parts[0]}${API_KEY_SEPARATOR}${parts[1]}` !== API_KEY_PREFIX) {
|
|
937
940
|
throw new PraxiumError(
|
|
938
941
|
400,
|
|
939
942
|
"INVALID_API_KEY_FORMAT",
|
|
940
|
-
`API key must start with '${API_KEY_PREFIX}' and contain at least 6 segments. Format:
|
|
943
|
+
`API key must start with '${API_KEY_PREFIX}' and contain at least 6 segments. Format: ${API_KEY_PREFIX}${API_KEY_SEPARATOR}{tenantSlug}${API_KEY_SEPARATOR}{profileSlug}${API_KEY_SEPARATOR}{timestamp}${API_KEY_SEPARATOR}{signature}`
|
|
941
944
|
);
|
|
942
945
|
}
|
|
943
946
|
return parts[2];
|
|
@@ -1013,6 +1016,9 @@ function localizeText(text, locale) {
|
|
|
1013
1016
|
return text[locale] || text["en"] || Object.values(text)[0] || "";
|
|
1014
1017
|
}
|
|
1015
1018
|
export {
|
|
1019
|
+
API_KEY_PREFIX,
|
|
1020
|
+
API_KEY_PREFIX_WITH_SEPARATOR,
|
|
1021
|
+
API_KEY_SEPARATOR,
|
|
1016
1022
|
PraxiumAuthError,
|
|
1017
1023
|
PraxiumError,
|
|
1018
1024
|
PraxiumForbiddenError,
|
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 {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@praxium/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.89",
|
|
4
4
|
"description": "Official TypeScript SDK for the Praxium platform API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -19,14 +19,18 @@
|
|
|
19
19
|
"scripts": {
|
|
20
20
|
"generate": "openapi-ts",
|
|
21
21
|
"build": "tsup",
|
|
22
|
+
"lint": "eslint src/ tests/",
|
|
22
23
|
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json",
|
|
23
24
|
"test": "vitest run",
|
|
24
25
|
"test:watch": "vitest"
|
|
25
26
|
},
|
|
26
27
|
"devDependencies": {
|
|
28
|
+
"@eslint/js": "^9.0.0",
|
|
27
29
|
"@hey-api/openapi-ts": "^0.92.4",
|
|
30
|
+
"eslint": "^9.0.0",
|
|
28
31
|
"tsup": "^8.0.0",
|
|
29
32
|
"typescript": "^5.7.0",
|
|
33
|
+
"typescript-eslint": "^8.0.0",
|
|
30
34
|
"vitest": "^4.0.18"
|
|
31
35
|
},
|
|
32
36
|
"peerDependencies": {
|