@pikku/fetch 0.7.0 → 0.8.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/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # @pikku/fetch
2
2
 
3
+ ## 0.8.0
4
+
5
+ - Updating to match remaining packages
6
+
3
7
  ## 0.7.0
4
8
 
5
9
  - Updating to match remaining packages
@@ -0,0 +1 @@
1
+ {"root":["../src/core-pikku-fetch.ts","../src/index.ts","../src/pikku-fetch.ts","../src/transform-date.ts"],"version":"5.8.3"}
@@ -0,0 +1 @@
1
+ {"root":["../src/core-pikku-fetch.ts","../src/index.ts","../src/pikku-fetch.ts","../src/transform-date.ts"],"version":"5.8.3"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/fetch",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "author": "yasser.fadl@gmail.com",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -0,0 +1,200 @@
1
+ import { transformDates } from './transform-date.js'
2
+ import { corePikkuFetch } from './pikku-fetch.js'
3
+
4
+ type AuthHeaders = {
5
+ jwt?: string
6
+ apiKey?: string
7
+ }
8
+
9
+ export type HTTPMethod = 'GET' | 'POST' | 'PATCH' | 'DELETE' | 'HEAD' | 'PUT'
10
+
11
+ /**
12
+ * Options for configuring the `CorePikkuFetch` utility.
13
+ *
14
+ * @typedef {Object} CorePikkuFetchOptions
15
+ * @property {boolean} [transformDate] - Whether to transform date-like strings in the response to `Date` objects.
16
+ * @property {string} [serverUrl] - The base server URL for requests.
17
+ * @property {AuthHeaders} [authHeaders] - Authorization headers, including JWT or API key.
18
+ * @property {RequestInit['cache']} [cache] - The cache mode for the request.
19
+ * @property {RequestInit['credentials']} [credentials] - The credentials mode for the request.
20
+ * @property {RequestInit['mode']} [mode] - The mode for the request.
21
+ */
22
+ export type CorePikkuFetchOptions = {
23
+ transformDate?: boolean
24
+ serverUrl?: string
25
+ authHeaders?: AuthHeaders
26
+ } & Pick<RequestInit, 'cache' | 'credentials' | 'mode'>
27
+
28
+ /**
29
+ * The `CorePikkuFetch` class provides a utility for making HTTP requests, including handling authorization,
30
+ * transforming dates in responses, and managing server URLs. This class is designed to simplify API interactions
31
+ * with configurable options and support for JWT and API key-based authentication.
32
+ */
33
+ export class CorePikkuFetch {
34
+ private authHeaders: AuthHeaders = {}
35
+
36
+ /**
37
+ * Constructs a new instance of the `CorePikkuFetch` class.
38
+ *
39
+ * @param {CorePikkuFetchOptions} options - Optional configuration for the fetch utility.
40
+ */
41
+ constructor(private options: CorePikkuFetchOptions = {}) {
42
+ this.authHeaders = options.authHeaders || {}
43
+ }
44
+
45
+ /**
46
+ * Generates the headers for the request, including authorization headers if set.
47
+ *
48
+ * @returns {Record<string, string>} - The headers for the request.
49
+ */
50
+ private getHeaders(): Record<string, string> {
51
+ const headers: Record<string, string> = {
52
+ 'Content-Type': 'application/json',
53
+ }
54
+ if (this.authHeaders.jwt) {
55
+ headers.Authorization = `Bearer ${this.authHeaders.jwt}`
56
+ } else if (this.authHeaders.apiKey) {
57
+ headers['X-API-KEY'] = this.authHeaders.apiKey
58
+ }
59
+ return headers
60
+ }
61
+
62
+ /**
63
+ * Sets the server URL for subsequent requests.
64
+ *
65
+ * @param {string} serverUrl - The server URL to be set.
66
+ */
67
+ public async setServerUrl(serverUrl: string): Promise<void> {
68
+ if (serverUrl.endsWith('/')) {
69
+ console.warn('Server URL should not end with a slash, removing.')
70
+ serverUrl = serverUrl.slice(0, -1)
71
+ }
72
+ this.options.serverUrl = serverUrl
73
+ }
74
+
75
+ /**
76
+ * Sets the JWT for authorization.
77
+ *
78
+ * @param {string} jwt - The JWT to be used for authorization.
79
+ */
80
+ public setAuthorizationJWT(jwt: string | null): void {
81
+ if (jwt) {
82
+ this.authHeaders.jwt = jwt
83
+ } else {
84
+ delete this.authHeaders.jwt
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Sets the API key for authorization.
90
+ *
91
+ * @param {string} [apiKey] - The API key to be used for authorization.
92
+ */
93
+ public setAPIKey(apiKey: string | null): void {
94
+ if (apiKey) {
95
+ this.authHeaders.apiKey = apiKey
96
+ } else {
97
+ delete this.authHeaders.apiKey
98
+ }
99
+ }
100
+
101
+ public async post(uri: string, data: any, options?: RequestInit) {
102
+ return await this.api(uri, 'POST', data, options)
103
+ }
104
+
105
+ public async get(uri: string, data: any, options?: RequestInit) {
106
+ return await this.api(uri, 'GET', data, options)
107
+ }
108
+
109
+ public async patch(uri: string, data: any, options?: RequestInit) {
110
+ return await this.api(uri, 'PATCH', data, options)
111
+ }
112
+
113
+ public async head(uri: string, data: any, options?: RequestInit) {
114
+ return await this.api(uri, 'HEAD', data, options)
115
+ }
116
+
117
+ /**
118
+ * Makes an API request with the specified URI, method, and data, and optionally transforms dates in the response.
119
+ *
120
+ * @param {string} uri - The endpoint URI for the request.
121
+ * @param {HTTPMethod} method - The HTTP method for the request.
122
+ * @param {any} data - The data to be sent with the request.
123
+ * @param {RequestInit} [options] - Additional options for the request.
124
+ * @returns {Promise<any>} - A promise that resolves to the response data.
125
+ * @throws {Response} - Throws the response if the status code is greater than 400.
126
+ */
127
+ public async api(
128
+ uri: string,
129
+ method: HTTPMethod,
130
+ data: any,
131
+ options?: RequestInit
132
+ ) {
133
+ const response = await this.fetch(uri, method, data, options)
134
+ if (response.status >= 400) {
135
+ throw response
136
+ }
137
+ try {
138
+ const result = await response.json()
139
+ return this.transformDates(result)
140
+ } catch {
141
+ // TODO: If it doesn't return anything..
142
+ return
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Makes a raw fetch request with the specified URI, method, and data.
148
+ *
149
+ * @param {string} uri - The endpoint URI for the request.
150
+ * @param {HTTPMethod} method - The HTTP method for the request.
151
+ * @param {any} data - The data to be sent with the request.
152
+ * @param {RequestInit} [options] - Additional options for the request.
153
+ * @returns {Promise<Response>} - A promise that resolves to the fetch response.
154
+ */
155
+ public async fetch(
156
+ uri: string,
157
+ method: HTTPMethod,
158
+ data: any,
159
+ options?: RequestInit
160
+ ) {
161
+ this.verifyServerUrlSet()
162
+ if (uri.startsWith('/')) {
163
+ uri = `${this.options.serverUrl}${uri}`
164
+ } else {
165
+ uri = `${this.options.serverUrl}/${uri}`
166
+ }
167
+
168
+ return await corePikkuFetch(uri, data, {
169
+ ...options,
170
+ method,
171
+ mode: this.options.mode,
172
+ credentials: this.options.credentials,
173
+ headers: { ...this.getHeaders(), ...options?.headers },
174
+ })
175
+ }
176
+
177
+ /**
178
+ * Verifies that the server URL is set before making a request.
179
+ *
180
+ * @throws {Error} - Throws an error if the server URL is not set.
181
+ */
182
+ private verifyServerUrlSet() {
183
+ if (!this.options.serverUrl) {
184
+ throw new Error('Server url is not set')
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Transforms date-like strings in the response data into `Date` objects if the `transformDate` option is set.
190
+ *
191
+ * @param {any} data - The data to transform.
192
+ * @returns {any} - The transformed data.
193
+ */
194
+ private transformDates(data: any): any {
195
+ if (!this.options.transformDate) {
196
+ return data
197
+ }
198
+ return transformDates(data)
199
+ }
200
+ }
package/src/index.ts ADDED
@@ -0,0 +1,15 @@
1
+ /**
2
+ * This module provides a wrapper around the Fetch API that integrates with the Pikku framework.
3
+ * It includes utilities for making HTTP requests, such as options for authorization, server URL management,
4
+ * and transforming dates in responses, while ensuring requests are validated against Pikku routes.
5
+ * The module exports the `CorePikkuFetch` class, as well as other supporting types and functions.
6
+ *
7
+ * @module @pikku/fetch
8
+ */
9
+
10
+ export {
11
+ CorePikkuFetch,
12
+ CorePikkuFetchOptions,
13
+ HTTPMethod,
14
+ } from './core-pikku-fetch.js'
15
+ export { corePikkuFetch } from './pikku-fetch.js'
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The `corePikkuFetch` function is a utility for making HTTP requests with dynamic URI and data handling.
3
+ * It can automatically replace URI parameters, append query strings for GET requests, and set the request body for POST, PATCH, or PUT requests.
4
+ *
5
+ * @param {string} uri - The endpoint URI for the request. URI parameters can be specified using `:param` syntax.
6
+ * @param {any} data - The data to be included in the request, either as query parameters or as the request body.
7
+ * @param {Omit<RequestInit, 'body'>} [options] - Optional configuration options for the fetch request, excluding the body.
8
+ * @returns {Promise<Response>} - A promise that resolves to the response of the fetch request.
9
+ */
10
+ export const corePikkuFetch = async (
11
+ uri: string,
12
+ data: any,
13
+ options?: Omit<RequestInit, 'body'>
14
+ ) => {
15
+ const method = options?.method?.toUpperCase() || 'GET'
16
+ let body: any | undefined
17
+
18
+ if (data) {
19
+ data = JSON.parse(JSON.stringify(data))
20
+
21
+ const keys = Object.keys(data)
22
+ for (const key of keys) {
23
+ if (uri.includes(`:${key}`)) {
24
+ uri = uri.replace(`:${key}`, data[key])
25
+ delete data[key]
26
+ }
27
+ }
28
+ if (method === 'POST' || method === 'PATCH' || method === 'PUT') {
29
+ body = data
30
+ } else {
31
+ const queryString = new URLSearchParams(JSON.parse(JSON.stringify(data)))
32
+ uri = `${uri}?${queryString}`
33
+ }
34
+ }
35
+
36
+ return await fetch(uri, {
37
+ method: method.toUpperCase(),
38
+ ...options,
39
+ body: body ? JSON.stringify(body) : undefined,
40
+ })
41
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The `transformDates` function recursively traverses an object or array and converts any date-like strings
3
+ * into JavaScript `Date` objects. This helps in ensuring that date fields are properly handled as `Date` instances
4
+ * rather than strings.
5
+ *
6
+ * @private
7
+ * @param {any} data - The input data that may contain date strings. It can be an object, array, or primitive value.
8
+ * @returns {any} - The transformed data with date strings converted to `Date` objects.
9
+ */
10
+ export const transformDates = (data: any) => {
11
+ if (data === null) return null
12
+ if (Array.isArray(data)) return data.map(transformDates.bind(this))
13
+ if (typeof data === 'object') {
14
+ return Object.entries(data).reduce((result, [key, value]) => {
15
+ result[key] = transformDates(value)
16
+ return result
17
+ }, {} as any)
18
+ }
19
+ if (
20
+ typeof data === 'string' &&
21
+ /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}\.\d{3}Z?)?/.test(data)
22
+ ) {
23
+ return new Date(data)
24
+ }
25
+ return data
26
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": "./src",
5
+ "module": "commonjs",
6
+ "outDir": "dist/cjs",
7
+ "target": "es2015",
8
+ "moduleResolution": "node"
9
+ },
10
+ "exclude": ["**/*.test.ts", "node_modules", "dist"]
11
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "extends": "../tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": "./src",
5
+ "module": "Node18",
6
+ "outDir": "dist/esm",
7
+ "target": "esnext",
8
+ "declaration": true,
9
+ "resolveJsonModule": true,
10
+ "lib": ["DOM"]
11
+ },
12
+ "include": ["src/*.ts"],
13
+ "exclude": ["**/*.test.ts", "node_modules"]
14
+ }