@kalutskii/foundation 0.6.7 → 0.6.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/README.md +33 -5
- package/dist/index.d.ts +75 -9
- package/dist/index.js +46 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ Designed for use in Bun/Node.js and Cloudflare Workers environments.
|
|
|
12
12
|
- Logging utility: unified colored log interface.
|
|
13
13
|
- Random string generation utility.
|
|
14
14
|
- JWT service with optional Zod schema validation.
|
|
15
|
-
- Zod helpers for query param coercion, pagination schemas, and type utilities.
|
|
15
|
+
- Zod helpers for query param coercion, pagination schemas, nested schema flattening, and type utilities.
|
|
16
16
|
|
|
17
17
|
## Installation
|
|
18
18
|
|
|
@@ -144,7 +144,34 @@ schema.parse({ page: '2', isActive: 'true' }); // { page: 2, isActive: true }
|
|
|
144
144
|
|
|
145
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
146
|
|
|
147
|
-
###
|
|
147
|
+
### 8. Build flat query schemas from nested objects
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
import { asQuerySchema } from '@kalutskii/foundation';
|
|
151
|
+
import { z } from 'zod';
|
|
152
|
+
|
|
153
|
+
const schema = asQuerySchema(
|
|
154
|
+
z.object({
|
|
155
|
+
search: z.string().optional(),
|
|
156
|
+
sort: z
|
|
157
|
+
.object({
|
|
158
|
+
field: z.string(),
|
|
159
|
+
order: z.enum(['asc', 'desc']),
|
|
160
|
+
})
|
|
161
|
+
.optional(),
|
|
162
|
+
})
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
schema.parse({ search: 'foo', field: 'name', order: 'asc' });
|
|
166
|
+
// { search: 'foo', field: 'name', order: 'asc' }
|
|
167
|
+
|
|
168
|
+
// Rejects the original nested structure
|
|
169
|
+
schema.parse({ sort: { field: 'name', order: 'asc' } }); // throws
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
`asQuerySchema` flattens nested `z.object(...)` fields to the top level, making it suitable for flat query parameter validation. Nested object fields are lifted, while optionality is preserved: required parents keep their fields required, optional parents make all promoted fields optional. The result is a strict schema (unknown keys are rejected).
|
|
173
|
+
|
|
174
|
+
### 8. Use pagination schema
|
|
148
175
|
|
|
149
176
|
```ts
|
|
150
177
|
import { refinePagination, zodPaginationSchema, zodPaginationShape } from '@kalutskii/foundation';
|
|
@@ -166,7 +193,7 @@ await db.query.users.findMany({
|
|
|
166
193
|
});
|
|
167
194
|
```
|
|
168
195
|
|
|
169
|
-
###
|
|
196
|
+
### 9. Work with JWT using `ZodJWTService`
|
|
170
197
|
|
|
171
198
|
```ts
|
|
172
199
|
import { ZodJWTService } from '@kalutskii/foundation';
|
|
@@ -183,7 +210,7 @@ const payload = await jwt.verifyOrThrow(token, 'secret');
|
|
|
183
210
|
const decoded = await jwt.decode(token);
|
|
184
211
|
```
|
|
185
212
|
|
|
186
|
-
###
|
|
213
|
+
### 10. Datetime helpers
|
|
187
214
|
|
|
188
215
|
```ts
|
|
189
216
|
import { formatTime, getFormattedDate, getFormattedTime, getZonedTime } from '@kalutskii/foundation';
|
|
@@ -194,7 +221,7 @@ getFormattedDate({ tz: 'Europe/Moscow', withTime: false }); // '22.06.2026'
|
|
|
194
221
|
formatTime(new Date(), { tz: 'Europe/Moscow' }); // '15:30:00, 22 июня 2026 (+3 UTC)'
|
|
195
222
|
```
|
|
196
223
|
|
|
197
|
-
###
|
|
224
|
+
### 11. Logging
|
|
198
225
|
|
|
199
226
|
```ts
|
|
200
227
|
import { log } from '@kalutskii/foundation';
|
|
@@ -214,6 +241,7 @@ The package exports all public APIs from a single entrypoint:
|
|
|
214
241
|
- **Utilities**: `safeExecute`, `measureExecutionTime`, `generateRandomString`, `log`, `getColoredHTTPStatus`, datetime helpers.
|
|
215
242
|
- **Zod JWT**: `ZodJWTService`.
|
|
216
243
|
- **Zod Pagination**: `zodPaginationSchema`, `zodPaginationShape`, `refinePagination`, `ZodPaginationOptions`.
|
|
244
|
+
- **Zod Flatten**: `asQuerySchema`, `flattenZodShape`, `isZodObject`, `isZodOptional`, `unwrapOptional`, `FlattenZodShape`.
|
|
217
245
|
- **Zod Validation**: `asQuery`, `AsQuery`, `AtLeastOne`.
|
|
218
246
|
- **Type Utilities**: `Simplify`.
|
|
219
247
|
|
package/dist/index.d.ts
CHANGED
|
@@ -11,14 +11,10 @@ import z from 'zod';
|
|
|
11
11
|
* Assumes that `where` was already validated and contains only existing table fields.
|
|
12
12
|
*
|
|
13
13
|
* ```ts
|
|
14
|
-
* await db
|
|
15
|
-
* .update(usersTable)
|
|
16
|
-
* .set(values)
|
|
17
|
-
* .where(sqlWhere(usersTable, { id: 1 }))
|
|
18
|
-
* .returning();
|
|
14
|
+
* await db.update(usersTable).set(values).where(sqlWhere(usersTable, { id: 1 })).returning();
|
|
19
15
|
* ```
|
|
20
16
|
*/
|
|
21
|
-
declare function sqlWhere(table: unknown, where: Record<string, unknown>): SQL
|
|
17
|
+
declare function sqlWhere(table: unknown, where: Record<string, unknown>): SQL;
|
|
22
18
|
|
|
23
19
|
/**
|
|
24
20
|
* Global error handler for Hono framework. Catches all exceptions thrown in route handlers and middlewares.
|
|
@@ -72,8 +68,8 @@ type Simplify<T> = {
|
|
|
72
68
|
|
|
73
69
|
type QueryPrimitive = string | number | boolean | bigint | Date | null | undefined;
|
|
74
70
|
/**
|
|
75
|
-
*
|
|
76
|
-
*
|
|
71
|
+
* Recursively transforms all fields of T to `string`, matching how query parameters are serialized.
|
|
72
|
+
* Handles nested objects, arrays, and primitive values (including null and undefined as optional).
|
|
77
73
|
*/
|
|
78
74
|
type AsQuery<T> = T extends QueryPrimitive ? string : T extends readonly (infer Item)[] ? AsQuery<Item>[] : T extends object ? {
|
|
79
75
|
[Key in keyof T]: AsQuery<T[Key]>;
|
|
@@ -319,6 +315,76 @@ type ZodPaginationOptions = z.infer<typeof zodPaginationSchema>;
|
|
|
319
315
|
*/
|
|
320
316
|
declare const refinePagination: (options?: ZodPaginationOptions) => ZodPaginationOptions;
|
|
321
317
|
|
|
318
|
+
type ZodShape = Record<string, z.ZodTypeAny>;
|
|
319
|
+
type AnyZodObject = z.ZodObject<ZodShape>;
|
|
320
|
+
type UnionToIntersection<T> = (T extends unknown ? (value: T) => void : never) extends (value: infer I) => void ? I : never;
|
|
321
|
+
type UnwrapOptional<T extends z.ZodTypeAny> = T extends z.ZodOptional<infer Inner> ? Inner : T;
|
|
322
|
+
type IsOptional<T extends z.ZodTypeAny> = T extends z.ZodOptional<z.ZodTypeAny> ? true : false;
|
|
323
|
+
type Optionalize<TSchema extends z.ZodTypeAny, TShouldOptionalize extends boolean> = TShouldOptionalize extends true ? z.ZodOptional<TSchema> : TSchema;
|
|
324
|
+
type FlattenObjectField<TSchema extends z.ZodTypeAny> = UnwrapOptional<TSchema> extends z.ZodObject<infer InnerShape extends ZodShape> ? {
|
|
325
|
+
[K in keyof InnerShape]: Optionalize<InnerShape[K], IsOptional<TSchema>>;
|
|
326
|
+
} : never;
|
|
327
|
+
type FlattenNestedObjectFields<TShape extends ZodShape> = {
|
|
328
|
+
[K in keyof TShape]: FlattenObjectField<TShape[K]>;
|
|
329
|
+
}[keyof TShape];
|
|
330
|
+
/**
|
|
331
|
+
* The resulting shape after all nested `z.ZodObject` fields have been lifted to the top level.
|
|
332
|
+
* Primitive fields are kept as-is; nested object fields are replaced with their inner keys.
|
|
333
|
+
* If the parent field was optional, all promoted inner fields become optional as well.
|
|
334
|
+
*/
|
|
335
|
+
type FlattenZodShape<TShape extends ZodShape> = {
|
|
336
|
+
[K in keyof TShape as UnwrapOptional<TShape[K]> extends AnyZodObject ? never : K]: TShape[K];
|
|
337
|
+
} & UnionToIntersection<FlattenNestedObjectFields<TShape>>;
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Recursively lifts all nested `z.ZodObject` fields to the top level of the shape.
|
|
341
|
+
* Primitive fields are preserved as-is. If the parent field was optional, all promoted
|
|
342
|
+
* inner keys become optional to avoid requiring fields that were previously nested under an optional object.
|
|
343
|
+
*
|
|
344
|
+
* ```ts
|
|
345
|
+
* const flat = flattenZodShape({
|
|
346
|
+
* name: z.string(),
|
|
347
|
+
* address: z.object({ city: z.string(), zip: z.string() }).optional(),
|
|
348
|
+
* });
|
|
349
|
+
* // { name: z.string(), city: z.string().optional(), zip: z.string().optional() }
|
|
350
|
+
* ```
|
|
351
|
+
*/
|
|
352
|
+
declare const flattenZodShape: <TShape extends ZodShape>(shape: TShape) => FlattenZodShape<TShape>;
|
|
353
|
+
/**
|
|
354
|
+
* Builds a strict flat query schema from a nested `z.ZodObject`.
|
|
355
|
+
* All nested object fields are lifted to the top level while preserving optionality,
|
|
356
|
+
* making the result suitable for validating flat query parameter objects.
|
|
357
|
+
*
|
|
358
|
+
* ```ts
|
|
359
|
+
* const schema = asQuerySchema(
|
|
360
|
+
* z.object({
|
|
361
|
+
* search: z.string().optional(),
|
|
362
|
+
* sort: z.object({ field: z.string(), order: z.enum(['asc', 'desc']) }).optional(),
|
|
363
|
+
* })
|
|
364
|
+
* );
|
|
365
|
+
*
|
|
366
|
+
* schema.parse({ search: 'foo', field: 'name', order: 'asc' });
|
|
367
|
+
* // { search: 'foo', field: 'name', order: 'asc' }
|
|
368
|
+
* ```
|
|
369
|
+
*/
|
|
370
|
+
declare const asQuerySchema: <TShape extends ZodShape>(schema: z.ZodObject<TShape>) => z.ZodObject<FlattenZodShape<TShape> extends infer T ? { -readonly [P in keyof T]: T[P]; } : never, z.core.$strict>;
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Checks if the provided schema is an instance of `z.ZodObject`.
|
|
374
|
+
* @returns `true` if the schema is a `z.ZodObject`, `false` otherwise.
|
|
375
|
+
*/
|
|
376
|
+
declare const isZodObject: (schema: z.ZodTypeAny) => schema is AnyZodObject;
|
|
377
|
+
/**
|
|
378
|
+
* Checks if the provided schema is an instance of `z.ZodOptional`.
|
|
379
|
+
* @returns `true` if the schema is a `z.ZodOptional`, `false` otherwise.
|
|
380
|
+
*/
|
|
381
|
+
declare const isZodOptional: (schema: z.ZodTypeAny) => schema is z.ZodOptional<z.ZodTypeAny>;
|
|
382
|
+
/**
|
|
383
|
+
* Unwraps a `z.ZodOptional` schema to retrieve its inner schema.
|
|
384
|
+
* If the provided schema is not optional, it is returned unchanged.
|
|
385
|
+
*/
|
|
386
|
+
declare const unwrapOptional: (schema: z.ZodTypeAny) => z.ZodTypeAny;
|
|
387
|
+
|
|
322
388
|
/**
|
|
323
389
|
* Query parameters always arrive as strings, even when they semantically represent numbers
|
|
324
390
|
* or booleans, so we preprocess them to convert to the appropriate types before validation.
|
|
@@ -338,4 +404,4 @@ declare const refinePagination: (options?: ZodPaginationOptions) => ZodPaginatio
|
|
|
338
404
|
*/
|
|
339
405
|
declare const asQuery: <T extends z.ZodTypeAny>(schema: T) => z.ZodPreprocess<T>;
|
|
340
406
|
|
|
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 };
|
|
407
|
+
export { type APIContractData, type APIContractError, type APIContractResult, type APIError, type APISuccess, type AnyZodObject, type AsQuery, type AtLeastOne, EXCEPTION_STATUS_CODES, type ExceptionStatusCode, type FetchResult, type FlattenZodShape, type JWTServiceOptions, type JWTSignOptions, type MeasuredExecution, type Payload, type PayloadSchema, SUCCESS_STATUS_CODES, type Simplify, type SuccessStatusCode, ZodJWTService, type ZodPaginationOptions, type ZodShape, asQuery, asQuerySchema, failure, fetchAndThrow, fetchSafely, flattenZodShape, formatTime, generateRandomString, getColoredHTTPStatus, getFormattedDate, getFormattedTime, getUTCOffset, getZonedTime, honoLoggingHandler, isZodObject, isZodOptional, log, measureExecutionTime, onHandlerError, refinePagination, respond, safeExecute, sqlWhere, success, unwrapOptional, zodPaginationSchema, zodPaginationShape };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
// src/drizzle/drizzle.refiners.ts
|
|
2
2
|
import { and, eq } from "drizzle-orm";
|
|
3
|
+
var AT_LEAST_ONE_DEFINED_CONDITION_ERROR = "sqlWhere requires at least one defined condition.";
|
|
3
4
|
function sqlWhere(table, where) {
|
|
4
|
-
|
|
5
|
+
const columns = table;
|
|
6
|
+
const conditions = Object.entries(where).filter(([, value]) => value !== void 0).map(([key, value]) => eq(columns[key], value));
|
|
7
|
+
if (conditions.length === 0) {
|
|
8
|
+
throw new Error(AT_LEAST_ONE_DEFINED_CONDITION_ERROR);
|
|
9
|
+
}
|
|
10
|
+
return and(...conditions);
|
|
5
11
|
}
|
|
6
12
|
|
|
7
13
|
// src/hono/hono.execution.ts
|
|
@@ -239,14 +245,50 @@ var zodPaginationSchema = z2.object({
|
|
|
239
245
|
limit: asQuery(z2.number().int().positive()).optional()
|
|
240
246
|
});
|
|
241
247
|
var zodPaginationShape = zodPaginationSchema.shape;
|
|
248
|
+
|
|
249
|
+
// src/zod-flatten/zod-flatten.schemas.ts
|
|
250
|
+
import z4 from "zod";
|
|
251
|
+
|
|
252
|
+
// src/zod-flatten/zod-flatten.utilities.ts
|
|
253
|
+
import z3 from "zod";
|
|
254
|
+
var isZodObject = (schema) => {
|
|
255
|
+
return schema instanceof z3.ZodObject;
|
|
256
|
+
};
|
|
257
|
+
var isZodOptional = (schema) => {
|
|
258
|
+
return schema instanceof z3.ZodOptional;
|
|
259
|
+
};
|
|
260
|
+
var unwrapOptional = (schema) => {
|
|
261
|
+
return isZodOptional(schema) ? schema.unwrap() : schema;
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
// src/zod-flatten/zod-flatten.schemas.ts
|
|
265
|
+
var flattenZodShape = (shape) => {
|
|
266
|
+
const flat = {};
|
|
267
|
+
for (const [key, schema] of Object.entries(shape)) {
|
|
268
|
+
const unwrappedSchema = unwrapOptional(schema);
|
|
269
|
+
if (isZodObject(unwrappedSchema) === false) {
|
|
270
|
+
flat[key] = schema;
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
for (const [innerKey, innerSchema] of Object.entries(unwrappedSchema.shape)) {
|
|
274
|
+
flat[innerKey] = isZodOptional(schema) ? innerSchema.optional() : innerSchema;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return flat;
|
|
278
|
+
};
|
|
279
|
+
var asQuerySchema = (schema) => {
|
|
280
|
+
return z4.object(flattenZodShape(schema.shape)).strict();
|
|
281
|
+
};
|
|
242
282
|
export {
|
|
243
283
|
EXCEPTION_STATUS_CODES,
|
|
244
284
|
SUCCESS_STATUS_CODES,
|
|
245
285
|
ZodJWTService,
|
|
246
286
|
asQuery,
|
|
287
|
+
asQuerySchema,
|
|
247
288
|
failure,
|
|
248
289
|
fetchAndThrow,
|
|
249
290
|
fetchSafely,
|
|
291
|
+
flattenZodShape,
|
|
250
292
|
formatTime,
|
|
251
293
|
generateRandomString,
|
|
252
294
|
getColoredHTTPStatus,
|
|
@@ -255,6 +297,8 @@ export {
|
|
|
255
297
|
getUTCOffset,
|
|
256
298
|
getZonedTime,
|
|
257
299
|
honoLoggingHandler,
|
|
300
|
+
isZodObject,
|
|
301
|
+
isZodOptional,
|
|
258
302
|
log,
|
|
259
303
|
measureExecutionTime,
|
|
260
304
|
onHandlerError,
|
|
@@ -263,6 +307,7 @@ export {
|
|
|
263
307
|
safeExecute,
|
|
264
308
|
sqlWhere,
|
|
265
309
|
success,
|
|
310
|
+
unwrapOptional,
|
|
266
311
|
zodPaginationSchema,
|
|
267
312
|
zodPaginationShape
|
|
268
313
|
};
|