@praxium/sdk 0.2.4 → 0.2.9

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/src/index.ts DELETED
@@ -1,70 +0,0 @@
1
- /**
2
- * @praxium/sdk — TypeScript SDK for Praxium Public Tenant API
3
- *
4
- * @example
5
- * ```ts
6
- * import { createTenantClient, PraxiumNotFoundError } from '@praxium/sdk'
7
- *
8
- * const client = createTenantClient({
9
- * baseUrl: process.env.PRAXIUM_PLATFORM_URL!,
10
- * apiKey: process.env.PRAXIUM_API_KEY!,
11
- * tenantSlug: 'ijfysio',
12
- * locale: 'nl',
13
- * })
14
- *
15
- * // Returns data directly — throws PraxiumError on failure
16
- * const hours = await client.getOpeningHours()
17
- * const team = await client.getTeamMembers()
18
- * ```
19
- */
20
-
21
- // ─── Client Factory ───
22
- export { createTenantClient } from './tenant-client'
23
- export type { TenantClient, TenantClientConfig } from './tenant-client'
24
-
25
- // ─── Error Hierarchy ───
26
- export {
27
- PraxiumError,
28
- PraxiumAuthError,
29
- PraxiumForbiddenError,
30
- PraxiumNotFoundError,
31
- PraxiumRateLimitError,
32
- PraxiumValidationError,
33
- } from './errors'
34
- export type { ValidationDetail } from './errors'
35
-
36
- // ─── Domain Types (re-exported from generated) ───
37
- export type {
38
- // Domain models
39
- OpeningHours,
40
- PublicDaySchedule,
41
- ContactDetails,
42
- ContactFormResult,
43
- Location,
44
- BusinessName,
45
- SocialLinks,
46
- TeamMembers,
47
- PublicTeamMember,
48
- LocalizedSpecialization,
49
- FaqContent,
50
- FaqGroup,
51
- FaqCategory,
52
- FaqItem,
53
- PricingVariants,
54
- PricingVariant,
55
- InsuranceList,
56
- InsuranceInfo,
57
- FeatureList,
58
- FeatureItem,
59
- PaymentMethodList,
60
- PaymentMethod,
61
- PolicyList,
62
- PolicyInfo,
63
- BookableServices,
64
- BookableService,
65
- BookableVariantInfo,
66
- ServiceCategoryInfo,
67
- BilingualText,
68
- // Error type
69
- ApiError,
70
- } from './generated/types.gen'
@@ -1,105 +0,0 @@
1
- /**
2
- * ISR Revalidation Handler for Tenant Websites
3
- *
4
- * When admin updates data in the platform, a webhook triggers
5
- * revalidation on the tenant's public website. This handler
6
- * validates the secret and calls Next.js revalidatePath().
7
- *
8
- * Usage in tenant website's `app/api/revalidate/route.ts`:
9
- *
10
- * ```ts
11
- * import { createRevalidationHandler } from '@praxium/sdk/revalidation'
12
- *
13
- * export const POST = createRevalidationHandler({
14
- * secret: process.env.PRAXIUM_REVALIDATION_SECRET!,
15
- * })
16
- * ```
17
- */
18
-
19
- interface RevalidationConfig {
20
- /** Shared secret for webhook authentication */
21
- secret: string
22
- }
23
-
24
- interface RevalidationBody {
25
- /** Webhook authentication secret */
26
- secret: string
27
- /** Paths to revalidate (defaults to ['/'] if omitted) */
28
- paths?: string[]
29
- }
30
-
31
- /**
32
- * Timing-safe string comparison to prevent timing side-channel attacks.
33
- *
34
- * Uses XOR-based constant-time comparison with TextEncoder (universally
35
- * available in all JS environments: Node.js, edge runtimes, browsers).
36
- * This avoids Node.js-specific APIs (Buffer, crypto.timingSafeEqual) to
37
- * keep the SDK platform-agnostic.
38
- *
39
- * The XOR approach is a well-established cryptographic pattern:
40
- * - Encodes both strings as UTF-8 byte arrays
41
- * - XORs every byte pair, accumulating mismatches via bitwise OR
42
- * - Only checks the accumulated result AFTER the full loop
43
- * - Prevents attackers from inferring secret content by measuring response times
44
- *
45
- * Note: Length difference is detected early (unavoidable information leak),
46
- * but content comparison is always constant-time.
47
- */
48
- function isSecretValid(provided: string, expected: string): boolean {
49
- const encoder = new TextEncoder()
50
- const a = encoder.encode(provided)
51
- const b = encoder.encode(expected)
52
-
53
- if (a.length !== b.length) {
54
- return false
55
- }
56
-
57
- let mismatch = 0
58
- for (let i = 0; i < a.length; i++) {
59
- mismatch |= a[i] ^ b[i]
60
- }
61
-
62
- return mismatch === 0
63
- }
64
-
65
- /**
66
- * Creates a Next.js route handler that revalidates ISR pages
67
- * when triggered by the Praxium platform webhook.
68
- */
69
- export function createRevalidationHandler(config: RevalidationConfig) {
70
- return async (request: Request): Promise<Response> => {
71
- let body: RevalidationBody
72
-
73
- try {
74
- body = await request.json()
75
- } catch {
76
- return Response.json({ error: 'Invalid JSON body' }, { status: 400 })
77
- }
78
-
79
- if (!isSecretValid(body.secret, config.secret)) {
80
- return Response.json(
81
- { error: 'Invalid revalidation secret' },
82
- { status: 401 }
83
- )
84
- }
85
-
86
- const paths = body.paths ?? ['/']
87
-
88
- // Dynamic import to avoid bundling next/cache in non-Next.js environments.
89
- // Type declarations for next/cache are provided by src/types/next-cache.d.ts
90
- // so the DTS generator can emit correct types without next being installed.
91
- try {
92
- const { revalidatePath } = await import('next/cache')
93
- for (const path of paths) {
94
- revalidatePath(path)
95
- }
96
- } catch {
97
- return Response.json(
98
- { error: 'Revalidation failed — next/cache not available' },
99
- { status: 500 }
100
- )
101
- }
102
-
103
- return Response.json({ revalidated: true, paths })
104
- }
105
- }
@@ -1,180 +0,0 @@
1
- /**
2
- * Praxium Tenant Client
3
- *
4
- * High-level wrapper around the auto-generated SDK that binds the
5
- * tenant slug and API key, giving consumers a clean throw-on-error API:
6
- *
7
- * ```ts
8
- * import { createTenantClient, PraxiumNotFoundError } from '@praxium/sdk'
9
- *
10
- * const client = createTenantClient({
11
- * baseUrl: 'https://platform.praxium.nl',
12
- * apiKey: process.env.PRAXIUM_API_KEY!,
13
- * tenantSlug: 'ijfysio',
14
- * })
15
- *
16
- * // Returns data directly — throws PraxiumError on failure
17
- * const hours = await client.getOpeningHours()
18
- * ```
19
- */
20
- import { createClient, createConfig } from './generated/client'
21
- import type {
22
- ClientOptions,
23
- ApiError,
24
- SubmitContactFormData,
25
- OpeningHours,
26
- ContactDetails,
27
- ContactFormResult,
28
- Location,
29
- BusinessName,
30
- SocialLinks,
31
- TeamMembers,
32
- FaqContent,
33
- PricingVariants,
34
- InsuranceList,
35
- FeatureList,
36
- PaymentMethodList,
37
- PolicyList,
38
- BookableServices,
39
- } from './generated/types.gen'
40
- import * as sdk from './generated/sdk.gen'
41
- import { PraxiumError, STATUS_ERROR_MAP } from './errors'
42
-
43
- export interface TenantClientConfig {
44
- /** Platform base URL (e.g., 'https://platform.praxium.nl') */
45
- baseUrl: string
46
- /** HMAC-signed API key (format: hmac_v1_{slug}_{timestamp}_{signature}) */
47
- apiKey: string
48
- /** Tenant identifier slug (must match the slug in the API key) */
49
- tenantSlug: string
50
- /** Accept-Language header for locale-aware responses (e.g., 'nl', 'en') */
51
- locale: string
52
- }
53
-
54
- // ─── Unwrap Helper ───────────────────────────────────────────────────
55
-
56
- /**
57
- * The auto-generated SDK returns `{ data, error, request, response }`.
58
- *
59
- * On success, `data` is the parsed JSON body which the API wraps as
60
- * `{ data: <payload> }`. The response-type mapping gives us
61
- * `{ data: SomeResponse }` for `data`.
62
- *
63
- * On error, `error` is the parsed `ApiError` JSON body.
64
- *
65
- * `unwrapOrThrow` extracts the inner payload on success, or throws a
66
- * typed `PraxiumError` subclass mapped from the HTTP status code.
67
- */
68
- function unwrapOrThrow<TPayload>(result: {
69
- data?: { data: TPayload } | undefined
70
- error?: ApiError | undefined
71
- response: Response
72
- }): TPayload {
73
- // Success path — return the inner data payload
74
- if (result.data !== undefined && result.error === undefined) {
75
- return result.data.data
76
- }
77
-
78
- // If both data and error are undefined, the generated client encountered
79
- // a network-level failure; fall through to the error path below.
80
-
81
- // Error path — map HTTP status to typed error
82
- const status = result.response?.status ?? 500
83
- const apiError = result.error as ApiError | undefined
84
-
85
- const errorCode = apiError?.errorCode ?? 'UNKNOWN_ERROR'
86
- const message = apiError?.message ?? `HTTP ${status} error`
87
- const details = apiError?.details
88
-
89
- const ErrorClass = STATUS_ERROR_MAP[status]
90
-
91
- if (ErrorClass) {
92
- throw new ErrorClass(errorCode, message, details)
93
- }
94
-
95
- // Fallback for unmapped status codes
96
- throw new PraxiumError(status, errorCode, message, details)
97
- }
98
-
99
- // ─── Client Factory ──────────────────────────────────────────────────
100
-
101
- export function createTenantClient(config: TenantClientConfig) {
102
- const clientInstance = createClient(
103
- createConfig<ClientOptions>({
104
- baseUrl: config.baseUrl as ClientOptions['baseUrl'],
105
- auth: config.apiKey,
106
- })
107
- )
108
-
109
- // Set Accept-Language header on all requests for locale-aware responses
110
- clientInstance.interceptors.request.use((request) => {
111
- request.headers.set('Accept-Language', config.locale)
112
- return request
113
- })
114
-
115
- const path = { tenantSlug: config.tenantSlug }
116
-
117
- return {
118
- // ─── Layout Endpoints ───
119
- getOpeningHours: (): Promise<OpeningHours> =>
120
- sdk.getOpeningHours({ client: clientInstance, path }).then(unwrapOrThrow),
121
-
122
- getContactDetails: (): Promise<ContactDetails> =>
123
- sdk
124
- .getContactDetails({ client: clientInstance, path })
125
- .then(unwrapOrThrow),
126
-
127
- getLocation: (): Promise<Location> =>
128
- sdk.getLocation({ client: clientInstance, path }).then(unwrapOrThrow),
129
-
130
- getBusinessName: (): Promise<BusinessName> =>
131
- sdk.getBusinessName({ client: clientInstance, path }).then(unwrapOrThrow),
132
-
133
- getSocialLinks: (): Promise<SocialLinks> =>
134
- sdk.getSocialLinks({ client: clientInstance, path }).then(unwrapOrThrow),
135
-
136
- // ─── Content Endpoints ───
137
- getTeamMembers: (): Promise<TeamMembers> =>
138
- sdk.getTeamMembers({ client: clientInstance, path }).then(unwrapOrThrow),
139
-
140
- getFaq: (): Promise<FaqContent> =>
141
- sdk.getFaq({ client: clientInstance, path }).then(unwrapOrThrow),
142
-
143
- getServiceVariants: (): Promise<PricingVariants> =>
144
- sdk
145
- .getServiceVariants({ client: clientInstance, path })
146
- .then(unwrapOrThrow),
147
-
148
- getInsuranceInfo: (): Promise<InsuranceList> =>
149
- sdk
150
- .getInsuranceInfo({ client: clientInstance, path })
151
- .then(unwrapOrThrow),
152
-
153
- getFeatures: (): Promise<FeatureList> =>
154
- sdk.getFeatures({ client: clientInstance, path }).then(unwrapOrThrow),
155
-
156
- getPaymentMethods: (): Promise<PaymentMethodList> =>
157
- sdk
158
- .getPaymentMethods({ client: clientInstance, path })
159
- .then(unwrapOrThrow),
160
-
161
- getPolicyInfo: (): Promise<PolicyList> =>
162
- sdk.getPolicyInfo({ client: clientInstance, path }).then(unwrapOrThrow),
163
-
164
- getBookableServices: (): Promise<BookableServices> =>
165
- sdk
166
- .getBookableServices({ client: clientInstance, path })
167
- .then(unwrapOrThrow),
168
-
169
- // ─── Contact Form ───
170
- submitContactForm: (
171
- body: SubmitContactFormData['body']
172
- ): Promise<ContactFormResult> =>
173
- sdk
174
- .submitContactForm({ client: clientInstance, path, body })
175
- .then(unwrapOrThrow),
176
- }
177
- }
178
-
179
- /** Return type of createTenantClient for type inference */
180
- export type TenantClient = ReturnType<typeof createTenantClient>
@@ -1,24 +0,0 @@
1
- /**
2
- * Type declarations for next/cache — optional peer dependency.
3
- *
4
- * next is declared as an optional peerDependency and is NOT installed
5
- * during SDK CI builds. This stub provides type information for the
6
- * dynamic import in revalidation.ts so TypeScript's DTS generator
7
- * can emit correct declarations without requiring next to be installed.
8
- *
9
- * Only the functions actually used by the SDK are declared here.
10
- * Consumers install next themselves (it's a peer dependency).
11
- */
12
- declare module 'next/cache' {
13
- /**
14
- * Revalidates data associated with a specific path.
15
- * @see https://nextjs.org/docs/app/api-reference/functions/revalidatePath
16
- */
17
- export function revalidatePath(path: string, type?: 'page' | 'layout'): void
18
-
19
- /**
20
- * Revalidates data associated with a cache tag.
21
- * @see https://nextjs.org/docs/app/api-reference/functions/revalidateTag
22
- */
23
- export function revalidateTag(tag: string): void
24
- }