@bookinglab/booking-journey-api 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/dist/index.mjs ADDED
@@ -0,0 +1,244 @@
1
+ import { createContext, useMemo, useContext } from 'react';
2
+ import { jsx } from 'react/jsx-runtime';
3
+ import { useMutation } from '@tanstack/react-query';
4
+
5
+ // src/core.ts
6
+ var ApiClient = class {
7
+ constructor(config) {
8
+ this.baseUrl = config.baseUrl.replace(/\/$/, "");
9
+ this.headers = config.headers || {};
10
+ this.timeout = config.timeout || 3e4;
11
+ }
12
+ /**
13
+ * Update client configuration
14
+ */
15
+ setConfig(config) {
16
+ if (config.baseUrl) this.baseUrl = config.baseUrl.replace(/\/$/, "");
17
+ if (config.headers) this.headers = { ...this.headers, ...config.headers };
18
+ if (config.timeout) this.timeout = config.timeout;
19
+ }
20
+ /**
21
+ * Set authorization token
22
+ */
23
+ setAuthToken(token) {
24
+ this.headers["Authorization"] = `Bearer ${token}`;
25
+ }
26
+ /**
27
+ * Make an HTTP request
28
+ */
29
+ async request(endpoint, options = {}) {
30
+ const { params, ...fetchOptions } = options;
31
+ let url = `${this.baseUrl}${endpoint}`;
32
+ if (params) {
33
+ const searchParams = new URLSearchParams();
34
+ Object.entries(params).forEach(([key, value]) => {
35
+ if (value !== void 0) {
36
+ searchParams.append(key, String(value));
37
+ }
38
+ });
39
+ const queryString = searchParams.toString();
40
+ if (queryString) {
41
+ url += `?${queryString}`;
42
+ }
43
+ }
44
+ const headers = {
45
+ "Content-Type": "application/json",
46
+ ...this.headers,
47
+ ...fetchOptions.headers || {}
48
+ };
49
+ const controller = new AbortController();
50
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
51
+ try {
52
+ const response = await fetch(url, {
53
+ ...fetchOptions,
54
+ headers,
55
+ signal: controller.signal
56
+ });
57
+ clearTimeout(timeoutId);
58
+ if (!response.ok) {
59
+ throw await this.handleError(response);
60
+ }
61
+ const data = await response.json();
62
+ return {
63
+ data,
64
+ status: response.status,
65
+ headers: response.headers
66
+ };
67
+ } catch (error) {
68
+ clearTimeout(timeoutId);
69
+ throw this.normalizeError(error);
70
+ }
71
+ }
72
+ /**
73
+ * GET request
74
+ */
75
+ async get(endpoint, options) {
76
+ return this.request(endpoint, { ...options, method: "GET" });
77
+ }
78
+ /**
79
+ * POST request
80
+ */
81
+ async post(endpoint, body, options) {
82
+ return this.request(endpoint, {
83
+ ...options,
84
+ method: "POST",
85
+ body: body ? JSON.stringify(body) : void 0
86
+ });
87
+ }
88
+ /**
89
+ * PUT request
90
+ */
91
+ async put(endpoint, body, options) {
92
+ return this.request(endpoint, {
93
+ ...options,
94
+ method: "PUT",
95
+ body: body ? JSON.stringify(body) : void 0
96
+ });
97
+ }
98
+ /**
99
+ * DELETE request
100
+ */
101
+ async delete(endpoint, options) {
102
+ return this.request(endpoint, { ...options, method: "DELETE" });
103
+ }
104
+ /**
105
+ * Handle error responses
106
+ */
107
+ async handleError(response) {
108
+ let message = `HTTP ${response.status}: ${response.statusText}`;
109
+ let details;
110
+ try {
111
+ details = await response.json();
112
+ if (details.message) {
113
+ message = details.message;
114
+ } else if (details.error) {
115
+ message = details.error;
116
+ }
117
+ } catch {
118
+ }
119
+ return {
120
+ message,
121
+ status: response.status,
122
+ details
123
+ };
124
+ }
125
+ /**
126
+ * Normalize errors to consistent format
127
+ */
128
+ normalizeError(error) {
129
+ if (error.name === "AbortError") {
130
+ return {
131
+ message: "Request timeout",
132
+ code: "TIMEOUT"
133
+ };
134
+ }
135
+ if ("status" in error) {
136
+ return error;
137
+ }
138
+ return {
139
+ message: error.message || "Unknown error",
140
+ code: "UNKNOWN"
141
+ };
142
+ }
143
+ };
144
+
145
+ // src/jrni.ts
146
+ var JrniClient = class extends ApiClient {
147
+ constructor(baseUrl, config) {
148
+ super({ baseUrl });
149
+ this.appId = config.appId;
150
+ this.appKey = config.appKey;
151
+ }
152
+ /**
153
+ * Login to JRNI
154
+ */
155
+ async login(credentials) {
156
+ return this.post("/login", credentials, {
157
+ headers: {
158
+ "Content-Type": "application/json",
159
+ "App-Id": this.appId,
160
+ "App-Key": this.appKey
161
+ }
162
+ });
163
+ }
164
+ /**
165
+ * Update JRNI configuration
166
+ */
167
+ setJrniConfig(config) {
168
+ if (config.appId) this.appId = config.appId;
169
+ if (config.appKey) this.appKey = config.appKey;
170
+ }
171
+ };
172
+ function createJrniClient(baseUrl, config) {
173
+ return new JrniClient(baseUrl, config);
174
+ }
175
+ var ApiClientContext = createContext(void 0);
176
+ function ApiClientProvider({
177
+ children,
178
+ jrniBaseUrl,
179
+ jrniConfig
180
+ }) {
181
+ const jrniClient = useMemo(() => {
182
+ if (jrniBaseUrl && jrniConfig) {
183
+ return new JrniClient(jrniBaseUrl, jrniConfig);
184
+ }
185
+ return null;
186
+ }, [jrniBaseUrl, jrniConfig?.appId, jrniConfig?.appKey]);
187
+ const value = useMemo(
188
+ () => ({
189
+ jrniClient
190
+ }),
191
+ [jrniClient]
192
+ );
193
+ return /* @__PURE__ */ jsx(ApiClientContext.Provider, { value, children });
194
+ }
195
+ function useApiClientContext() {
196
+ const context = useContext(ApiClientContext);
197
+ if (context === void 0) {
198
+ throw new Error("useApiClientContext must be used within an ApiClientProvider");
199
+ }
200
+ return context;
201
+ }
202
+ var JrniContext = createContext(void 0);
203
+ function JrniProvider({ children, baseUrl, config }) {
204
+ const client = useMemo(() => {
205
+ return new JrniClient(baseUrl, config);
206
+ }, [baseUrl, config.appId, config.appKey]);
207
+ const value = useMemo(() => ({ client }), [client]);
208
+ return /* @__PURE__ */ jsx(JrniContext.Provider, { value, children });
209
+ }
210
+ function useJrniContext() {
211
+ const context = useContext(JrniContext);
212
+ if (context === void 0) {
213
+ throw new Error("useJrniContext must be used within a JrniProvider");
214
+ }
215
+ return context.client;
216
+ }
217
+
218
+ // src/hooks/useApiClient.ts
219
+ function useJrniClient() {
220
+ try {
221
+ return useJrniContext();
222
+ } catch {
223
+ const context = useApiClientContext();
224
+ if (!context.jrniClient) {
225
+ throw new Error(
226
+ "JRNI client not configured. Wrap your app with ApiClientProvider or JrniProvider."
227
+ );
228
+ }
229
+ return context.jrniClient;
230
+ }
231
+ }
232
+ function useLogin() {
233
+ const client = useJrniClient();
234
+ return useMutation({
235
+ mutationFn: async (credentials) => {
236
+ const response = await client.login(credentials);
237
+ return response.data;
238
+ }
239
+ });
240
+ }
241
+
242
+ export { ApiClient, ApiClientProvider, JrniClient, JrniProvider, createJrniClient, useApiClientContext, useJrniClient, useJrniContext, useLogin };
243
+ //# sourceMappingURL=index.mjs.map
244
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core.ts","../src/jrni.ts","../src/providers/ApiClientProvider.tsx","../src/providers/JrniProvider.tsx","../src/hooks/useApiClient.ts","../src/hooks/useJrni.ts"],"names":["createContext","useMemo","jsx","useContext"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Core API Client\n * Base class for making HTTP requests\n */\n\nimport { ApiClientConfig, RequestOptions, ApiResponse, ApiError } from './types';\n\nexport class ApiClient {\n protected baseUrl: string;\n protected headers: Record<string, string>;\n protected timeout: number;\n\n constructor(config: ApiClientConfig) {\n this.baseUrl = config.baseUrl.replace(/\\/$/, '');\n this.headers = config.headers || {};\n this.timeout = config.timeout || 30000;\n }\n\n /**\n * Update client configuration\n */\n setConfig(config: Partial<ApiClientConfig>) {\n if (config.baseUrl) this.baseUrl = config.baseUrl.replace(/\\/$/, '');\n if (config.headers) this.headers = { ...this.headers, ...config.headers };\n if (config.timeout) this.timeout = config.timeout;\n }\n\n /**\n * Set authorization token\n */\n setAuthToken(token: string) {\n this.headers['Authorization'] = `Bearer ${token}`;\n }\n\n /**\n * Make an HTTP request\n */\n protected async request<T>(\n endpoint: string,\n options: RequestOptions = {}\n ): Promise<ApiResponse<T>> {\n const { params, ...fetchOptions } = options;\n\n // Build URL with query parameters\n let url = `${this.baseUrl}${endpoint}`;\n if (params) {\n const searchParams = new URLSearchParams();\n Object.entries(params).forEach(([key, value]) => {\n if (value !== undefined) {\n searchParams.append(key, String(value));\n }\n });\n const queryString = searchParams.toString();\n if (queryString) {\n url += `?${queryString}`;\n }\n }\n\n // Merge headers\n const headers = {\n 'Content-Type': 'application/json',\n ...this.headers,\n ...((fetchOptions.headers as Record<string, string>) || {}),\n };\n\n // Create abort controller for timeout\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const response = await fetch(url, {\n ...fetchOptions,\n headers,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n throw await this.handleError(response);\n }\n\n const data = await response.json();\n\n return {\n data,\n status: response.status,\n headers: response.headers,\n };\n } catch (error) {\n clearTimeout(timeoutId);\n throw this.normalizeError(error as Error);\n }\n }\n\n /**\n * GET request\n */\n protected async get<T>(endpoint: string, options?: RequestOptions): Promise<ApiResponse<T>> {\n return this.request<T>(endpoint, { ...options, method: 'GET' });\n }\n\n /**\n * POST request\n */\n protected async post<T>(\n endpoint: string,\n body?: any,\n options?: RequestOptions\n ): Promise<ApiResponse<T>> {\n return this.request<T>(endpoint, {\n ...options,\n method: 'POST',\n body: body ? JSON.stringify(body) : undefined,\n });\n }\n\n /**\n * PUT request\n */\n protected async put<T>(\n endpoint: string,\n body?: any,\n options?: RequestOptions\n ): Promise<ApiResponse<T>> {\n return this.request<T>(endpoint, {\n ...options,\n method: 'PUT',\n body: body ? JSON.stringify(body) : undefined,\n });\n }\n\n /**\n * DELETE request\n */\n protected async delete<T>(endpoint: string, options?: RequestOptions): Promise<ApiResponse<T>> {\n return this.request<T>(endpoint, { ...options, method: 'DELETE' });\n }\n\n /**\n * Handle error responses\n */\n private async handleError(response: Response): Promise<ApiError> {\n let message = `HTTP ${response.status}: ${response.statusText}`;\n let details: any;\n\n try {\n details = await response.json();\n if (details.message) {\n message = details.message;\n } else if (details.error) {\n message = details.error;\n }\n } catch {\n // Response body is not JSON\n }\n\n return {\n message,\n status: response.status,\n details,\n };\n }\n\n /**\n * Normalize errors to consistent format\n */\n private normalizeError(error: Error): ApiError {\n if (error.name === 'AbortError') {\n return {\n message: 'Request timeout',\n code: 'TIMEOUT',\n };\n }\n\n if ('status' in error) {\n return error as ApiError;\n }\n\n return {\n message: error.message || 'Unknown error',\n code: 'UNKNOWN',\n };\n }\n}\n","/**\n * JRNI API Client\n * Provides methods for interacting with the JRNI API\n */\n\nimport { ApiClient } from './core';\nimport { LoginRequest, LoginResponse, ApiResponse, JrniConfig } from './types';\n\nexport class JrniClient extends ApiClient {\n private appId: string;\n private appKey: string;\n\n constructor(baseUrl: string, config: JrniConfig) {\n super({ baseUrl });\n this.appId = config.appId;\n this.appKey = config.appKey;\n }\n\n /**\n * Login to JRNI\n */\n async login(credentials: LoginRequest): Promise<ApiResponse<LoginResponse>> {\n return this.post<LoginResponse>('/login', credentials, {\n headers: {\n 'Content-Type': 'application/json',\n 'App-Id': this.appId,\n 'App-Key': this.appKey,\n },\n });\n }\n\n /**\n * Update JRNI configuration\n */\n setJrniConfig(config: Partial<JrniConfig>) {\n if (config.appId) this.appId = config.appId;\n if (config.appKey) this.appKey = config.appKey;\n }\n}\n\n/**\n * Create a new JRNI client instance\n */\nexport function createJrniClient(baseUrl: string, config: JrniConfig) {\n return new JrniClient(baseUrl, config);\n}\n","/**\n * React Context Provider for API Clients\n * Combined provider for applications using multiple API clients\n */\n\nimport React, { createContext, useContext, useMemo, ReactNode } from 'react';\nimport { JrniClient } from '../jrni';\nimport { JrniConfig } from '../types';\n\ninterface ApiClientContextValue {\n jrniClient: JrniClient | null;\n}\n\ninterface ApiClientProviderProps {\n children: ReactNode;\n jrniBaseUrl?: string;\n jrniConfig?: JrniConfig;\n}\n\nconst ApiClientContext = createContext<ApiClientContextValue | undefined>(undefined);\n\n/**\n * Combined provider for multiple API clients\n */\nexport function ApiClientProvider({\n children,\n jrniBaseUrl,\n jrniConfig,\n}: ApiClientProviderProps) {\n const jrniClient = useMemo(() => {\n if (jrniBaseUrl && jrniConfig) {\n return new JrniClient(jrniBaseUrl, jrniConfig);\n }\n return null;\n }, [jrniBaseUrl, jrniConfig?.appId, jrniConfig?.appKey]);\n\n const value = useMemo(\n () => ({\n jrniClient,\n }),\n [jrniClient]\n );\n\n return <ApiClientContext.Provider value={value}>{children}</ApiClientContext.Provider>;\n}\n\n/**\n * Hook to access API client context\n */\nexport function useApiClientContext() {\n const context = useContext(ApiClientContext);\n if (context === undefined) {\n throw new Error('useApiClientContext must be used within an ApiClientProvider');\n }\n return context;\n}\n","/**\n * React Context Provider for JRNI API Client\n * Standalone provider for apps that only use JRNI\n */\n\nimport React, { createContext, useContext, useMemo, ReactNode } from 'react';\nimport { JrniClient } from '../jrni';\nimport { JrniConfig } from '../types';\n\ninterface JrniContextValue {\n client: JrniClient;\n}\n\ninterface JrniProviderProps {\n children: ReactNode;\n baseUrl: string;\n config: JrniConfig;\n}\n\nconst JrniContext = createContext<JrniContextValue | undefined>(undefined);\n\n/**\n * Provider component for JRNI client\n */\nexport function JrniProvider({ children, baseUrl, config }: JrniProviderProps) {\n const client = useMemo(() => {\n return new JrniClient(baseUrl, config);\n }, [baseUrl, config.appId, config.appKey]);\n\n const value = useMemo(() => ({ client }), [client]);\n\n return <JrniContext.Provider value={value}>{children}</JrniContext.Provider>;\n}\n\n/**\n * Hook to access JRNI client from context\n */\nexport function useJrniContext() {\n const context = useContext(JrniContext);\n if (context === undefined) {\n throw new Error('useJrniContext must be used within a JrniProvider');\n }\n return context.client;\n}\n","/**\n * Hook to access API clients from context\n */\n\nimport { useApiClientContext } from '../providers/ApiClientProvider';\nimport { useJrniContext } from '../providers/JrniProvider';\n\n/**\n * Hook to get JRNI client from either ApiClientProvider or JrniProvider\n */\nexport function useJrniClient() {\n // Try to get from standalone provider first\n try {\n return useJrniContext();\n } catch {\n // Fall back to combined provider\n const context = useApiClientContext();\n if (!context.jrniClient) {\n throw new Error(\n 'JRNI client not configured. Wrap your app with ApiClientProvider or JrniProvider.'\n );\n }\n return context.jrniClient;\n }\n}\n","/**\n * React hooks for JRNI API\n */\n\nimport { useMutation } from '@tanstack/react-query';\nimport { useJrniClient } from './useApiClient';\nimport { LoginRequest, LoginResponse } from '../types';\n\n/**\n * Hook for JRNI login\n */\nexport function useLogin() {\n const client = useJrniClient();\n\n return useMutation({\n mutationFn: async (credentials: LoginRequest) => {\n const response = await client.login(credentials);\n return response.data;\n },\n });\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@bookinglab/booking-journey-api",
3
+ "version": "1.0.0",
4
+ "description": "TypeScript library for BookingLab and JRNI APIs with React hooks support",
5
+ "main": "./dist/index.cjs",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "require": {
15
+ "types": "./dist/index.d.cts",
16
+ "default": "./dist/index.cjs"
17
+ }
18
+ }
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "README.md",
23
+ "LICENSE"
24
+ ],
25
+ "keywords": [
26
+ "jrni",
27
+ "bookinglab",
28
+ "api-client",
29
+ "react",
30
+ "typescript",
31
+ "booking",
32
+ "scheduling"
33
+ ],
34
+ "author": "BookingLab",
35
+ "license": "MIT",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "https://github.com/bookinglab/booking-journey-api"
39
+ },
40
+ "bugs": {
41
+ "url": "https://github.com/bookinglab/booking-journey-api/issues"
42
+ },
43
+ "homepage": "https://github.com/bookinglab/booking-journey-api#readme",
44
+ "scripts": {
45
+ "build": "tsup",
46
+ "dev": "tsup --watch",
47
+ "typecheck": "tsc --noEmit",
48
+ "prepublishOnly": "npm run build"
49
+ },
50
+ "peerDependencies": {
51
+ "@tanstack/react-query": ">=5.0.0",
52
+ "react": ">=17.0.0"
53
+ },
54
+ "peerDependenciesMeta": {
55
+ "@tanstack/react-query": {
56
+ "optional": true
57
+ },
58
+ "react": {
59
+ "optional": true
60
+ }
61
+ },
62
+ "devDependencies": {
63
+ "@types/react": "^18.3.0",
64
+ "tsup": "^8.0.0",
65
+ "typescript": "^5.8.0"
66
+ }
67
+ }