@kalutskii/foundation 0.6.5 → 0.6.7
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/README.md +120 -19
- package/dist/index.d.ts +136 -106
- package/dist/index.js +49 -51
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -5,11 +5,14 @@ Designed for use in Bun/Node.js and Cloudflare Workers environments.
|
|
|
5
5
|
|
|
6
6
|
## What this package provides
|
|
7
7
|
|
|
8
|
-
- Typed API response contracts.
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
8
|
+
- Typed API response contracts with success/failure factories and safe resolvers.
|
|
9
|
+
- Hono helpers: typed JSON responses, global error handler, request logging middleware.
|
|
10
|
+
- Execution utilities: safe async execution and execution time measurement.
|
|
11
|
+
- Datetime utilities: timezone-aware formatting helpers.
|
|
12
|
+
- Logging utility: unified colored log interface.
|
|
13
|
+
- Random string generation utility.
|
|
14
|
+
- JWT service with optional Zod schema validation.
|
|
15
|
+
- Zod helpers for query param coercion, pagination schemas, and type utilities.
|
|
13
16
|
|
|
14
17
|
## Installation
|
|
15
18
|
|
|
@@ -32,8 +35,8 @@ Contract shape:
|
|
|
32
35
|
|
|
33
36
|
Status code groups are exported as constants and used by types:
|
|
34
37
|
|
|
35
|
-
- `SUCCESS_STATUS_CODES`
|
|
36
|
-
- `EXCEPTION_STATUS_CODES`
|
|
38
|
+
- `SUCCESS_STATUS_CODES` — `[200, 201, 202, 307]`
|
|
39
|
+
- `EXCEPTION_STATUS_CODES` — `[400, 401, 403, 404, 405, 409, 500]`
|
|
37
40
|
|
|
38
41
|
## Usage
|
|
39
42
|
|
|
@@ -87,34 +90,132 @@ const app = new Hono();
|
|
|
87
90
|
app.get('/health', (c) => {
|
|
88
91
|
return respond(c, {
|
|
89
92
|
status: 200,
|
|
90
|
-
data: { ok: true
|
|
93
|
+
data: { ok: true },
|
|
91
94
|
});
|
|
92
95
|
});
|
|
93
96
|
```
|
|
94
97
|
|
|
95
|
-
`respond`
|
|
98
|
+
`respond` wraps `c.json` with a typed success payload and includes `APIError` in the route's output union.
|
|
96
99
|
|
|
97
|
-
### 4.
|
|
100
|
+
### 4. Wire up Hono error handler and logging middleware
|
|
98
101
|
|
|
99
102
|
```ts
|
|
100
|
-
import {
|
|
103
|
+
import { honoLoggingHandler, onHandlerError } from '@kalutskii/foundation';
|
|
104
|
+
import { Hono } from 'hono';
|
|
105
|
+
|
|
106
|
+
const app = new Hono();
|
|
107
|
+
|
|
108
|
+
app.use('*', honoLoggingHandler);
|
|
109
|
+
app.onError(onHandlerError);
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
`onHandlerError` catches all thrown errors, maps `HTTPException` (< 500) to their status codes, and returns a generic 500 with a unique error ID for unexpected errors.
|
|
113
|
+
|
|
114
|
+
`honoLoggingHandler` logs each request with method, status, duration, path, and query params.
|
|
115
|
+
Example: `[12:12:12 (+4 UTC)] hono | POST 200 123ms /api/v1/users (search=term)`
|
|
116
|
+
|
|
117
|
+
### 5. Execute functions safely and measure performance
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
import { measureExecutionTime, safeExecute } from '@kalutskii/foundation';
|
|
121
|
+
|
|
122
|
+
const result = await safeExecute(
|
|
123
|
+
() => fetchData(),
|
|
124
|
+
(error) => console.error(error)
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const { result: data, executionTime } = await measureExecutionTime(() => fetchData());
|
|
128
|
+
console.log(`Done in ${executionTime}ms`);
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### 6. Parse query params with `asQuery`
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
import { asQuery } from '@kalutskii/foundation';
|
|
135
|
+
import { z } from 'zod';
|
|
136
|
+
|
|
137
|
+
const schema = z.object({
|
|
138
|
+
page: asQuery(z.number().int().positive()),
|
|
139
|
+
isActive: asQuery(z.boolean()),
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
schema.parse({ page: '2', isActive: 'true' }); // { page: 2, isActive: true }
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`asQuery` preprocesses string values from query parameters — coercing `'true'`/`'false'` to booleans and numeric strings to numbers — before passing them to the Zod schema for validation.
|
|
146
|
+
|
|
147
|
+
### 7. Use pagination schema
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
import { refinePagination, zodPaginationSchema, zodPaginationShape } from '@kalutskii/foundation';
|
|
151
|
+
import { z } from 'zod';
|
|
152
|
+
|
|
153
|
+
// Use as a standalone schema
|
|
154
|
+
const options = zodPaginationSchema.parse({ offset: '0', limit: '20' });
|
|
155
|
+
// { offset: 0, limit: 20 }
|
|
156
|
+
|
|
157
|
+
// Spread shape into a larger query schema
|
|
158
|
+
const querySchema = z.object({
|
|
159
|
+
search: z.string().optional(),
|
|
160
|
+
...zodPaginationShape,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Strip undefined pagination values before passing to a query builder (e.g. Drizzle)
|
|
164
|
+
await db.query.users.findMany({
|
|
165
|
+
...refinePagination(options),
|
|
166
|
+
});
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### 8. Work with JWT using `ZodJWTService`
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
import { ZodJWTService } from '@kalutskii/foundation';
|
|
101
173
|
import { z } from 'zod';
|
|
102
174
|
|
|
103
|
-
const
|
|
104
|
-
const
|
|
175
|
+
const payloadSchema = z.object({ userId: z.string() });
|
|
176
|
+
const jwt = new ZodJWTService(payloadSchema, { defaultExpirationSeconds: 3600 });
|
|
177
|
+
|
|
178
|
+
const token = await jwt.sign({ userId: '42' }, 'secret');
|
|
179
|
+
const payload = await jwt.verifyOrThrow(token, 'secret');
|
|
180
|
+
// payload: { userId: '42', exp: ... }
|
|
181
|
+
|
|
182
|
+
// decode without throwing (returns null on invalid token or schema mismatch)
|
|
183
|
+
const decoded = await jwt.decode(token);
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### 9. Datetime helpers
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
import { formatTime, getFormattedDate, getFormattedTime, getZonedTime } from '@kalutskii/foundation';
|
|
190
|
+
|
|
191
|
+
getFormattedTime({ tz: 'Europe/Moscow' }); // '15:30:00 (+3 UTC)'
|
|
192
|
+
getFormattedDate({ tz: 'Europe/Moscow' }); // '22.06.2026 15:30:00 (+3 UTC)'
|
|
193
|
+
getFormattedDate({ tz: 'Europe/Moscow', withTime: false }); // '22.06.2026'
|
|
194
|
+
formatTime(new Date(), { tz: 'Europe/Moscow' }); // '15:30:00, 22 июня 2026 (+3 UTC)'
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### 10. Logging
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
import { log } from '@kalutskii/foundation';
|
|
105
201
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
202
|
+
log.info('Server started', 'app');
|
|
203
|
+
log.warn('Deprecated call', 'auth');
|
|
204
|
+
log.error('Something failed', 'db', error.stack);
|
|
205
|
+
// [15:30:00 (+3 UTC)] app | Server started
|
|
109
206
|
```
|
|
110
207
|
|
|
111
208
|
## Exports
|
|
112
209
|
|
|
113
210
|
The package exports all public APIs from a single entrypoint:
|
|
114
211
|
|
|
115
|
-
- HTTP constants, schemas
|
|
116
|
-
- Hono `respond`
|
|
117
|
-
-
|
|
212
|
+
- **HTTP**: `success`, `failure`, `fetchSafely`, `fetchAndThrow`, constants, schemas.
|
|
213
|
+
- **Hono**: `respond`, `onHandlerError`, `honoLoggingHandler`.
|
|
214
|
+
- **Utilities**: `safeExecute`, `measureExecutionTime`, `generateRandomString`, `log`, `getColoredHTTPStatus`, datetime helpers.
|
|
215
|
+
- **Zod JWT**: `ZodJWTService`.
|
|
216
|
+
- **Zod Pagination**: `zodPaginationSchema`, `zodPaginationShape`, `refinePagination`, `ZodPaginationOptions`.
|
|
217
|
+
- **Zod Validation**: `asQuery`, `AsQuery`, `AtLeastOne`.
|
|
218
|
+
- **Type Utilities**: `Simplify`.
|
|
118
219
|
|
|
119
220
|
## Development
|
|
120
221
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,24 @@
|
|
|
1
|
+
import { SQL } from 'drizzle-orm';
|
|
1
2
|
import { ErrorHandler, MiddlewareHandler, Context, TypedResponse } from 'hono';
|
|
2
|
-
import z from 'zod';
|
|
3
3
|
import { Locale } from 'date-fns';
|
|
4
4
|
import { SymmetricAlgorithm } from 'hono/utils/jwt/jwa';
|
|
5
|
+
import z from 'zod';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Builds a Drizzle SQL `where` condition from a plain object.
|
|
9
|
+
*
|
|
10
|
+
* Maps each `where` entry to `eq(table[key], value)` and combines them with `and`.
|
|
11
|
+
* Assumes that `where` was already validated and contains only existing table fields.
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* await db
|
|
15
|
+
* .update(usersTable)
|
|
16
|
+
* .set(values)
|
|
17
|
+
* .where(sqlWhere(usersTable, { id: 1 }))
|
|
18
|
+
* .returning();
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
declare function sqlWhere(table: unknown, where: Record<string, unknown>): SQL | undefined;
|
|
5
22
|
|
|
6
23
|
/**
|
|
7
24
|
* Global error handler for Hono framework. Catches all exceptions thrown in route handlers and middlewares.
|
|
@@ -33,7 +50,10 @@ type APIError = {
|
|
|
33
50
|
type APIContractResult<TData = void> = APISuccess<TData> | APIError;
|
|
34
51
|
type APIContractData<TResult extends APIContractResult<unknown>> = TResult extends APISuccess<infer TData> ? TData : never;
|
|
35
52
|
type APIContractError<TResult extends APIContractResult<unknown>> = Extract<TResult, APIError>;
|
|
36
|
-
/**
|
|
53
|
+
/**
|
|
54
|
+
* Discriminated union result of a fetch operation.
|
|
55
|
+
* If `error` is set, `data` is null and vice versa.
|
|
56
|
+
*/
|
|
37
57
|
type FetchResult<T> = {
|
|
38
58
|
error: null;
|
|
39
59
|
data: T;
|
|
@@ -42,15 +62,36 @@ type FetchResult<T> = {
|
|
|
42
62
|
data: null;
|
|
43
63
|
};
|
|
44
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Utility type that simplifies a given type T by flattening its structure.
|
|
67
|
+
* This is particularly useful for improving the readability of complex types.
|
|
68
|
+
*/
|
|
69
|
+
type Simplify<T> = {
|
|
70
|
+
[K in keyof T]: T[K];
|
|
71
|
+
} & {};
|
|
72
|
+
|
|
45
73
|
type QueryPrimitive = string | number | boolean | bigint | Date | null | undefined;
|
|
46
74
|
/**
|
|
47
75
|
* Type utility that recursively transforms all fields to string, as well as handling arrays and objects.
|
|
48
76
|
* This is useful for serializing complex data structures into query parameters, which must be strings.
|
|
49
|
-
* Example usage: `AsQuery<{ id: number; name: string; tags: string[] }>` = `{ id: string; name: string; tags: string[] }`
|
|
50
77
|
*/
|
|
51
78
|
type AsQuery<T> = T extends QueryPrimitive ? string : T extends readonly (infer Item)[] ? AsQuery<Item>[] : T extends object ? {
|
|
52
79
|
[Key in keyof T]: AsQuery<T[Key]>;
|
|
53
80
|
} : string;
|
|
81
|
+
/**
|
|
82
|
+
* Utility type that ensures at least one property from the specified
|
|
83
|
+
* keys of a given type T is required, while the rest remain optional.
|
|
84
|
+
*
|
|
85
|
+
* This is useful for scenarios where you want to enforce that at least one
|
|
86
|
+
* of several optional (nullable) properties must be provided in an object.
|
|
87
|
+
*
|
|
88
|
+
* ```ts
|
|
89
|
+
* type Example = AtLeastOne<{ a?: string; b?: number; c?: boolean }>;
|
|
90
|
+
* // Valid: { a: "hello" }, { b: 42 }, { c: true }, { a: "hello", b: 42 }
|
|
91
|
+
* // Invalid: {}, { a: undefined, b: undefined, c: undefined }
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
type AtLeastOne<T, Keys extends keyof T = keyof T> = Keys extends keyof T ? Simplify<Required<Pick<T, Keys>> & Partial<Omit<T, Keys>>> : never;
|
|
54
95
|
|
|
55
96
|
/**
|
|
56
97
|
* Wraps c.json with a typed success payload / (or void data) & possible APIError response.
|
|
@@ -80,86 +121,65 @@ declare function failure({ status, error }: {
|
|
|
80
121
|
|
|
81
122
|
/**
|
|
82
123
|
* Resolves an APIContractResult fetcher into a FetchResult discriminated union.
|
|
83
|
-
* Does not throw errors, instead returning them
|
|
124
|
+
* Does not throw errors, instead returning them as property of the FetchResult.
|
|
84
125
|
* Example usage: const result = await fetchSafely(() => api.fetchUser(userId));
|
|
85
126
|
*/
|
|
86
127
|
declare function fetchSafely<TResult extends APIContractResult<unknown>>(fetcher: () => Promise<TResult>): Promise<FetchResult<APIContractData<TResult>>>;
|
|
87
128
|
/**
|
|
88
|
-
* Resolves an APIContractResult
|
|
129
|
+
* Resolves an APIContractResult, throwing an error if the result is an APIError.
|
|
89
130
|
* Example usage: const data = await fetchAndThrow(() => api.fetchUser(userId));
|
|
90
131
|
*/
|
|
91
132
|
declare function fetchAndThrow<TResult extends APIContractResult<unknown>>(fetcher: () => Promise<TResult>): Promise<APIContractData<TResult>>;
|
|
92
133
|
|
|
93
134
|
/**
|
|
94
|
-
*
|
|
95
|
-
* - `offset`: An optional non-negative integer representing the starting point for pagination.
|
|
96
|
-
* - `limit`: An optional positive integer representing the maximum number of items to return.
|
|
97
|
-
*
|
|
98
|
-
* This schema can be used to inject in other Zod schemas for validating pagination options:
|
|
99
|
-
*
|
|
100
|
-
* ```ts
|
|
101
|
-
* const mySchema = paginationSchema.extend({
|
|
102
|
-
* // other fields here
|
|
103
|
-
* });
|
|
104
|
-
* ```
|
|
105
|
-
*/
|
|
106
|
-
declare const paginationSchema: z.ZodObject<{
|
|
107
|
-
offset: z.ZodOptional<z.ZodPreprocess<z.ZodNumber>>;
|
|
108
|
-
limit: z.ZodOptional<z.ZodPreprocess<z.ZodNumber>>;
|
|
109
|
-
}, z.core.$strip>;
|
|
110
|
-
/**
|
|
111
|
-
* A TypeScript type representing the pagination options validated by the `paginationSchema`.
|
|
112
|
-
* Read documentation for `paginationSchema` for more details on the structure and usage of this type.
|
|
113
|
-
*/
|
|
114
|
-
type PaginationOptions = z.infer<typeof paginationSchema>;
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* A utility function that extracts pagination options from the provided input.
|
|
118
|
-
* It returns an object containing the `offset` and `limit` properties if they are defined.
|
|
119
|
-
* If the input is undefined or does not contain these properties, it returns an empty object.
|
|
135
|
+
* Returns the current date and time shifted to the specified timezone.
|
|
120
136
|
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
* ...refinePagination(options),
|
|
124
|
-
* });
|
|
125
|
-
* ```
|
|
126
|
-
*/
|
|
127
|
-
declare const refinePagination: (options?: PaginationOptions) => {
|
|
128
|
-
offset?: number | undefined;
|
|
129
|
-
limit?: number | undefined;
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Returns the current time in the specified timezone.
|
|
134
|
-
* Example usage: `getZonedTime({ tz: 'America/New_York' }) = new Date('2026-03-15T12:00:00Z')`
|
|
137
|
+
* This is useful when the application needs to display "now" from another
|
|
138
|
+
* region's point of view instead of relying on the server's local timezone.
|
|
135
139
|
*/
|
|
136
140
|
declare const getZonedTime: ({ tz }?: {
|
|
137
141
|
tz?: string;
|
|
138
142
|
}) => Date;
|
|
139
143
|
/**
|
|
140
|
-
* Returns the
|
|
141
|
-
*
|
|
144
|
+
* Returns the UTC offset for the specified timezone at the given date.
|
|
145
|
+
*
|
|
146
|
+
* The offset is formatted for display next to dates and times, for example
|
|
147
|
+
* `(+4 UTC)`, `(0 UTC)`, or `(-5 UTC)`. The date is required because timezone
|
|
148
|
+
* offsets may change depending on daylight saving time or historical rules.
|
|
142
149
|
*/
|
|
143
150
|
declare const getUTCOffset: (date: Date, tz: string) => string;
|
|
144
151
|
/**
|
|
145
|
-
* Returns the current time in the specified timezone
|
|
146
|
-
*
|
|
152
|
+
* Returns the current time formatted in the specified timezone.
|
|
153
|
+
*
|
|
154
|
+
* The result includes both the local time and its UTC offset, making it suitable
|
|
155
|
+
* for UI labels, logs, bot messages, and other places where the user should see
|
|
156
|
+
* not only the time itself, but also the timezone context behind that value.
|
|
157
|
+
*
|
|
158
|
+
* Format: `HH:mm:ss (+X UTC)`.
|
|
147
159
|
*/
|
|
148
160
|
declare const getFormattedTime: ({ tz }?: {
|
|
149
161
|
tz?: string;
|
|
150
162
|
}) => string;
|
|
151
163
|
/**
|
|
152
|
-
* Returns the current date in the specified timezone
|
|
153
|
-
*
|
|
154
|
-
*
|
|
164
|
+
* Returns the current date formatted in the specified timezone.
|
|
165
|
+
*
|
|
166
|
+
* By default, the result includes date, time, and UTC offset. This is useful for
|
|
167
|
+
* complete timestamps displayed in UI, bot messages, reports, or diagnostics.
|
|
168
|
+
*
|
|
169
|
+
* Format with time: `dd.MM.yyyy HH:mm:ss (+X UTC)`.
|
|
170
|
+
* Format without time: `dd.MM.yyyy`.
|
|
155
171
|
*/
|
|
156
172
|
declare const getFormattedDate: ({ tz, withTime, }?: {
|
|
157
173
|
tz?: string;
|
|
158
174
|
withTime?: boolean;
|
|
159
175
|
}) => string;
|
|
160
176
|
/**
|
|
161
|
-
* Formats the
|
|
162
|
-
*
|
|
177
|
+
* Formats the provided date and time in the specified timezone.
|
|
178
|
+
*
|
|
179
|
+
* Unlike helpers that always use the current moment, this function accepts an
|
|
180
|
+
* explicit Date value and formats that exact point in time for another timezone.
|
|
181
|
+
*
|
|
182
|
+
* Format: `HH:mm:ss, d MMMM yyyy (+X UTC)`.
|
|
163
183
|
*/
|
|
164
184
|
declare const formatTime: (time: Date, { locale, tz }?: {
|
|
165
185
|
locale?: Locale;
|
|
@@ -211,55 +231,6 @@ declare const log: {
|
|
|
211
231
|
error: (message: string, service?: string, stack?: string) => void;
|
|
212
232
|
};
|
|
213
233
|
|
|
214
|
-
/**
|
|
215
|
-
* Utility type that simplifies a given type T by flattening its structure.
|
|
216
|
-
* This is particularly useful for improving the readability of complex types.
|
|
217
|
-
*/
|
|
218
|
-
type Simplify<T> = {
|
|
219
|
-
[K in keyof T]: T[K];
|
|
220
|
-
} & {};
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Transforms a string to a number (when passing query parameters).
|
|
224
|
-
* Example usage: `asQueryNumber(z.number())('123') = 123`
|
|
225
|
-
*/
|
|
226
|
-
declare const asQueryNumber: <T extends z.ZodTypeAny>(schema: T) => z.ZodPreprocess<T>;
|
|
227
|
-
/**
|
|
228
|
-
* Transforms various string representations of boolean values into actual booleans.
|
|
229
|
-
* See POSITIVE_VALUES and NEGATIVE_VALUES arrays below for supported inputs.
|
|
230
|
-
*/
|
|
231
|
-
declare const asQueryBoolean: <T extends z.ZodTypeAny>(schema: T) => z.ZodPreprocess<T>;
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* A utility type that represents a value that can be of type T or null.
|
|
235
|
-
* Commonly used for optional fields in data models or function return types.
|
|
236
|
-
*
|
|
237
|
-
* ```ts
|
|
238
|
-
* export type ObjectSelect = XOR<
|
|
239
|
-
* Pick<Object, 'id'>,
|
|
240
|
-
* Pick<Object, 'anotherUniqueField'>
|
|
241
|
-
* >;
|
|
242
|
-
*/
|
|
243
|
-
type XOR<T, U> = Simplify<T & {
|
|
244
|
-
[K in keyof U]?: never;
|
|
245
|
-
}> | Simplify<U & {
|
|
246
|
-
[K in keyof T]?: never;
|
|
247
|
-
}>;
|
|
248
|
-
/**
|
|
249
|
-
* Utility type that ensures at least one property from the specified
|
|
250
|
-
* keys of a given type T is required, while the rest remain optional.
|
|
251
|
-
*
|
|
252
|
-
* This is useful for scenarios where you want to enforce that at least one
|
|
253
|
-
* of several optional (nullable) properties must be provided in an object.
|
|
254
|
-
*
|
|
255
|
-
* ```ts
|
|
256
|
-
* type Example = AtLeastOne<{ a?: string; b?: number; c?: boolean }>;
|
|
257
|
-
* // Valid: { a: "hello" }, { b: 42 }, { c: true }, { a: "hello", b: 42 }
|
|
258
|
-
* // Invalid: {}, { a: undefined, b: undefined, c: undefined }
|
|
259
|
-
* ```
|
|
260
|
-
*/
|
|
261
|
-
type AtLeastOne<T, Keys extends keyof T = keyof T> = Keys extends keyof T ? Simplify<Required<Pick<T, Keys>> & Partial<Omit<T, Keys>>> : never;
|
|
262
|
-
|
|
263
234
|
/** Options for configuring the JWT service. */
|
|
264
235
|
type JWTServiceOptions = {
|
|
265
236
|
/** The algorithm to use for signing and verifying JWTs. Defaults to 'HS256'. */
|
|
@@ -272,9 +243,13 @@ type JWTSignOptions = {
|
|
|
272
243
|
/** The number of seconds until the JWT expires. */
|
|
273
244
|
expiresInSeconds?: number;
|
|
274
245
|
};
|
|
275
|
-
/**
|
|
246
|
+
/**
|
|
247
|
+
* Utility types for inferring payload types from Zod schemas.
|
|
248
|
+
*/
|
|
276
249
|
type Payload<T> = T extends z.ZodType ? z.infer<T> : T;
|
|
277
|
-
/**
|
|
250
|
+
/**
|
|
251
|
+
* Utility type to extract the payload schema type from a Zod schema.
|
|
252
|
+
* */
|
|
278
253
|
type PayloadSchema<T> = T extends z.ZodType ? T : never;
|
|
279
254
|
|
|
280
255
|
/**
|
|
@@ -308,4 +283,59 @@ declare class ZodJWTService<TPayloadOrSchema> {
|
|
|
308
283
|
verifyOrThrow(token: string, secret: string): Promise<Payload<TPayloadOrSchema>>;
|
|
309
284
|
}
|
|
310
285
|
|
|
311
|
-
|
|
286
|
+
declare const zodPaginationSchema: z.ZodObject<{
|
|
287
|
+
offset: z.ZodOptional<z.ZodPreprocess<z.ZodNumber>>;
|
|
288
|
+
limit: z.ZodOptional<z.ZodPreprocess<z.ZodNumber>>;
|
|
289
|
+
}, z.core.$strip>;
|
|
290
|
+
/**
|
|
291
|
+
* Reusable pagination fields for flat query schemas, allowing for
|
|
292
|
+
* consistent validation of pagination parameters across different endpoints.
|
|
293
|
+
*
|
|
294
|
+
* Spread into another `z.object(...)` when composing a larger query schema,
|
|
295
|
+
* or use `zodPaginationSchema.extend(...)` when building directly from the schema.
|
|
296
|
+
*/
|
|
297
|
+
declare const zodPaginationShape: {
|
|
298
|
+
offset: z.ZodOptional<z.ZodPreprocess<z.ZodNumber>>;
|
|
299
|
+
limit: z.ZodOptional<z.ZodPreprocess<z.ZodNumber>>;
|
|
300
|
+
};
|
|
301
|
+
/**
|
|
302
|
+
* TypeScript type representing the pagination options validated by the `zodPaginationSchema`.
|
|
303
|
+
* Read documentation for `zodPaginationSchema` for more details on the structure and usage of this type.
|
|
304
|
+
*/
|
|
305
|
+
type ZodPaginationOptions = z.infer<typeof zodPaginationSchema>;
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Extracts defined pagination options from a query object.
|
|
309
|
+
*
|
|
310
|
+
* This keeps query builder calls clean by omitting undefined values while
|
|
311
|
+
* preserving valid pagination values like `0`, useful when spreading pagination
|
|
312
|
+
* options into APIs that expect only explicitly provided fields (e.g., Drizzle).
|
|
313
|
+
*
|
|
314
|
+
* ```ts
|
|
315
|
+
* await db.query.someTable.findMany({
|
|
316
|
+
* ...refinePagination(options),
|
|
317
|
+
* });
|
|
318
|
+
* ```
|
|
319
|
+
*/
|
|
320
|
+
declare const refinePagination: (options?: ZodPaginationOptions) => ZodPaginationOptions;
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Query parameters always arrive as strings, even when they semantically represent numbers
|
|
324
|
+
* or booleans, so we preprocess them to convert to the appropriate types before validation.
|
|
325
|
+
*
|
|
326
|
+
* ```ts
|
|
327
|
+
* const schema = z.object({
|
|
328
|
+
* page: z.number().int().positive(),
|
|
329
|
+
* isActive: z.boolean(),
|
|
330
|
+
* });
|
|
331
|
+
*
|
|
332
|
+
* const queryParams = { page: '2', isActive: 'true' };
|
|
333
|
+
* const result = schema.parse(queryParams); // { page: 2, isActive: true }
|
|
334
|
+
* ```
|
|
335
|
+
*
|
|
336
|
+
* @param schema - The Zod schema to validate the query parameters against.
|
|
337
|
+
* @returns A new Zod schema that preprocesses query parameter values before validation.
|
|
338
|
+
*/
|
|
339
|
+
declare const asQuery: <T extends z.ZodTypeAny>(schema: T) => z.ZodPreprocess<T>;
|
|
340
|
+
|
|
341
|
+
export { type APIContractData, type APIContractError, type APIContractResult, type APIError, type APISuccess, type AsQuery, type AtLeastOne, EXCEPTION_STATUS_CODES, type ExceptionStatusCode, type FetchResult, type JWTServiceOptions, type JWTSignOptions, type MeasuredExecution, type Payload, type PayloadSchema, SUCCESS_STATUS_CODES, type Simplify, type SuccessStatusCode, ZodJWTService, type ZodPaginationOptions, asQuery, failure, fetchAndThrow, fetchSafely, formatTime, generateRandomString, getColoredHTTPStatus, getFormattedDate, getFormattedTime, getUTCOffset, getZonedTime, honoLoggingHandler, log, measureExecutionTime, onHandlerError, refinePagination, respond, safeExecute, sqlWhere, success, zodPaginationSchema, zodPaginationShape };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
// src/drizzle/drizzle.refiners.ts
|
|
2
|
+
import { and, eq } from "drizzle-orm";
|
|
3
|
+
function sqlWhere(table, where) {
|
|
4
|
+
return and(...Object.entries(where).map(([key, value]) => eq(table[key], value)));
|
|
5
|
+
}
|
|
6
|
+
|
|
1
7
|
// src/hono/hono.execution.ts
|
|
2
8
|
import { HTTPException } from "hono/http-exception";
|
|
3
9
|
import { red as red2 } from "kleur/colors";
|
|
@@ -130,53 +136,6 @@ async function fetchAndThrow(fetcher) {
|
|
|
130
136
|
return response.data;
|
|
131
137
|
}
|
|
132
138
|
|
|
133
|
-
// src/pagination/pagination.schemas.ts
|
|
134
|
-
import z2 from "zod";
|
|
135
|
-
|
|
136
|
-
// src/validation/validation.refiners.ts
|
|
137
|
-
import z from "zod";
|
|
138
|
-
var asQueryNumber = (schema) => z.preprocess((value) => {
|
|
139
|
-
if (typeof value !== "string")
|
|
140
|
-
return value;
|
|
141
|
-
if (value.trim() === "")
|
|
142
|
-
return value;
|
|
143
|
-
return Number(value);
|
|
144
|
-
}, schema);
|
|
145
|
-
var asQueryBoolean = (schema) => {
|
|
146
|
-
const POSITIVE_VALUES = ["1", "true", "yes", "y", "on"];
|
|
147
|
-
const NEGATIVE_VALUES = ["0", "false", "no", "n", "off"];
|
|
148
|
-
return z.preprocess((value) => {
|
|
149
|
-
if (typeof value === "boolean")
|
|
150
|
-
return value;
|
|
151
|
-
if (typeof value === "string") {
|
|
152
|
-
const normalized = value.trim().toLowerCase();
|
|
153
|
-
if (POSITIVE_VALUES.includes(normalized))
|
|
154
|
-
return true;
|
|
155
|
-
if (NEGATIVE_VALUES.includes(normalized))
|
|
156
|
-
return false;
|
|
157
|
-
}
|
|
158
|
-
return value;
|
|
159
|
-
}, schema);
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
// src/pagination/pagination.schemas.ts
|
|
163
|
-
var paginationSchema = z2.object({
|
|
164
|
-
offset: asQueryNumber(z2.number().int().nonnegative()).optional(),
|
|
165
|
-
limit: asQueryNumber(z2.number().int().positive()).optional()
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
// src/pagination/pagination.refiners.ts
|
|
169
|
-
var refinePagination = (options) => {
|
|
170
|
-
const pagination = {};
|
|
171
|
-
if (options?.offset !== void 0) {
|
|
172
|
-
pagination.offset = options.offset;
|
|
173
|
-
}
|
|
174
|
-
if (options?.limit !== void 0) {
|
|
175
|
-
pagination.limit = options.limit;
|
|
176
|
-
}
|
|
177
|
-
return pagination;
|
|
178
|
-
};
|
|
179
|
-
|
|
180
139
|
// src/utilities/execution.utilities.ts
|
|
181
140
|
async function safeExecute(fn, onError) {
|
|
182
141
|
try {
|
|
@@ -242,12 +201,49 @@ var ZodJWTService = class {
|
|
|
242
201
|
return this.payloadSchema.parseAsync(payload);
|
|
243
202
|
}
|
|
244
203
|
};
|
|
204
|
+
|
|
205
|
+
// src/zod-pagination/zod-pagination.refiners.ts
|
|
206
|
+
var refinePagination = (options) => ({
|
|
207
|
+
...options?.offset !== void 0 ? { offset: options.offset } : {},
|
|
208
|
+
...options?.limit !== void 0 ? { limit: options.limit } : {}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// src/zod-pagination/zod-pagination.schemas.ts
|
|
212
|
+
import z2 from "zod";
|
|
213
|
+
|
|
214
|
+
// src/zod-validation/zod-validation.refiners.ts
|
|
215
|
+
import z from "zod";
|
|
216
|
+
var parseQueryValue = (value) => {
|
|
217
|
+
if (Array.isArray(value))
|
|
218
|
+
return value.map(parseQueryValue);
|
|
219
|
+
if (typeof value !== "string")
|
|
220
|
+
return value;
|
|
221
|
+
const normalized = value.trim().toLowerCase();
|
|
222
|
+
if (normalized === "")
|
|
223
|
+
return value;
|
|
224
|
+
if (normalized === "true")
|
|
225
|
+
return true;
|
|
226
|
+
if (normalized === "false")
|
|
227
|
+
return false;
|
|
228
|
+
if (/^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i.test(normalized))
|
|
229
|
+
return Number(normalized);
|
|
230
|
+
return value;
|
|
231
|
+
};
|
|
232
|
+
var asQuery = (schema) => z.preprocess(parseQueryValue, schema);
|
|
233
|
+
|
|
234
|
+
// src/zod-pagination/zod-pagination.schemas.ts
|
|
235
|
+
var zodPaginationSchema = z2.object({
|
|
236
|
+
/** Integer representing the starting point for pagination. */
|
|
237
|
+
offset: asQuery(z2.number().int().nonnegative()).optional(),
|
|
238
|
+
/** Integer representing the maximum number of items to return. */
|
|
239
|
+
limit: asQuery(z2.number().int().positive()).optional()
|
|
240
|
+
});
|
|
241
|
+
var zodPaginationShape = zodPaginationSchema.shape;
|
|
245
242
|
export {
|
|
246
243
|
EXCEPTION_STATUS_CODES,
|
|
247
244
|
SUCCESS_STATUS_CODES,
|
|
248
245
|
ZodJWTService,
|
|
249
|
-
|
|
250
|
-
asQueryNumber,
|
|
246
|
+
asQuery,
|
|
251
247
|
failure,
|
|
252
248
|
fetchAndThrow,
|
|
253
249
|
fetchSafely,
|
|
@@ -262,9 +258,11 @@ export {
|
|
|
262
258
|
log,
|
|
263
259
|
measureExecutionTime,
|
|
264
260
|
onHandlerError,
|
|
265
|
-
paginationSchema,
|
|
266
261
|
refinePagination,
|
|
267
262
|
respond,
|
|
268
263
|
safeExecute,
|
|
269
|
-
|
|
264
|
+
sqlWhere,
|
|
265
|
+
success,
|
|
266
|
+
zodPaginationSchema,
|
|
267
|
+
zodPaginationShape
|
|
270
268
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kalutskii/foundation",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.7",
|
|
4
4
|
"description": "Typescript collection of most common utilities, schemas and functions among private projects.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
},
|
|
13
13
|
"scripts": {
|
|
14
14
|
"build": "tsup",
|
|
15
|
-
"lint": "eslint .",
|
|
15
|
+
"lint": "eslint . --ext .ts",
|
|
16
|
+
"format": "prettier --write .",
|
|
16
17
|
"typecheck": "tsc --noEmit"
|
|
17
18
|
},
|
|
18
19
|
"exports": {
|
|
@@ -28,6 +29,7 @@
|
|
|
28
29
|
"@hono/zod-validator": "^0.8.0",
|
|
29
30
|
"date-fns": "^4.1.0",
|
|
30
31
|
"date-fns-tz": "^3.2.0",
|
|
32
|
+
"drizzle-orm": "^1.0.0-beta.22",
|
|
31
33
|
"hono": "^4.12.18",
|
|
32
34
|
"kleur": "^4.1.5",
|
|
33
35
|
"typescript": "^5",
|