@lyxa.ai/types 1.1.52 → 1.1.54

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
@@ -22,7 +22,7 @@ Perfect for sharing types between frontend and backend applications.
22
22
 
23
23
  ## Version
24
24
 
25
- Version: 1.1.52
25
+ Version: 1.1.54
26
26
 
27
27
  ## Dependencies
28
28
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lyxa.ai/types",
3
- "version": "1.1.52",
3
+ "version": "1.1.54",
4
4
  "description": "Lyxa type definitions and validation schemas for both frontend and backend",
5
5
  "author": "elie <42282499+Internalizable@users.noreply.github.com>",
6
6
  "license": "MIT",
@@ -1,9 +1,10 @@
1
1
  import { z } from 'zod';
2
2
  type ReadonlyNonEmptyArray<T> = readonly [T, ...T[]];
3
- export interface FilterSchemaConfig<TSelect extends ReadonlyNonEmptyArray<string>, TPopulate extends ReadonlyNonEmptyArray<string>, TSearch extends ReadonlyNonEmptyArray<string>> {
3
+ export interface FilterSchemaConfig<TSelect extends ReadonlyNonEmptyArray<string>, TPopulate extends ReadonlyNonEmptyArray<string>, TSearch extends ReadonlyNonEmptyArray<string>, TQuery extends ReadonlyNonEmptyArray<string> | [] = []> {
4
4
  selectableFields: TSelect;
5
5
  populatableFields: TPopulate;
6
6
  searchableFields: TSearch;
7
+ queryableFields?: TQuery;
7
8
  maxSize?: number;
8
9
  defaultSize?: number;
9
10
  }
@@ -18,11 +19,11 @@ export type PopulateObject<TPopulate extends ReadonlyNonEmptyArray<string>, TSel
18
19
  };
19
20
  populate?: PopulateObject<TPopulate, TSelect> | PopulateObject<TPopulate, TSelect>[];
20
21
  };
