@lyxa.ai/core 1.1.55 → 1.1.56
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/dist/types/README.md +1 -1
- package/dist/types/package.json +1 -1
- package/dist/types/utilities/validation/filter-schema.factory.d.ts +7 -5
- package/dist/types/utilities/validation/filter-schema.factory.js +6 -2
- package/dist/types/utilities/validation/filter-schema.factory.js.map +1 -1
- package/dist/utilities/validation/filter-schema.factory.d.ts +7 -5
- package/dist/utilities/validation/filter-schema.factory.js +6 -2
- package/dist/utilities/validation/filter-schema.factory.js.map +1 -1
- package/package.json +1 -1
package/dist/types/README.md
CHANGED
package/dist/types/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
1
|
+
import { z, ZodTypeAny } from 'zod';
|
|
2
2
|
type ReadonlyNonEmptyArray<T> = readonly [T, ...T[]];
|
|
3
|
-
export interface FilterSchemaConfig<TSelect extends ReadonlyNonEmptyArray<string> | [] = [], TPopulate extends Record<string, ReadonlyNonEmptyArray<string>> | {} = {}, TSearch extends ReadonlyNonEmptyArray<string> | [] = [], TQuery extends
|
|
3
|
+
export interface FilterSchemaConfig<TSelect extends ReadonlyNonEmptyArray<string> | [] = [], TPopulate extends Record<string, ReadonlyNonEmptyArray<string>> | {} = {}, TSearch extends ReadonlyNonEmptyArray<string> | [] = [], TQuery extends Record<string, ZodTypeAny> = {}> {
|
|
4
4
|
selectableFields?: TSelect;
|
|
5
5
|
populatableFields?: TPopulate;
|
|
6
6
|
searchableFields?: TSearch;
|
|
@@ -19,11 +19,10 @@ export type PopulateObject<TPath extends string, TSelect extends ReadonlyNonEmpt
|
|
|
19
19
|
};
|
|
20
20
|
populate?: never;
|
|
21
21
|
};
|
|
22
|
-
export type FilterDTO<TSelect extends ReadonlyNonEmptyArray<string> | [] = [], TPopulate extends ReadonlyNonEmptyArray<string> | [] = [], TSearch extends ReadonlyNonEmptyArray<string> | [] = [], TQuery extends
|
|
22
|
+
export type FilterDTO<TSelect extends ReadonlyNonEmptyArray<string> | [] = [], TPopulate extends ReadonlyNonEmptyArray<string> | [] = [], TSearch extends ReadonlyNonEmptyArray<string> | [] = [], TQuery extends Record<string, ZodTypeAny> = {}> = {
|
|
23
23
|
size?: number;
|
|
24
24
|
page?: number;
|
|
25
25
|
sort?: Record<string, 1 | -1>;
|
|
26
|
-
query?: TQuery extends ReadonlyNonEmptyArray<string> ? Partial<Record<TQuery[number], any>> : never;
|
|
27
26
|
select?: TSelect extends ReadonlyNonEmptyArray<string> ? Partial<Record<TSelect[number], 0 | 1>> : never;
|
|
28
27
|
populate?: TPopulate extends ReadonlyNonEmptyArray<string> ? {
|
|
29
28
|
[P in TPopulate[number]]: PopulateObject<P, Extract<TSelect, ReadonlyNonEmptyArray<string>>>;
|
|
@@ -34,6 +33,9 @@ export type FilterDTO<TSelect extends ReadonlyNonEmptyArray<string> | [] = [], T
|
|
|
34
33
|
searchKey: string;
|
|
35
34
|
searchFields?: TSearch[number][];
|
|
36
35
|
} : never;
|
|
36
|
+
query?: {
|
|
37
|
+
[K in keyof TQuery]?: z.infer<TQuery[K]>;
|
|
38
|
+
};
|
|
37
39
|
};
|
|
38
|
-
export declare function createFilterSchema<TSelect extends ReadonlyNonEmptyArray<string> | [] = [], TPopulate extends Record<string, ReadonlyNonEmptyArray<string>> | {} = {}, TSearch extends ReadonlyNonEmptyArray<string> | [] = [], TQuery extends
|
|
40
|
+
export declare function createFilterSchema<TSelect extends ReadonlyNonEmptyArray<string> | [] = [], TPopulate extends Record<string, ReadonlyNonEmptyArray<string>> | {} = {}, TSearch extends ReadonlyNonEmptyArray<string> | [] = [], TQuery extends Record<string, ZodTypeAny> = {}>(config: FilterSchemaConfig<TSelect, TPopulate, TSearch, TQuery>): z.ZodType<FilterDTO<TSelect, keyof TPopulate extends never ? [] : readonly [keyof TPopulate & string, ...(keyof TPopulate & string)[]], TSearch, TQuery>>;
|
|
39
41
|
export {};
|
|
@@ -58,8 +58,12 @@ function createFilterSchema(config) {
|
|
|
58
58
|
})
|
|
59
59
|
.optional();
|
|
60
60
|
}
|
|
61
|
-
if (queryableFields
|
|
62
|
-
|
|
61
|
+
if (queryableFields && Object.keys(queryableFields).length) {
|
|
62
|
+
const queryShape = {};
|
|
63
|
+
for (const key in queryableFields) {
|
|
64
|
+
queryShape[key] = queryableFields[key].optional();
|
|
65
|
+
}
|
|
66
|
+
schemaShape.query = zod_1.z.object(queryShape).optional();
|
|
63
67
|
}
|
|
64
68
|
return zod_1.z.object(schemaShape);
|
|
65
69
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filter-schema.factory.js","sourceRoot":"/","sources":["utilities/validation/filter-schema.factory.ts"],"names":[],"mappings":";;AAuDA,
|
|
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, ZodTypeAny } from 'zod';\n\ntype ReadonlyNonEmptyArray<T> = readonly [T, ...T[]];\n\nexport interface FilterSchemaConfig<\n\tTSelect extends ReadonlyNonEmptyArray<string> | [] = [],\n\tTPopulate extends Record<string, ReadonlyNonEmptyArray<string>> | {} = {},\n\tTSearch extends ReadonlyNonEmptyArray<string> | [] = [],\n\tTQuery extends Record<string, ZodTypeAny> = {},\n> {\n\tselectableFields?: TSelect;\n\tpopulatableFields?: TPopulate;\n\tsearchableFields?: TSearch;\n\tqueryableFields?: TQuery;\n\tmaxSize?: number;\n\tdefaultSize?: number;\n}\n\nexport type PopulateObject<TPath extends string, TSelect extends ReadonlyNonEmptyArray<string>> = {\n\tpath: TPath;\n\tselect: Partial<Record<TSelect[number], 0 | 1>>;\n\tmatch?: Partial<Record<TSelect[number], any>>;\n\toptions?: { sort?: Record<string, 1 | -1>; limit?: number; skip?: number };\n\tpopulate?: never;\n};\n\nexport type FilterDTO<\n\tTSelect extends ReadonlyNonEmptyArray<string> | [] = [],\n\tTPopulate extends ReadonlyNonEmptyArray<string> | [] = [],\n\tTSearch extends ReadonlyNonEmptyArray<string> | [] = [],\n\tTQuery extends Record<string, ZodTypeAny> = {},\n> = {\n\tsize?: number;\n\tpage?: number;\n\tsort?: Record<string, 1 | -1>;\n\tselect?: TSelect extends ReadonlyNonEmptyArray<string> ? Partial<Record<TSelect[number], 0 | 1>> : never;\n\tpopulate?: TPopulate extends ReadonlyNonEmptyArray<string>\n\t\t?\n\t\t\t\t| {\n\t\t\t\t\t\t[P in TPopulate[number]]: PopulateObject<P, Extract<TSelect, ReadonlyNonEmptyArray<string>>>;\n\t\t\t\t }[TPopulate[number]]\n\t\t\t\t| Array<\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t[P in TPopulate[number]]: PopulateObject<P, Extract<TSelect, ReadonlyNonEmptyArray<string>>>;\n\t\t\t\t\t\t}[TPopulate[number]]\n\t\t\t\t >\n\t\t: never;\n\tsearch?: TSearch extends ReadonlyNonEmptyArray<string>\n\t\t? { searchKey: string; searchFields?: TSearch[number][] }\n\t\t: never;\n\tquery?: { [K in keyof TQuery]?: z.infer<TQuery[K]> };\n};\n\nconst sortSchema = z.record(z.string(), z.union([z.literal(1), z.literal(-1)]));\n\nexport function createFilterSchema<\n\tTSelect extends ReadonlyNonEmptyArray<string> | [] = [],\n\tTPopulate extends Record<string, ReadonlyNonEmptyArray<string>> | {} = {},\n\tTSearch extends ReadonlyNonEmptyArray<string> | [] = [],\n\tTQuery extends Record<string, ZodTypeAny> = {},\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\tconst populateKeys = populatableFields ? (Object.keys(populatableFields) as (keyof TPopulate)[]) : [];\n\n\tconst schemaShape: Record<string, any> = {\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};\n\n\t// Select\n\tif (selectableFields?.length) {\n\t\tschemaShape.select = z\n\t\t\t.record(z.enum([...selectableFields] as [string, ...string[]]), z.union([z.literal(0), z.literal(1)]))\n\t\t\t.optional();\n\t}\n\n\t// Populate\n\tif (populateKeys.length) {\n\t\tconst populateSchemaMap: Record<string, ZodTypeAny> = {};\n\n\t\tfor (const path of populateKeys) {\n\t\t\tconst allowedSelects = populatableFields![path] as ReadonlyNonEmptyArray<string>;\n\n\t\t\tpopulateSchemaMap[path as string] = z.lazy(() =>\n\t\t\t\tz.object({\n\t\t\t\t\tpath: z.literal(path as string),\n\t\t\t\t\tselect: z\n\t\t\t\t\t\t.record(\n\t\t\t\t\t\t\tz.enum([...allowedSelects] as [string, ...string[]]),\n\t\t\t\t\t\t\tz.union([z.literal(0), z.literal(1)])\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.optional(),\n\t\t\t\t\tmatch: z.record(z.enum([...allowedSelects] as [string, ...string[]]), z.any()).optional(),\n\t\t\t\t\toptions: z\n\t\t\t\t\t\t.object({\n\t\t\t\t\t\t\tsort: sortSchema.optional(),\n\t\t\t\t\t\t\tlimit: z.number().optional(),\n\t\t\t\t\t\t\tskip: z.number().optional(),\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.optional(),\n\t\t\t\t\tpopulate: z.never(),\n\t\t\t\t})\n\t\t\t) as ZodTypeAny;\n\t\t}\n\n\t\tconst populateSchemas = Object.values(populateSchemaMap);\n\n\t\tif (populateSchemas.length > 0) {\n\t\t\tlet unionSchema: ZodTypeAny;\n\t\t\tif (populateSchemas.length === 1) {\n\t\t\t\tunionSchema = z.union([populateSchemas[0], populateSchemas[0]]);\n\t\t\t} else {\n\t\t\t\tunionSchema = z.union(populateSchemas as [ZodTypeAny, ZodTypeAny, ...ZodTypeAny[]]);\n\t\t\t}\n\t\t\tschemaShape.populate = z\n\t\t\t\t.union([unionSchema, z.array(unionSchema)] as [ZodTypeAny, ZodTypeAny, ...ZodTypeAny[]])\n\t\t\t\t.optional();\n\t\t}\n\t}\n\n\t// Search\n\tif (searchableFields?.length) {\n\t\tschemaShape.search = z\n\t\t\t.object({\n\t\t\t\tsearchKey: z.string(),\n\t\t\t\tsearchFields: z.array(z.enum([...searchableFields] as [string, ...string[]])).optional(),\n\t\t\t})\n\t\t\t.optional();\n\t}\n\n\t// Query\n\tif (queryableFields && Object.keys(queryableFields).length) {\n\t\tconst queryShape: Record<string, ZodTypeAny> = {};\n\t\tfor (const key in queryableFields) {\n\t\t\tqueryShape[key] = queryableFields[key].optional();\n\t\t}\n\t\tschemaShape.query = z.object(queryShape).optional();\n\t}\n\n\ttype KeysToReadonlyNonEmptyArray<T> = keyof T extends never\n\t\t? []\n\t\t: readonly [keyof T & string, ...(keyof T & string)[]];\n\n\treturn z.object(schemaShape) as z.ZodType<\n\t\tFilterDTO<TSelect, KeysToReadonlyNonEmptyArray<TPopulate>, TSearch, TQuery>\n\t>;\n}\n\n/**\n *\n *\n * const UserFilterSchema = createFilterSchema({\n * \tselectableFields: ['_id', 'name', 'email', 'role'] as const,\n * \tpopulatableFields: { organization: ['_id', 'name', 'address'] as const },\n * \tsearchableFields: ['name', 'email'] as const,\n * \tqueryableFields: {\n * \t\trole: z.string(),\n * \t\tactive: z.boolean(),\n * \t},\n * });\n *\n * type UserFilter = z.infer<typeof UserFilterSchema>;\n *\n * const validFilter: UserFilter = {\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 reject invalid assignments\n * const invalidFilter: UserFilter = {\n * \tselect: { password: 1 }, // ❌ not allowed\n * \tquery: { role: 123, active: 'yes' }, // ❌ not allowed\n * \tpopulate: { path: 'organization', select: { unknown: 1 } }, // ❌ not allowed\n * \tsearch: { searchKey: 'x', searchFields: ['unknown'] }, // ❌ not allowed\n * };\n *\n */\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
1
|
+
import { z, ZodTypeAny } from 'zod';
|
|
2
2
|
type ReadonlyNonEmptyArray<T> = readonly [T, ...T[]];
|
|
3
|
-
export interface FilterSchemaConfig<TSelect extends ReadonlyNonEmptyArray<string> | [] = [], TPopulate extends Record<string, ReadonlyNonEmptyArray<string>> | {} = {}, TSearch extends ReadonlyNonEmptyArray<string> | [] = [], TQuery extends
|
|
3
|
+
export interface FilterSchemaConfig<TSelect extends ReadonlyNonEmptyArray<string> | [] = [], TPopulate extends Record<string, ReadonlyNonEmptyArray<string>> | {} = {}, TSearch extends ReadonlyNonEmptyArray<string> | [] = [], TQuery extends Record<string, ZodTypeAny> = {}> {
|
|
4
4
|
selectableFields?: TSelect;
|
|
5
5
|
populatableFields?: TPopulate;
|
|
6
6
|
searchableFields?: TSearch;
|
|
@@ -19,11 +19,10 @@ export type PopulateObject<TPath extends string, TSelect extends ReadonlyNonEmpt
|
|
|
19
19
|
};
|
|
20
20
|
populate?: never;
|
|
21
21
|
};
|
|
22
|
-
export type FilterDTO<TSelect extends ReadonlyNonEmptyArray<string> | [] = [], TPopulate extends ReadonlyNonEmptyArray<string> | [] = [], TSearch extends ReadonlyNonEmptyArray<string> | [] = [], TQuery extends
|
|
22
|
+
export type FilterDTO<TSelect extends ReadonlyNonEmptyArray<string> | [] = [], TPopulate extends ReadonlyNonEmptyArray<string> | [] = [], TSearch extends ReadonlyNonEmptyArray<string> | [] = [], TQuery extends Record<string, ZodTypeAny> = {}> = {
|
|
23
23
|
size?: number;
|
|
24
24
|
page?: number;
|
|
25
25
|
sort?: Record<string, 1 | -1>;
|
|
26
|
-
query?: TQuery extends ReadonlyNonEmptyArray<string> ? Partial<Record<TQuery[number], any>> : never;
|
|
27
26
|
select?: TSelect extends ReadonlyNonEmptyArray<string> ? Partial<Record<TSelect[number], 0 | 1>> : never;
|
|
28
27
|
populate?: TPopulate extends ReadonlyNonEmptyArray<string> ? {
|
|
29
28
|
[P in TPopulate[number]]: PopulateObject<P, Extract<TSelect, ReadonlyNonEmptyArray<string>>>;
|
|
@@ -34,6 +33,9 @@ export type FilterDTO<TSelect extends ReadonlyNonEmptyArray<string> | [] = [], T
|
|
|
34
33
|
searchKey: string;
|
|
35
34
|
searchFields?: TSearch[number][];
|
|
36
35
|
} : never;
|
|
36
|
+
query?: {
|
|
37
|
+
[K in keyof TQuery]?: z.infer<TQuery[K]>;
|
|
38
|
+
};
|
|
37
39
|
};
|
|
38
|
-
export declare function createFilterSchema<TSelect extends ReadonlyNonEmptyArray<string> | [] = [], TPopulate extends Record<string, ReadonlyNonEmptyArray<string>> | {} = {}, TSearch extends ReadonlyNonEmptyArray<string> | [] = [], TQuery extends
|
|
40
|
+
export declare function createFilterSchema<TSelect extends ReadonlyNonEmptyArray<string> | [] = [], TPopulate extends Record<string, ReadonlyNonEmptyArray<string>> | {} = {}, TSearch extends ReadonlyNonEmptyArray<string> | [] = [], TQuery extends Record<string, ZodTypeAny> = {}>(config: FilterSchemaConfig<TSelect, TPopulate, TSearch, TQuery>): z.ZodType<FilterDTO<TSelect, keyof TPopulate extends never ? [] : readonly [keyof TPopulate & string, ...(keyof TPopulate & string)[]], TSearch, TQuery>>;
|
|
39
41
|
export {};
|
|
@@ -58,8 +58,12 @@ function createFilterSchema(config) {
|
|
|
58
58
|
})
|
|
59
59
|
.optional();
|
|
60
60
|
}
|
|
61
|
-
if (queryableFields
|
|
62
|
-
|
|
61
|
+
if (queryableFields && Object.keys(queryableFields).length) {
|
|
62
|
+
const queryShape = {};
|
|
63
|
+
for (const key in queryableFields) {
|
|
64
|
+
queryShape[key] = queryableFields[key].optional();
|
|
65
|
+
}
|
|
66
|
+
schemaShape.query = zod_1.z.object(queryShape).optional();
|
|
63
67
|
}
|
|
64
68
|
return zod_1.z.object(schemaShape);
|
|
65
69
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filter-schema.factory.js","sourceRoot":"/","sources":["utilities/validation/filter-schema.factory.ts"],"names":[],"mappings":";;AAuDA,
|
|
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, ZodTypeAny } from 'zod';\n\ntype ReadonlyNonEmptyArray<T> = readonly [T, ...T[]];\n\nexport interface FilterSchemaConfig<\n\tTSelect extends ReadonlyNonEmptyArray<string> | [] = [],\n\tTPopulate extends Record<string, ReadonlyNonEmptyArray<string>> | {} = {},\n\tTSearch extends ReadonlyNonEmptyArray<string> | [] = [],\n\tTQuery extends Record<string, ZodTypeAny> = {},\n> {\n\tselectableFields?: TSelect;\n\tpopulatableFields?: TPopulate;\n\tsearchableFields?: TSearch;\n\tqueryableFields?: TQuery;\n\tmaxSize?: number;\n\tdefaultSize?: number;\n}\n\nexport type PopulateObject<TPath extends string, TSelect extends ReadonlyNonEmptyArray<string>> = {\n\tpath: TPath;\n\tselect: Partial<Record<TSelect[number], 0 | 1>>;\n\tmatch?: Partial<Record<TSelect[number], any>>;\n\toptions?: { sort?: Record<string, 1 | -1>; limit?: number; skip?: number };\n\tpopulate?: never;\n};\n\nexport type FilterDTO<\n\tTSelect extends ReadonlyNonEmptyArray<string> | [] = [],\n\tTPopulate extends ReadonlyNonEmptyArray<string> | [] = [],\n\tTSearch extends ReadonlyNonEmptyArray<string> | [] = [],\n\tTQuery extends Record<string, ZodTypeAny> = {},\n> = {\n\tsize?: number;\n\tpage?: number;\n\tsort?: Record<string, 1 | -1>;\n\tselect?: TSelect extends ReadonlyNonEmptyArray<string> ? Partial<Record<TSelect[number], 0 | 1>> : never;\n\tpopulate?: TPopulate extends ReadonlyNonEmptyArray<string>\n\t\t?\n\t\t\t\t| {\n\t\t\t\t\t\t[P in TPopulate[number]]: PopulateObject<P, Extract<TSelect, ReadonlyNonEmptyArray<string>>>;\n\t\t\t\t }[TPopulate[number]]\n\t\t\t\t| Array<\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t[P in TPopulate[number]]: PopulateObject<P, Extract<TSelect, ReadonlyNonEmptyArray<string>>>;\n\t\t\t\t\t\t}[TPopulate[number]]\n\t\t\t\t >\n\t\t: never;\n\tsearch?: TSearch extends ReadonlyNonEmptyArray<string>\n\t\t? { searchKey: string; searchFields?: TSearch[number][] }\n\t\t: never;\n\tquery?: { [K in keyof TQuery]?: z.infer<TQuery[K]> };\n};\n\nconst sortSchema = z.record(z.string(), z.union([z.literal(1), z.literal(-1)]));\n\nexport function createFilterSchema<\n\tTSelect extends ReadonlyNonEmptyArray<string> | [] = [],\n\tTPopulate extends Record<string, ReadonlyNonEmptyArray<string>> | {} = {},\n\tTSearch extends ReadonlyNonEmptyArray<string> | [] = [],\n\tTQuery extends Record<string, ZodTypeAny> = {},\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\tconst populateKeys = populatableFields ? (Object.keys(populatableFields) as (keyof TPopulate)[]) : [];\n\n\tconst schemaShape: Record<string, any> = {\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};\n\n\t// Select\n\tif (selectableFields?.length) {\n\t\tschemaShape.select = z\n\t\t\t.record(z.enum([...selectableFields] as [string, ...string[]]), z.union([z.literal(0), z.literal(1)]))\n\t\t\t.optional();\n\t}\n\n\t// Populate\n\tif (populateKeys.length) {\n\t\tconst populateSchemaMap: Record<string, ZodTypeAny> = {};\n\n\t\tfor (const path of populateKeys) {\n\t\t\tconst allowedSelects = populatableFields![path] as ReadonlyNonEmptyArray<string>;\n\n\t\t\tpopulateSchemaMap[path as string] = z.lazy(() =>\n\t\t\t\tz.object({\n\t\t\t\t\tpath: z.literal(path as string),\n\t\t\t\t\tselect: z\n\t\t\t\t\t\t.record(\n\t\t\t\t\t\t\tz.enum([...allowedSelects] as [string, ...string[]]),\n\t\t\t\t\t\t\tz.union([z.literal(0), z.literal(1)])\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.optional(),\n\t\t\t\t\tmatch: z.record(z.enum([...allowedSelects] as [string, ...string[]]), z.any()).optional(),\n\t\t\t\t\toptions: z\n\t\t\t\t\t\t.object({\n\t\t\t\t\t\t\tsort: sortSchema.optional(),\n\t\t\t\t\t\t\tlimit: z.number().optional(),\n\t\t\t\t\t\t\tskip: z.number().optional(),\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.optional(),\n\t\t\t\t\tpopulate: z.never(),\n\t\t\t\t})\n\t\t\t) as ZodTypeAny;\n\t\t}\n\n\t\tconst populateSchemas = Object.values(populateSchemaMap);\n\n\t\tif (populateSchemas.length > 0) {\n\t\t\tlet unionSchema: ZodTypeAny;\n\t\t\tif (populateSchemas.length === 1) {\n\t\t\t\tunionSchema = z.union([populateSchemas[0], populateSchemas[0]]);\n\t\t\t} else {\n\t\t\t\tunionSchema = z.union(populateSchemas as [ZodTypeAny, ZodTypeAny, ...ZodTypeAny[]]);\n\t\t\t}\n\t\t\tschemaShape.populate = z\n\t\t\t\t.union([unionSchema, z.array(unionSchema)] as [ZodTypeAny, ZodTypeAny, ...ZodTypeAny[]])\n\t\t\t\t.optional();\n\t\t}\n\t}\n\n\t// Search\n\tif (searchableFields?.length) {\n\t\tschemaShape.search = z\n\t\t\t.object({\n\t\t\t\tsearchKey: z.string(),\n\t\t\t\tsearchFields: z.array(z.enum([...searchableFields] as [string, ...string[]])).optional(),\n\t\t\t})\n\t\t\t.optional();\n\t}\n\n\t// Query\n\tif (queryableFields && Object.keys(queryableFields).length) {\n\t\tconst queryShape: Record<string, ZodTypeAny> = {};\n\t\tfor (const key in queryableFields) {\n\t\t\tqueryShape[key] = queryableFields[key].optional();\n\t\t}\n\t\tschemaShape.query = z.object(queryShape).optional();\n\t}\n\n\ttype KeysToReadonlyNonEmptyArray<T> = keyof T extends never\n\t\t? []\n\t\t: readonly [keyof T & string, ...(keyof T & string)[]];\n\n\treturn z.object(schemaShape) as z.ZodType<\n\t\tFilterDTO<TSelect, KeysToReadonlyNonEmptyArray<TPopulate>, TSearch, TQuery>\n\t>;\n}\n\n/**\n *\n *\n * const UserFilterSchema = createFilterSchema({\n * \tselectableFields: ['_id', 'name', 'email', 'role'] as const,\n * \tpopulatableFields: { organization: ['_id', 'name', 'address'] as const },\n * \tsearchableFields: ['name', 'email'] as const,\n * \tqueryableFields: {\n * \t\trole: z.string(),\n * \t\tactive: z.boolean(),\n * \t},\n * });\n *\n * type UserFilter = z.infer<typeof UserFilterSchema>;\n *\n * const validFilter: UserFilter = {\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 reject invalid assignments\n * const invalidFilter: UserFilter = {\n * \tselect: { password: 1 }, // ❌ not allowed\n * \tquery: { role: 123, active: 'yes' }, // ❌ not allowed\n * \tpopulate: { path: 'organization', select: { unknown: 1 } }, // ❌ not allowed\n * \tsearch: { searchKey: 'x', searchFields: ['unknown'] }, // ❌ not allowed\n * };\n *\n */\n"]}
|