@kalutskii/foundation 0.6.22 → 0.6.23

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 CHANGED
@@ -144,7 +144,7 @@ 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
- ### 8. Build flat query schemas from nested objects
147
+ ### 7. Build flat query schemas from nested objects
148
148
 
149
149
  ```typescript
150
150
  import { asQuerySchema } from '@kalutskii/foundation';
@@ -171,26 +171,32 @@ schema.parse({ sort: { field: 'name', order: 'asc' } }); // throws
171
171
 
172
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
173
 
174
- ### 8. Use pagination schema
174
+ ### 8. Build reusable search schemas
175
175
 
176
176
  ```typescript
177
- import { refinePagination, zodPaginationSchema, zodPaginationShape } from '@kalutskii/foundation';
177
+ import { zodPaginationSchema, zodSearchSchema } from '@kalutskii/foundation';
178
178
  import { z } from 'zod';
179
179
 
180
- // Use as a standalone schema
181
- const options = zodPaginationSchema.parse({ offset: '0', limit: '20' });
182
- // { offset: 0, limit: 20 }
180
+ const assetSchema = z.object({
181
+ status: z.enum(['active', 'archived']),
182
+ categoryId: z.number(),
183
+ });
183
184
 
184
- // Spread shape into a larger query schema
185
- const querySchema = z.object({
186
- search: z.string().optional(),
187
- ...zodPaginationShape,
185
+ const assetSearchSchema = zodSearchSchema({
186
+ filters: assetSchema.pick({ status: true, categoryId: true }),
188
187
  });
189
188
 
190
- // Strip undefined pagination values before passing to a query builder (e.g. Drizzle)
191
- await db.query.users.findMany({
192
- ...refinePagination(options),
189
+ type AssetSearch = z.infer<typeof assetSearchSchema>;
190
+
191
+ assetSearchSchema.parse({
192
+ where: { status: 'active' },
193
+ query: 'asset name',
194
+ pagination: { offset: 0, limit: 20 },
193
195
  });
196
+
197
+ // Pagination remains available as a standalone reusable schema.
198
+ zodPaginationSchema.parse({ offset: '0', limit: '20' });
199
+ // { offset: 0, limit: 20 }
194
200
  ```
195
201
 
196
202
  ### 9. Work with JWT using `ZodJWTService`
@@ -240,7 +246,8 @@ The package exports all public APIs from a single entrypoint:
240
246
  - **Hono**: `respond`, `onHandlerError`, `honoLoggingHandler`.
241
247
  - **Utilities**: `safeExecute`, `measureExecutionTime`, `generateRandomString`, `log`, `getColoredHTTPStatus`, datetime helpers.
242
248
  - **Zod JWT**: `ZodJWTService`.
243
- - **Zod Pagination**: `zodPaginationSchema`, `zodPaginationShape`, `refinePagination`, `ZodPaginationOptions`.
249
+ - **Zod Search**: `zodSearchSchema`, `ZodSearchSchemaOptions`.
250
+ - **Zod Pagination**: `zodPaginationSchema`, `zodPaginationShape`, `ZodPaginationOptions`.
244
251
  - **Zod Flatten**: `asQuerySchema`, `flattenZodShape`, `isZodObject`, `isZodOptional`, `unwrapOptional`, `FlattenZodShape`.
245
252
  - **Zod Validation**: `asQuery`, `AsQuery`, `AtLeastOne`.
246
253
  - **Type Utilities**: `Simplify`.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { SQL, Simplify as Simplify$1 } from 'drizzle-orm';
1
+ import { SQL } from 'drizzle-orm';
2
2
  import { ErrorHandler, MiddlewareHandler, Context, TypedResponse } from 'hono';
3
3
  import { Locale } from 'date-fns';
4
4
  import { SymmetricAlgorithm } from 'hono/utils/jwt/jwa';
@@ -274,26 +274,91 @@ declare class ZodJWTService<TPayloadOrSchema> {
274
274
  verifyOrThrow(token: string, secret: string): Promise<Payload<TPayloadOrSchema>>;
275
275
  }
276
276
 
277
+ /**
278
+ * Validates offset-based pagination shared by search request contracts.
279
+ * Missing fields inside the required object receive predictable defaults.
280
+ */
277
281
  declare const zodPaginationSchema: z.ZodObject<{
278
282
  offset: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
279
283
  limit: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
280
284
  }, z.core.$strip>;
281
285
  /**
282
- * Reusable pagination fields for flat query schemas, allowing for
283
- * consistent validation of pagination parameters across different endpoints.
284
- *
285
- * Spread into another `z.object(...)` when composing a larger query schema,
286
- * or use `zodPaginationSchema.extend(...)` when building directly from the schema.
286
+ * Exposes pagination fields for composition into other object schemas.
287
+ * The shape stays derived from the schema to prevent contract divergence.
287
288
  */
288
289
  declare const zodPaginationShape: {
289
290
  offset: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
290
291
  limit: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
291
292
  };
293
+
292
294
  /**
293
- * TypeScript type representing the pagination options validated by the `zodPaginationSchema`.
294
- * Read documentation for `zodPaginationSchema` for more details on the structure and usage of this type.
295
+ * TypeScript output produced after successful pagination validation.
296
+ * Defaulted fields are represented as required numeric properties.
295
297
  */
296
298
  type ZodPaginationOptions = z.infer<typeof zodPaginationSchema>;
299
+ /**
300
+ * Configuration used to compose a reusable search request schema.
301
+ * Literal feature flags are preserved in the resulting static shape.
302
+ */
303
+ type ZodSearchSchemaOptions<TShape extends z.ZodRawShape, TQueryEnabled extends boolean = true, TPaginationEnabled extends boolean = true> = {
304
+ /**
305
+ * Object schema whose fields become available inside the `where` object.
306
+ * Every resulting field is optional, but one value must be defined.
307
+ */
308
+ filters: z.ZodObject<TShape>;
309
+ /**
310
+ * Controls whether an optional non-empty `query` field is generated.
311
+ * The field is enabled when this option is omitted from the call.
312
+ */
313
+ queryEnabled?: TQueryEnabled;
314
+ /**
315
+ * Controls whether a required `pagination` object is generated.
316
+ * The field is enabled when this option is omitted from the call.
317
+ */
318
+ paginationEnabled?: TPaginationEnabled;
319
+ };
320
+
321
+ /**
322
+ * Optional text query shared by search request contracts.
323
+ * Provided values are trimmed and must contain visible text.
324
+ */
325
+ declare const zodSearchQuerySchema: z.ZodOptional<z.ZodString>;
326
+ /**
327
+ * Makes every supplied filter optional while requiring one defined value.
328
+ * The wrapper itself stays optional because searches may omit filtering.
329
+ */
330
+ declare const createZodSearchWhereSchema: <TShape extends z.ZodRawShape>(filters: z.ZodObject<TShape>) => z.ZodOptional<z.ZodPipe<z.ZodObject<{ -readonly [k in keyof TShape]: z.ZodOptional<TShape[k]>; }, z.core.$strip>, z.ZodTransform<Awaited<AtLeastOne<z.core.$InferObjectOutput<{ -readonly [k in keyof TShape]: z.ZodOptional<TShape[k]>; }, {}>, keyof z.core.$InferObjectOutput<{ -readonly [k in keyof TShape]: z.ZodOptional<TShape[k]>; }, {}>>>, z.core.$InferObjectOutput<{ -readonly [k in keyof TShape]: z.ZodOptional<TShape[k]>; }, {}>>>>;
331
+ /**
332
+ * Empty schema shape used when a search feature is explicitly disabled.
333
+ * Intersections remove disabled fields without widening enabled branches.
334
+ */
335
+ type ZodDisabledSearchShape = Record<never, never>;
336
+ /**
337
+ * Schema-level shape assembled from filter fields and literal feature flags.
338
+ * Keeping Zod schemas here makes runtime composition the contract source.
339
+ */
340
+ type ZodSearchShape<TShape extends z.ZodRawShape, TQueryEnabled extends boolean, TPaginationEnabled extends boolean> = {
341
+ where: ReturnType<typeof createZodSearchWhereSchema<TShape>>;
342
+ } & (TQueryEnabled extends false ? ZodDisabledSearchShape : {
343
+ query: typeof zodSearchQuerySchema;
344
+ }) & (TPaginationEnabled extends false ? ZodDisabledSearchShape : {
345
+ pagination: typeof zodPaginationSchema;
346
+ });
347
+ /**
348
+ * Builds a strict schema for reusable search request contracts.
349
+ * The optional `where` object must contain one defined filter.
350
+ *
351
+ * Query is optional and non-empty; pagination is required by default.
352
+ * Literal feature flags update both runtime and inferred static shapes.
353
+ *
354
+ * @example
355
+ * const assetSearchSchema = zodSearchSchema({
356
+ * filters: assetSchema.pick({ status: true }),
357
+ * });
358
+ *
359
+ * type AssetSearch = z.infer<typeof assetSearchSchema>;
360
+ */
361
+ declare const zodSearchSchema: <const TShape extends z.ZodRawShape, const TQueryEnabled extends boolean = true, const TPaginationEnabled extends boolean = true>({ filters, queryEnabled, paginationEnabled, }: ZodSearchSchemaOptions<TShape, TQueryEnabled, TPaginationEnabled>) => z.ZodObject<ZodSearchShape<TShape, TQueryEnabled, TPaginationEnabled> extends infer T ? { -readonly [P in keyof T]: T[P]; } : never, z.core.$strict>;
297
362
 
298
363
  type QueryPrimitive = string | number | boolean | bigint | Date;
299
364
  /**
@@ -316,7 +381,7 @@ type AsQuery<T> = T extends null | undefined ? T : T extends QueryPrimitive ? st
316
381
  * // Invalid: {}, { a: undefined, b: undefined, c: undefined }
317
382
  * ```
318
383
  */
319
- type AtLeastOne<T, Keys extends keyof T = keyof T> = Keys extends keyof T ? Simplify$1<Required<Pick<T, Keys>> & Partial<Omit<T, Keys>>> : never;
384
+ type AtLeastOne<T, Keys extends keyof T = keyof T> = Keys extends keyof T ? Simplify<Required<Pick<T, Keys>> & Partial<Omit<T, Keys>>> : never;
320
385
 
321
386
  /**
322
387
  * Wraps a partial Zod object schema with:
@@ -356,4 +421,4 @@ declare const asQuery: <T extends z.ZodTypeAny>(schema: T) => z.ZodPreprocess<T>
356
421
  */
357
422
  declare const isPlainObject: (value: unknown) => value is Record<string, unknown>;
358
423
 
359
- 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, type ReplaceDotsWithUnderscores, SUCCESS_STATUS_CODES, type Simplify, type StringEnumRecord, type SuccessStatusCode, ZodJWTService, type ZodPaginationOptions, asQuery, createStringEnumRecord, failure, fetchAndThrow, fetchSafely, formatTime, generateRandomString, getColoredHTTPStatus, getFormattedDate, getFormattedTime, getUTCOffset, getZonedTime, honoLoggingHandler, isPlainObject, log, measureExecutionTime, onHandlerError, parseQueryValue, respond, safeExecute, sqlWhere, success, zodAtLeastOne, zodPaginationSchema, zodPaginationShape };
424
+ 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, type ReplaceDotsWithUnderscores, SUCCESS_STATUS_CODES, type Simplify, type StringEnumRecord, type SuccessStatusCode, ZodJWTService, type ZodPaginationOptions, type ZodSearchSchemaOptions, asQuery, createStringEnumRecord, failure, fetchAndThrow, fetchSafely, formatTime, generateRandomString, getColoredHTTPStatus, getFormattedDate, getFormattedTime, getUTCOffset, getZonedTime, honoLoggingHandler, isPlainObject, log, measureExecutionTime, onHandlerError, parseQueryValue, respond, safeExecute, sqlWhere, success, zodAtLeastOne, zodPaginationSchema, zodPaginationShape, zodSearchSchema };
package/dist/index.js CHANGED
@@ -213,16 +213,25 @@ var ZodJWTService = class {
213
213
  }
214
214
  };
215
215
 
216
- // src/zod-pagination/zod-pagination.schemas.ts
216
+ // src/zod-search/zod-search.pagination.schemas.ts
217
217
  import z from "zod";
218
218
  var zodPaginationSchema = z.object({
219
- /** Integer representing the starting point for pagination. */
219
+ /**
220
+ * Zero-based number of records skipped before collecting a result page.
221
+ * String values are coerced to support validation of URL query input.
222
+ */
220
223
  offset: z.coerce.number().int().nonnegative().default(0),
221
- /** Integer representing the maximum number of items to return. */
224
+ /**
225
+ * Positive maximum number of records returned in a single result page.
226
+ * String values are coerced to support validation of URL query input.
227
+ */
222
228
  limit: z.coerce.number().int().positive().default(10)
223
229
  });
224
230
  var zodPaginationShape = zodPaginationSchema.shape;
225
231
 
232
+ // src/zod-search/zod-search.schemas.ts
233
+ import z2 from "zod";
234
+
226
235
  // src/zod-validation/zod-validation.refiners.ts
227
236
  var DEFAULT_ERROR_MESSAGE = "Invalid input. At least one field must be provided.";
228
237
  var zodAtLeastOne = (schema) => schema.superRefine((val, ctx) => {
@@ -231,8 +240,20 @@ var zodAtLeastOne = (schema) => schema.superRefine((val, ctx) => {
231
240
  ctx.addIssue({ code: "custom", message: DEFAULT_ERROR_MESSAGE });
232
241
  }).transform((val) => val);
233
242
 
243
+ // src/zod-search/zod-search.schemas.ts
244
+ var zodSearchQuerySchema = z2.string().trim().min(1).optional();
245
+ var createZodSearchWhereSchema = (filters) => zodAtLeastOne(filters.partial()).optional();
246
+ var zodSearchSchema = ({ filters, queryEnabled, paginationEnabled }) => {
247
+ const shape = {
248
+ where: createZodSearchWhereSchema(filters),
249
+ ...queryEnabled !== false ? { query: zodSearchQuerySchema } : {},
250
+ ...paginationEnabled !== false ? { pagination: zodPaginationSchema } : {}
251
+ };
252
+ return z2.object(shape).strict();
253
+ };
254
+
234
255
  // src/zod-validation/zod-validation.parsing.ts
235
- import z2 from "zod";
256
+ import z3 from "zod";
236
257
 
237
258
  // src/zod-validation/zod-validation.utilities.ts
238
259
  var isPlainObject = (value) => {
@@ -259,7 +280,7 @@ var parseQueryValue = (value) => {
259
280
  return Number(normalized);
260
281
  return value;
261
282
  };
262
- var asQuery = (schema) => z2.preprocess(parseQueryValue, schema);
283
+ var asQuery = (schema) => z3.preprocess(parseQueryValue, schema);
263
284
  export {
264
285
  EXCEPTION_STATUS_CODES,
265
286
  SUCCESS_STATUS_CODES,
@@ -288,5 +309,6 @@ export {
288
309
  success,
289
310
  zodAtLeastOne,
290
311
  zodPaginationSchema,
291
- zodPaginationShape
312
+ zodPaginationShape,
313
+ zodSearchSchema
292
314
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kalutskii/foundation",
3
- "version": "0.6.22",
3
+ "version": "0.6.23",
4
4
  "description": "Typescript collection of most common utilities, schemas and functions among private projects.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -14,6 +14,7 @@
14
14
  "build": "tsup",
15
15
  "lint": "eslint . --ext .ts --fix",
16
16
  "format": "prettier --write .",
17
+ "test": "bun test",
17
18
  "typecheck": "tsc --noEmit"
18
19
  },
19
20
  "exports": {