@lyxa.ai/types 1.1.67 → 1.1.69
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
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
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 Record<string, ZodTypeAny> = {}> {
|
|
3
|
+
export interface FilterSchemaConfig<TSelect extends ReadonlyNonEmptyArray<string> | [] = [], TPopulate extends Record<string, ReadonlyNonEmptyArray<string>> | {} = {}, TSearch extends ReadonlyNonEmptyArray<string> | [] = [], TQuery extends Record<string, ZodTypeAny> = {}, TSort extends ReadonlyNonEmptyArray<string> | [] = []> {
|
|
4
4
|
selectableFields?: TSelect;
|
|
5
5
|
populatableFields?: TPopulate;
|
|
6
6
|
searchableFields?: TSearch;
|
|
7
7
|
queryableFields?: TQuery;
|
|
8
|
+
sortableFields?: TSort;
|
|
8
9
|
maxSize?: number;
|
|
9
10
|
defaultSize?: number;
|
|
10
11
|
}
|
|
@@ -21,10 +22,10 @@ export type PopulateObject<TPath extends string, TSelectFields extends ReadonlyN
|
|
|
21
22
|
type PopulateUnion<TPopulate extends Record<string, ReadonlyNonEmptyArray<string>>> = {
|
|
22
23
|
[K in keyof TPopulate]: PopulateObject<K & string, TPopulate[K]>;
|
|
23
24
|
}[keyof TPopulate];
|
|
24
|
-
export type FilterDTO<TSelect extends ReadonlyNonEmptyArray<string> | [] = [], TPopulate extends Record<string, ReadonlyNonEmptyArray<string>> | {} = {}, TSearch extends ReadonlyNonEmptyArray<string> | [] = [], TQuery extends Record<string, ZodTypeAny> = {}> = {
|
|
25
|
+
export type FilterDTO<TSelect extends ReadonlyNonEmptyArray<string> | [] = [], TPopulate extends Record<string, ReadonlyNonEmptyArray<string>> | {} = {}, TSearch extends ReadonlyNonEmptyArray<string> | [] = [], TQuery extends Record<string, ZodTypeAny> = {}, TSort extends ReadonlyNonEmptyArray<string> | [] = []> = {
|
|
25
26
|
size?: number;
|
|
26
27
|
page?: number;
|
|
27
|
-
sort?: Record<
|
|
28
|
+
sort?: TSort extends ReadonlyNonEmptyArray<string> ? Partial<Record<TSort[number], 1 | -1>> : never;
|
|
28
29
|
select?: TSelect extends ReadonlyNonEmptyArray<string> ? Partial<Record<TSelect[number], 0 | 1>> : never;
|
|
29
30
|
populate?: PopulateUnion<TPopulate>[];
|
|
30
31
|
search?: TSearch extends ReadonlyNonEmptyArray<string> ? {
|
|
@@ -35,5 +36,5 @@ export type FilterDTO<TSelect extends ReadonlyNonEmptyArray<string> | [] = [], T
|
|
|
35
36
|
[K in keyof TQuery]?: z.infer<TQuery[K]>;
|
|
36
37
|
};
|
|
37
38
|
};
|
|
38
|
-
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, TPopulate, TSearch, TQuery>>;
|
|
39
|
+
export declare function createFilterSchema<TSelect extends ReadonlyNonEmptyArray<string> | [] = [], TPopulate extends Record<string, ReadonlyNonEmptyArray<string>> | {} = {}, TSearch extends ReadonlyNonEmptyArray<string> | [] = [], TQuery extends Record<string, ZodTypeAny> = {}, TSort extends ReadonlyNonEmptyArray<string> | [] = []>(config: FilterSchemaConfig<TSelect, TPopulate, TSearch, TQuery, TSort>): z.ZodType<FilterDTO<TSelect, TPopulate, TSearch, TQuery, TSort>>;
|
|
39
40
|
export {};
|
|
@@ -4,13 +4,17 @@ 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
6
|
function createFilterSchema(config) {
|
|
7
|
-
const { selectableFields, populatableFields, searchableFields, queryableFields, maxSize = 100, defaultSize = 10, } = config;
|
|
7
|
+
const { selectableFields, populatableFields, searchableFields, queryableFields, sortableFields, maxSize = 100, defaultSize = 10, } = config;
|
|
8
8
|
const populateKeys = populatableFields ? Object.keys(populatableFields) : [];
|
|
9
9
|
const schemaShape = {
|
|
10
10
|
size: zod_1.z.number().min(1).max(maxSize).optional().default(defaultSize),
|
|
11
11
|
page: zod_1.z.number().min(1).optional().default(1),
|
|
12
|
-
sort: sortSchema.optional(),
|
|
13
12
|
};
|
|
13
|
+
if (sortableFields?.length) {
|
|
14
|
+
schemaShape.sort = zod_1.z
|
|
15
|
+
.record(zod_1.z.enum([...sortableFields]), zod_1.z.union([zod_1.z.literal(1), zod_1.z.literal(-1)]))
|
|
16
|
+
.optional();
|
|
17
|
+
}
|
|
14
18
|
if (selectableFields?.length) {
|
|
15
19
|
schemaShape.select = zod_1.z
|
|
16
20
|
.record(zod_1.z.enum([...selectableFields]), zod_1.z.union([zod_1.z.literal(0), zod_1.z.literal(1)]))
|
|
@@ -59,14 +63,7 @@ function createFilterSchema(config) {
|
|
|
59
63
|
return zod_1.z.object(schemaShape);
|
|
60
64
|
}
|
|
61
65
|
const TicketFilterSchema = createFilterSchema({
|
|
62
|
-
selectableFields: [
|
|
63
|
-
'_id',
|
|
64
|
-
'ticketId',
|
|
65
|
-
'type',
|
|
66
|
-
'status',
|
|
67
|
-
'createdAt',
|
|
68
|
-
'updatedAt',
|
|
69
|
-
],
|
|
66
|
+
selectableFields: ['_id', 'ticketId', 'type', 'status', 'createdAt', 'updatedAt'],
|
|
70
67
|
populatableFields: {
|
|
71
68
|
chatroom: ['_id', 'lastMessage', 'participants', 'unreadCount'],
|
|
72
69
|
client: ['_id', 'name', 'profilePhoto', 'type'],
|
|
@@ -74,6 +71,7 @@ const TicketFilterSchema = createFilterSchema({
|
|
|
74
71
|
order: ['_id', 'orderId', 'shop', 'total'],
|
|
75
72
|
},
|
|
76
73
|
searchableFields: ['ticketId'],
|
|
74
|
+
sortableFields: ['createdAt', 'updatedAt', 'ticketId', 'status'],
|
|
77
75
|
queryableFields: {
|
|
78
76
|
status: zod_1.z.enum(['OPEN', 'CLOSED', 'PENDING']),
|
|
79
77
|
type: zod_1.z.enum(['ORDER', 'GENERAL', 'COMPLAINT']),
|
|
@@ -81,5 +79,7 @@ const TicketFilterSchema = createFilterSchema({
|
|
|
81
79
|
clientType: zod_1.z.enum(['USER', 'SHOP']),
|
|
82
80
|
},
|
|
83
81
|
});
|
|
84
|
-
const ticket = {
|
|
82
|
+
const ticket = {
|
|
83
|
+
sort: { createdAt: -1, status: 1 },
|
|
84
|
+
};
|
|
85
85
|
//# sourceMappingURL=filter-schema.factory.js.map
|
|
@@ -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, 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\n// Populate object type\nexport type PopulateObject<\n\tTPath extends string,\n\tTSelectFields extends ReadonlyNonEmptyArray<string>\n> = {\n\tpath: TPath;\n\tselect?: Partial<Record<TSelectFields[number], 0 | 1>>;\n\tmatch?: Record<string, any>;\n\toptions?: { sort?: Record<string, 1 | -1>; limit?: number; skip?: number };\n};\n\n// Union of all possible populate objects based on populatableFields\ntype PopulateUnion<TPopulate extends Record<string, ReadonlyNonEmptyArray<string>>> = {\n\t[K in keyof TPopulate]: PopulateObject<K & string, TPopulate[K]>;\n}[keyof TPopulate];\n\n// DTO type\nexport type FilterDTO<\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\tsize?: number;\n\tpage?: number;\n\tsort?: Record<string, 1 | -1>;\n\tselect?: TSelect extends ReadonlyNonEmptyArray<string>\n\t\t? Partial<Record<TSelect[number], 0 | 1>>\n\t\t: never;\n\tpopulate?: PopulateUnion<TPopulate>[];\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// Top-level select\n\tif (selectableFields?.length) {\n\t\tschemaShape.select = z\n\t\t\t.record(\n\t\t\t\tz.enum([...selectableFields] as [string, ...string[]]),\n\t\t\t\tz.union([z.literal(0), z.literal(1)]),\n\t\t\t)\n\t\t\t.optional();\n\t}\n\n\t// Populate as array of objects with strict typing per path\n\tif (populateKeys.length) {\n\t\tconst populateSchemas: ZodTypeAny[] = [];\n\n\t\tfor (const path of populateKeys) {\n\t\t\tconst allowedSelects = populatableFields![path] as ReadonlyNonEmptyArray<string>;\n\n\t\t\tconst populateSchema = z.object({\n\t\t\t\tpath: z.literal(path as string),\n\t\t\t\tselect: z\n\t\t\t\t\t.record(z.enum([...allowedSelects] as [string, ...string[]]), z.union([z.literal(0), z.literal(1)]))\n\t\t\t\t\t.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});\n\n\t\t\tpopulateSchemas.push(populateSchema);\n\t\t}\n\n\t\tconst unionSchema =\n\t\t\tpopulateSchemas.length === 1\n\t\t\t\t? populateSchemas[0]\n\t\t\t\t: z.union(populateSchemas as [ZodTypeAny, ZodTypeAny, ...ZodTypeAny[]]);\n\n\t\tschemaShape.populate = z.array(unionSchema).optional();\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\treturn z.object(schemaShape) as z.ZodType<FilterDTO<TSelect, TPopulate, TSearch, TQuery>>;\n}\n\n\nconst TicketFilterSchema = createFilterSchema({\n\t// Top-level document fields that can be selected\n\tselectableFields: [\n\t\t'_id',\n\t\t'ticketId',\n\t\t'type',\n\t\t'status',\n\t\t'createdAt',\n\t\t'updatedAt',\n\t] as const,\n\n\t// Each populate path has its own allowed select fields\n\tpopulatableFields: {\n\t\tchatroom: ['_id', 'lastMessage', 'participants', 'unreadCount'] as const,\n\t\tclient: ['_id', 'name', 'profilePhoto', 'type'] as const,\n\t\tadmin: ['_id', 'name', 'profilePhoto'] as const,\n\t\torder: ['_id', 'orderId', 'shop', 'total'] as const,\n\t} as const,\n\n\tsearchableFields: ['ticketId'] as const,\n\n\tqueryableFields: {\n\t\tstatus: z.enum(['OPEN', 'CLOSED', 'PENDING']),\n\t\ttype: z.enum(['ORDER', 'GENERAL', 'COMPLAINT']),\n\t\tclient: z.string(),\n\t\tclientType: z.enum(['USER', 'SHOP']),\n\t},\n});\n\ntype TicketFilterDTO = z.infer<typeof TicketFilterSchema>;\n\nconst ticket: TicketFilterDTO = {\n\n}\n\n\n/**\n * EXAMPLE USAGE\n *\n * const TicketFilterSchema = createFilterSchema({\n * // Top-level document fields that can be selected\n * selectableFields: [\n * '_id',\n * 'ticketId',\n * 'type',\n * 'status',\n * 'createdAt',\n * 'updatedAt',\n * ] as const,\n *\n * // Each populate path has its own allowed select fields\n * populatableFields: {\n * chatroom: ['_id', 'lastMessage', 'participants', 'unreadCount'] as const,\n * client: ['_id', 'name', 'profilePhoto', 'type'] as const,\n * admin: ['_id', 'name', 'profilePhoto'] as const,\n * order: ['_id', 'orderId', 'shop', 'total'] as const,\n * } as const,\n *\n * searchableFields: ['ticketId'] as const,\n *\n * queryableFields: {\n * status: z.enum(['OPEN', 'CLOSED', 'PENDING']),\n * type: z.enum(['ORDER', 'GENERAL', 'COMPLAINT']),\n * client: z.string(),\n * clientType: z.enum(['USER', 'SHOP']),\n * },\n * });\n *\n * type TicketFilterDTO = z.infer<typeof TicketFilterSchema>;\n *\n * // ✅ VALID EXAMPLES\n *\n * // Example 1: Select only top-level fields\n * const filter1: TicketFilterDTO = {\n * select: { ticketId: 1, status: 1, createdAt: 1 },\n * query: { status: 'OPEN' },\n * page: 1,\n * size: 20,\n * };\n *\n * // Example 2: Populate client with specific fields\n * const filter2: TicketFilterDTO = {\n * populate: {\n * path: 'client',\n * select: { name: 1, profilePhoto: 1 }, // Uses client's allowed fields\n * },\n * page: 1,\n * size: 20,\n * };\n *\n * // Example 3: Populate multiple relations\n * const filter3: TicketFilterDTO = {\n * populate: [\n * { path: 'client', select: { name: 1, profilePhoto: 1 } },\n * { path: 'order', select: { orderId: 1, total: 1 } },\n * { path: 'chatroom', select: { lastMessage: 1, unreadCount: 1 } },\n * ],\n * page: 1,\n * size: 20,\n * };\n *\n * // Example 4: Complex query with search\n * const filter4: TicketFilterDTO = {\n * query: { status: 'OPEN', type: 'ORDER' },\n * search: { searchKey: 'TKT-123', searchFields: ['ticketId'] },\n * populate: { path: 'client', select: { name: 1 } },\n * sort: { createdAt: -1 },\n * page: 1,\n * size: 20,\n * };\n *\n * // Example 5: Populate with match and options\n * const filter5: TicketFilterDTO = {\n * populate: {\n * path: 'order',\n * select: { orderId: 1, shop: 1 },\n * match: { status: 'DELIVERED' },\n * options: { sort: { createdAt: -1 }, limit: 10 },\n * },\n * page: 1,\n * size: 20,\n * };\n *\n * // ❌ INVALID EXAMPLES (TypeScript will reject)\n *\n * const invalid1: TicketFilterDTO = {\n * select: { invalidField: 1 }, // ❌ not in selectableFields\n * };\n *\n * const invalid2: TicketFilterDTO = {\n * populate: {\n * path: 'client',\n * select: { email: 1 }, // ❌ email not in client's populatableFields\n * },\n * };\n *\n * const invalid3: TicketFilterDTO = {\n * populate: {\n * path: 'invalidPath', // ❌ path doesn't exist\n * select: { name: 1 },\n * },\n * };\n *\n * const invalid4: TicketFilterDTO = {\n * query: { status: 'INVALID_STATUS' }, // ❌ not in enum\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, 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\tTSort extends ReadonlyNonEmptyArray<string> | [] = [],\n> {\n\tselectableFields?: TSelect;\n\tpopulatableFields?: TPopulate;\n\tsearchableFields?: TSearch;\n\tqueryableFields?: TQuery;\n\tsortableFields?: TSort;\n\tmaxSize?: number;\n\tdefaultSize?: number;\n}\n\n// Populate object type\nexport type PopulateObject<TPath extends string, TSelectFields extends ReadonlyNonEmptyArray<string>> = {\n\tpath: TPath;\n\tselect?: Partial<Record<TSelectFields[number], 0 | 1>>;\n\tmatch?: Record<string, any>;\n\toptions?: { sort?: Record<string, 1 | -1>; limit?: number; skip?: number };\n};\n\n// Union of all possible populate objects based on populatableFields\ntype PopulateUnion<TPopulate extends Record<string, ReadonlyNonEmptyArray<string>>> = {\n\t[K in keyof TPopulate]: PopulateObject<K & string, TPopulate[K]>;\n}[keyof TPopulate];\n\n// DTO type\nexport type FilterDTO<\n\tTSelect extends ReadonlyNonEmptyArray<string> | [] = [],\n\tTPopulate extends Record<string, ReadonlyNonEmptyArray<string>> | {} = {},\n\tTSearch extends ReadonlyNonEmptyArray<string> | [] = [],\n\tTQuery extends Record<string, ZodTypeAny> = {},\n\tTSort extends ReadonlyNonEmptyArray<string> | [] = [],\n> = {\n\tsize?: number;\n\tpage?: number;\n\tsort?: TSort extends ReadonlyNonEmptyArray<string> ? Partial<Record<TSort[number], 1 | -1>> : never;\n\tselect?: TSelect extends ReadonlyNonEmptyArray<string> ? Partial<Record<TSelect[number], 0 | 1>> : never;\n\tpopulate?: PopulateUnion<TPopulate>[];\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\tTSort extends ReadonlyNonEmptyArray<string> | [] = [],\n>(config: FilterSchemaConfig<TSelect, TPopulate, TSearch, TQuery, TSort>) {\n\tconst {\n\t\tselectableFields,\n\t\tpopulatableFields,\n\t\tsearchableFields,\n\t\tqueryableFields,\n\t\tsortableFields,\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};\n\n\t// Sort with allowed fields\n\tif (sortableFields?.length) {\n\t\tschemaShape.sort = z\n\t\t\t.record(z.enum([...sortableFields] as [string, ...string[]]), z.union([z.literal(1), z.literal(-1)]))\n\t\t\t.optional();\n\t}\n\n\t// Top-level 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 as array of objects with strict typing per path\n\tif (populateKeys.length) {\n\t\tconst populateSchemas: ZodTypeAny[] = [];\n\n\t\tfor (const path of populateKeys) {\n\t\t\tconst allowedSelects = populatableFields![path] as ReadonlyNonEmptyArray<string>;\n\n\t\t\tconst populateSchema = z.object({\n\t\t\t\tpath: z.literal(path as string),\n\t\t\t\tselect: z\n\t\t\t\t\t.record(z.enum([...allowedSelects] as [string, ...string[]]), z.union([z.literal(0), z.literal(1)]))\n\t\t\t\t\t.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});\n\n\t\t\tpopulateSchemas.push(populateSchema);\n\t\t}\n\n\t\tconst unionSchema =\n\t\t\tpopulateSchemas.length === 1\n\t\t\t\t? populateSchemas[0]\n\t\t\t\t: z.union(populateSchemas as [ZodTypeAny, ZodTypeAny, ...ZodTypeAny[]]);\n\n\t\tschemaShape.populate = z.array(unionSchema).optional();\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\treturn z.object(schemaShape) as z.ZodType<FilterDTO<TSelect, TPopulate, TSearch, TQuery, TSort>>;\n}\n\nconst TicketFilterSchema = createFilterSchema({\n\t// Top-level document fields that can be selected\n\tselectableFields: ['_id', 'ticketId', 'type', 'status', 'createdAt', 'updatedAt'] as const,\n\n\t// Each populate path has its own allowed select fields\n\tpopulatableFields: {\n\t\tchatroom: ['_id', 'lastMessage', 'participants', 'unreadCount'] as const,\n\t\tclient: ['_id', 'name', 'profilePhoto', 'type'] as const,\n\t\tadmin: ['_id', 'name', 'profilePhoto'] as const,\n\t\torder: ['_id', 'orderId', 'shop', 'total'] as const,\n\t} as const,\n\n\tsearchableFields: ['ticketId'] as const,\n\n\tsortableFields: ['createdAt', 'updatedAt', 'ticketId', 'status'] as const,\n\n\tqueryableFields: {\n\t\tstatus: z.enum(['OPEN', 'CLOSED', 'PENDING']),\n\t\ttype: z.enum(['ORDER', 'GENERAL', 'COMPLAINT']),\n\t\tclient: z.string(),\n\t\tclientType: z.enum(['USER', 'SHOP']),\n\t},\n});\n\ntype TicketFilterDTO = z.infer<typeof TicketFilterSchema>;\n\nconst ticket: TicketFilterDTO = {\n\tsort: { createdAt: -1, status: 1 }, // ✅ Only allowed fields with 1 or -1\n};\n\n/**\n * EXAMPLE USAGE\n *\n * const TicketFilterSchema = createFilterSchema({\n * selectableFields: [\n * '_id',\n * 'ticketId',\n * 'type',\n * 'status',\n * 'createdAt',\n * 'updatedAt',\n * ] as const,\n *\n * populatableFields: {\n * chatroom: ['_id', 'lastMessage', 'participants', 'unreadCount'] as const,\n * client: ['_id', 'name', 'profilePhoto', 'type'] as const,\n * admin: ['_id', 'name', 'profilePhoto'] as const,\n * order: ['_id', 'orderId', 'shop', 'total'] as const,\n * } as const,\n *\n * searchableFields: ['ticketId'] as const,\n *\n * sortableFields: ['createdAt', 'updatedAt', 'ticketId', 'status'] as const,\n *\n * queryableFields: {\n * status: z.enum(['OPEN', 'CLOSED', 'PENDING']),\n * type: z.enum(['ORDER', 'GENERAL', 'COMPLAINT']),\n * client: z.string(),\n * clientType: z.enum(['USER', 'SHOP']),\n * },\n * });\n *\n * type TicketFilterDTO = z.infer<typeof TicketFilterSchema>;\n *\n * // ✅ VALID EXAMPLES\n *\n * // Example 1: Sort by allowed fields\n * const filter1: TicketFilterDTO = {\n * sort: { createdAt: -1, status: 1 },\n * query: { status: 'OPEN' },\n * page: 1,\n * size: 20,\n * };\n *\n * // Example 2: Populate client with specific fields\n * const filter2: TicketFilterDTO = {\n * populate: [\n * { path: 'client', select: { name: 1, profilePhoto: 1 } }\n * ],\n * sort: { updatedAt: -1 },\n * page: 1,\n * size: 20,\n * };\n *\n * // Example 3: Populate multiple relations with sort\n * const filter3: TicketFilterDTO = {\n * populate: [\n * { path: 'client', select: { name: 1, profilePhoto: 1 } },\n * { path: 'order', select: { orderId: 1, total: 1 } },\n * { path: 'chatroom', select: { lastMessage: 1, unreadCount: 1 } },\n * ],\n * sort: { createdAt: -1 },\n * page: 1,\n * size: 20,\n * };\n *\n * // Example 4: Complex query with search and sort\n * const filter4: TicketFilterDTO = {\n * query: { status: 'OPEN', type: 'ORDER' },\n * search: { searchKey: 'TKT-123', searchFields: ['ticketId'] },\n * populate: [{ path: 'client', select: { name: 1 } }],\n * sort: { createdAt: -1, ticketId: 1 },\n * page: 1,\n * size: 20,\n * };\n *\n * // Example 5: Populate with match and options\n * const filter5: TicketFilterDTO = {\n * populate: [\n * {\n * path: 'order',\n * select: { orderId: 1, shop: 1 },\n * match: { status: 'DELIVERED' },\n * options: { sort: { createdAt: -1 }, limit: 10 },\n * }\n * ],\n * sort: { updatedAt: -1 },\n * page: 1,\n * size: 20,\n * };\n *\n * // ❌ INVALID EXAMPLES (TypeScript will reject)\n *\n * const invalid1: TicketFilterDTO = {\n * sort: { invalidField: 1 }, // ❌ not in sortableFields\n * };\n *\n * const invalid2: TicketFilterDTO = {\n * sort: { createdAt: 2 }, // ❌ must be 1 or -1\n * };\n *\n * const invalid3: TicketFilterDTO = {\n * populate: [\n * {\n * path: 'client',\n * select: { email: 1 }, // ❌ email not in client's populatableFields\n * }\n * ],\n * };\n *\n * const invalid4: TicketFilterDTO = {\n * query: { status: 'INVALID_STATUS' }, // ❌ not in enum\n * }\n */\n"]}
|