21
- export type FilterDTO<TSelect extends ReadonlyNonEmptyArray<string>, TPopulate extends ReadonlyNonEmptyArray<string>, TSearch extends ReadonlyNonEmptyArray<string>> = {
22
+ export type FilterDTO<TSelect extends ReadonlyNonEmptyArray<string>, TPopulate extends ReadonlyNonEmptyArray<string>, TSearch extends ReadonlyNonEmptyArray<string>, TQuery extends ReadonlyNonEmptyArray<string> | [] = []> = {
22
23
  size?: number;
23
24
  page?: number;
24
25
  sort?: Record<string, 1 | -1>;
25
- query?: Record<string, any>;
26
+ query?: TQuery extends ReadonlyNonEmptyArray<string> ? Partial<Record<TQuery[number], any>> : Record<string, any>;
26
27
  select?: Partial<Record<TSelect[number], 0 | 1>>;
27
28
  populate?: PopulateObject<TPopulate, TSelect> | PopulateObject<TPopulate, TSelect>[];
28
29
  search?: {
@@ -30,5 +31,5 @@ export type FilterDTO<TSelect extends ReadonlyNonEmptyArray<string>, TPopulate e
30
31
  searchFields?: TSearch[number][];
31
32
  };
32
33
  };
33
- export declare function createFilterSchema<TSelect extends ReadonlyNonEmptyArray<string>, TPopulate extends ReadonlyNonEmptyArray<string>, TSearch extends ReadonlyNonEmptyArray<string>>(config: FilterSchemaConfig<TSelect, TPopulate, TSearch>): z.ZodType<FilterDTO<TSelect, TPopulate, TSearch>>;
34
+ export declare function createFilterSchema<TSelect extends ReadonlyNonEmptyArray<string>, TPopulate extends ReadonlyNonEmptyArray<string>, TSearch extends ReadonlyNonEmptyArray<string>, TQuery extends ReadonlyNonEmptyArray<string> | [] = []>(config: FilterSchemaConfig<TSelect, TPopulate, TSearch, TQuery>): z.ZodType<FilterDTO<TSelect, TPopulate, TSearch, TQuery>>;
34
35
  export {};
@@ -3,9 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createFilterSchema = createFilterSchema;
4
4
  const zod_1 = require("zod");
5
5
  const sortSchema = zod_1.z.record(zod_1.z.string(), zod_1.z.union([zod_1.z.literal(1), zod_1.z.literal(-1)]));
6
- const MongoQuerySchema = zod_1.z.record(zod_1.z.string(), zod_1.z.any());
7
6
  function createFilterSchema(config) {
8
- const { selectableFields, populatableFields, searchableFields, maxSize = 100, defaultSize = 10 } = config;
7
+ const { selectableFields, populatableFields, searchableFields, queryableFields, maxSize = 100, defaultSize = 10, } = config;
9
8
  const selectSchema = zod_1.z.record(zod_1.z.enum(selectableFields), zod_1.z.union([zod_1.z.literal(0), zod_1.z.literal(1)]));
10
9
  const populateSchema = zod_1.z.lazy(() => zod_1.z.object({
11
10
  path: zod_1.z.enum(populatableFields),
@@ -24,11 +23,14 @@ function createFilterSchema(config) {
24
23
  searchKey: zod_1.z.string(),
25
24
  searchFields: zod_1.z.array(zod_1.z.enum(searchableFields)).optional(),
26
25
  });
26
+ const querySchema = queryableFields && queryableFields.length > 0
27
+ ? zod_1.z.record(zod_1.z.enum(queryableFields), zod_1.z.any())
28
+ : zod_1.z.any();
27
29
  const schema = zod_1.z.object({
28
30
  size: zod_1.z.number().min(1).max(maxSize).optional().default(defaultSize),
29
31
  page: zod_1.z.number().min(1).optional().default(1),
30
32
  sort: sortSchema.optional(),
31
- query: MongoQuerySchema.optional(),
33
+ query: querySchema.optional(),
32
34
  select: selectSchema.optional(),
33
35
  populate: zod_1.z.union([populateSchema, zod_1.z.array(populateSchema)]).optional(),
34
36
  search: searchSchema.optional(),
@@ -1 +1 @@
1
- {"version":3,"file":"filter-schema.factory.js","sourceRoot":"/","sources":["utilities/validation/filter-schema.factory.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from 'zod';\n\nconst sortSchema = z.record(z.string(), z.union([z.literal(1), z.literal(-1)]));\nconst MongoQuerySchema = z.record(z.string(), z.any());\n\ntype ReadonlyNonEmptyArray<T> = readonly [T, ...T[]];\n\nexport interface FilterSchemaConfig<\n\tTSelect extends ReadonlyNonEmptyArray<string>,\n\tTPopulate extends ReadonlyNonEmptyArray<string>,\n\tTSearch extends ReadonlyNonEmptyArray<string>,\n> {\n\tselectableFields: TSelect;\n\tpopulatableFields: TPopulate;\n\tsearchableFields: TSearch;\n\tmaxSize?: number;\n\tdefaultSize?: number;\n}\n\nexport type PopulateObject<\n\tTPopulate extends ReadonlyNonEmptyArray<string>,\n\tTSelect extends ReadonlyNonEmptyArray<string>,\n> = {\n\tpath: TPopulate[number];\n\tselect?: Partial<Record<TSelect[number], 0 | 1>>;\n\tmatch?: Record<string, any>;\n\toptions?: { sort?: Record<string, 1 | -1>; limit?: number; skip?: number };\n\tpopulate?: PopulateObject<TPopulate, TSelect> | PopulateObject<TPopulate, TSelect>[];\n};\n\nexport type FilterDTO<\n\tTSelect extends ReadonlyNonEmptyArray<string>,\n\tTPopulate extends ReadonlyNonEmptyArray<string>,\n\tTSearch extends ReadonlyNonEmptyArray<string>,\n> = {\n\tsize?: number;\n\tpage?: number;\n\tsort?: Record<string, 1 | -1>;\n\tquery?: Record<string, any>;\n\tselect?: Partial<Record<TSelect[number], 0 | 1>>;\n\tpopulate?: PopulateObject<TPopulate, TSelect> | PopulateObject<TPopulate, TSelect>[];\n\tsearch?: { searchKey: string; searchFields?: TSearch[number][] };\n};\n\nexport function createFilterSchema<\n\tTSelect extends ReadonlyNonEmptyArray<string>,\n\tTPopulate extends ReadonlyNonEmptyArray<string>,\n\tTSearch extends ReadonlyNonEmptyArray<string>,\n>(config: FilterSchemaConfig<TSelect, TPopulate, TSearch>) {\n\tconst { selectableFields, populatableFields, searchableFields, maxSize = 100, defaultSize = 10 } = config;\n\n\tconst selectSchema = z.record(z.enum(selectableFields), z.union([z.literal(0), z.literal(1)]));\n\n\tconst populateSchema: z.ZodType<PopulateObject<TPopulate, TSelect>> = z.lazy(\n\t\t() =>\n\t\t\tz.object({\n\t\t\t\tpath: z.enum(populatableFields),\n\t\t\t\tselect: z.record(z.enum(selectableFields), z.union([z.literal(0), z.literal(1)])).optional(),\n\t\t\t\tmatch: z.record(z.string(), z.any()).optional(),\n\t\t\t\toptions: z\n\t\t\t\t\t.object({\n\t\t\t\t\t\tsort: sortSchema.optional(),\n\t\t\t\t\t\tlimit: z.number().optional(),\n\t\t\t\t\t\tskip: z.number().optional(),\n\t\t\t\t\t})\n\t\t\t\t\t.optional(),\n\t\t\t\tpopulate: z.union([populateSchema, z.array(populateSchema)]).optional(),\n\t\t\t}) as z.ZodType<PopulateObject<TPopulate, TSelect>>\n\t);\n\n\tconst searchSchema = z.object({\n\t\tsearchKey: z.string(),\n\t\tsearchFields: z.array(z.enum(searchableFields)).optional(),\n\t});\n\n\tconst schema = z.object({\n\t\tsize: z.number().min(1).max(maxSize).optional().default(defaultSize),\n\t\tpage: z.number().min(1).optional().default(1),\n\t\tsort: sortSchema.optional(),\n\t\tquery: MongoQuerySchema.optional(),\n\t\tselect: selectSchema.optional(),\n\t\tpopulate: z.union([populateSchema, z.array(populateSchema)]).optional(),\n\t\tsearch: searchSchema.optional(),\n\t});\n\n\treturn schema as z.ZodType<FilterDTO<TSelect, TPopulate, TSearch>>;\n}\n\n/**\n * @Example\n *\n * const UserFilterSchema = createFilterSchema({\n * \tselectableFields: ['_id', 'name', 'email', 'role', 'createdAt', 'updatedAt'] as const,\n * \tpopulatableFields: ['organization', 'permissions', 'addresses'] as const,\n * \tsearchableFields: ['name', 'email', 'role'] as const,\n * });\n *\n * type UserFilterDTO = z.infer<typeof UserFilterSchema>;\n *\n * const correctFilter: UserFilterDTO = {\n * \tselect: { name: 1, email: 1 },\n * \tpopulate: [{ path: 'organization', select: { name: 1 } }],\n * \tsearch: { searchKey: 'john', searchFields: ['name', 'email'] },\n * \tpage: 1,\n * \tsize: 20,\n * };\n *\n * const wrongFilter: UserFilterDTO = {\n * \tselect: { name: 1, email: 1, password: 1 }, Password is not in selectableFields\n * \tpopulate: [{ path: 'shops', select: { name: 1 } }], ❌ Shops is not in populatableFields\n * \tsearch: { searchKey: 'john', searchFields: ['name', 'email', 'price'] }, ❌ Price is not in searchableFields\n * \tpage: 1,\n * \tsize: 20,\n * };\n */\n"]}
1
+ {"version":3,"file":"filter-schema.factory.js","sourceRoot":"/","sources":["utilities/validation/filter-schema.factory.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from 'zod';\n\nconst sortSchema = z.record(z.string(), z.union([z.literal(1), z.literal(-1)]));\n\ntype ReadonlyNonEmptyArray<T> = readonly [T, ...T[]];\n\nexport interface FilterSchemaConfig<\n\tTSelect extends ReadonlyNonEmptyArray<string>,\n\tTPopulate extends ReadonlyNonEmptyArray<string>,\n\tTSearch extends ReadonlyNonEmptyArray<string>,\n\tTQuery extends ReadonlyNonEmptyArray<string> | [] = [],\n> {\n\tselectableFields: TSelect;\n\tpopulatableFields: TPopulate;\n\tsearchableFields: TSearch;\n\tqueryableFields?: TQuery;\n\tmaxSize?: number;\n\tdefaultSize?: number;\n}\n\nexport type PopulateObject<\n\tTPopulate extends ReadonlyNonEmptyArray<string>,\n\tTSelect extends ReadonlyNonEmptyArray<string>,\n> = {\n\tpath: TPopulate[number];\n\tselect?: Partial<Record<TSelect[number], 0 | 1>>;\n\tmatch?: Record<string, any>;\n\toptions?: { sort?: Record<string, 1 | -1>; limit?: number; skip?: number };\n\tpopulate?: PopulateObject<TPopulate, TSelect> | PopulateObject<TPopulate, TSelect>[];\n};\n\nexport type FilterDTO<\n\tTSelect extends ReadonlyNonEmptyArray<string>,\n\tTPopulate extends ReadonlyNonEmptyArray<string>,\n\tTSearch extends ReadonlyNonEmptyArray<string>,\n\tTQuery extends ReadonlyNonEmptyArray<string> | [] = [],\n> = {\n\tsize?: number;\n\tpage?: number;\n\tsort?: Record<string, 1 | -1>;\n\tquery?: TQuery extends ReadonlyNonEmptyArray<string>\n\t\t? Partial<Record<TQuery[number], any>>\n\t\t: Record<string, any>;\n\tselect?: Partial<Record<TSelect[number], 0 | 1>>;\n\tpopulate?: PopulateObject<TPopulate, TSelect> | PopulateObject<TPopulate, TSelect>[];\n\tsearch?: { searchKey: string; searchFields?: TSearch[number][] };\n};\n\nexport function createFilterSchema<\n\tTSelect extends ReadonlyNonEmptyArray<string>,\n\tTPopulate extends ReadonlyNonEmptyArray<string>,\n\tTSearch extends ReadonlyNonEmptyArray<string>,\n\tTQuery extends ReadonlyNonEmptyArray<string> | [] = [],\n>(config: FilterSchemaConfig<TSelect, TPopulate, TSearch, TQuery>) {\n\tconst {\n\t\tselectableFields,\n\t\tpopulatableFields,\n\t\tsearchableFields,\n\t\tqueryableFields,\n\t\tmaxSize = 100,\n\t\tdefaultSize = 10,\n\t} = config;\n\n\t// select schema\n\tconst selectSchema = z.record(z.enum(selectableFields), z.union([z.literal(0), z.literal(1)]));\n\n\t// populate schema\n\tconst populateSchema: z.ZodType<PopulateObject<TPopulate, TSelect>> = z.lazy(\n\t\t() =>\n\t\t\tz.object({\n\t\t\t\tpath: z.enum(populatableFields),\n\t\t\t\tselect: z.record(z.enum(selectableFields), z.union([z.literal(0), z.literal(1)])).optional(),\n\t\t\t\tmatch: z.record(z.string(), z.any()).optional(),\n\t\t\t\toptions: z\n\t\t\t\t\t.object({\n\t\t\t\t\t\tsort: sortSchema.optional(),\n\t\t\t\t\t\tlimit: z.number().optional(),\n\t\t\t\t\t\tskip: z.number().optional(),\n\t\t\t\t\t})\n\t\t\t\t\t.optional(),\n\t\t\t\tpopulate: z.union([populateSchema, z.array(populateSchema)]).optional(),\n\t\t\t}) as z.ZodType<PopulateObject<TPopulate, TSelect>>\n\t);\n\n\t// search schema\n\tconst searchSchema = z.object({\n\t\tsearchKey: z.string(),\n\t\tsearchFields: z.array(z.enum(searchableFields)).optional(),\n\t});\n\n\t// query schema\n\tconst querySchema =\n\t\tqueryableFields && queryableFields.length > 0\n\t\t\t? z.record(z.enum(queryableFields as ReadonlyNonEmptyArray<string>), z.any())\n\t\t\t: z.any();\n\n\tconst schema = z.object({\n\t\tsize: z.number().min(1).max(maxSize).optional().default(defaultSize),\n\t\tpage: z.number().min(1).optional().default(1),\n\t\tsort: sortSchema.optional(),\n\t\tquery: querySchema.optional(),\n\t\tselect: selectSchema.optional(),\n\t\tpopulate: z.union([populateSchema, z.array(populateSchema)]).optional(),\n\t\tsearch: searchSchema.optional(),\n\t});\n\n\treturn schema as z.ZodType<FilterDTO<TSelect, TPopulate, TSearch, TQuery>>;\n}\n\n/**\n *\n * @Example\n *\n * const UserFilterSchema = createFilterSchema({\n * \tselectableFields: ['_id', 'name', 'email', 'role'] as const,\n * \tpopulatableFields: ['organization'] as const,\n * \tsearchableFields: ['name', 'email'] as const,\n * \tqueryableFields: ['role', 'active'] as const,\n * });\n *\n * type UserFilterDTO = z.infer<typeof UserFilterSchema>;\n *\n * ✅ Correct\n * const correctFilter: UserFilterDTO = {\n * \tselect: { name: 1, email: 1 },\n * \tquery: { role: 'admin', active: true },\n * \tpopulate: [{ path: 'organization', select: { name: 1 } }],\n * \tsearch: { searchKey: 'john', searchFields: ['name', 'email'] },\n * \tpage: 1,\n * \tsize: 20,\n * };\n *\n * ❌ TypeScript will catch errors\n * const wrongFilter: UserFilterDTO = {\n * \tselect: { name: 1, email: 1, password: 1 },\n * \tquery: { status: true },\n * \tpopulate: [{ path: 'shops', select: { name: 1 } }],\n * \tsearch: { searchKey: 'john', searchFields: ['name', 'email', 'price'] },\n * \tpage: 1,\n * \tsize: 20,\n * };\n */\n"]}