@lyxa.ai/types 1.1.59 → 1.1.61

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.59
25
+ Version: 1.1.61
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.59",
3
+ "version": "1.1.61",
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",
@@ -8,25 +8,23 @@ export interface FilterSchemaConfig<TSelect extends ReadonlyNonEmptyArray<string
8
8
  maxSize?: number;
9
9
  defaultSize?: number;
10
10
  }
11
- export type PopulateObject<TPath extends string, TSelectFields extends ReadonlyNonEmptyArray<string>> = {
12
- path: TPath;
13
- select?: Partial<Record<TSelectFields[number], 0 | 1>>;
14
- match?: Record<string, any>;
15
- options?: {
16
- sort?: Record<string, 1 | -1>;
17
- limit?: number;
18
- skip?: number;
11
+ type PopulateMap<TPopulate extends Record<string, ReadonlyNonEmptyArray<string>>> = {
12
+ [K in keyof TPopulate]?: {
13
+ select?: Partial<Record<TPopulate[K][number], 0 | 1>>;
14
+ match?: Record<string, any>;
15
+ options?: {
16
+ sort?: Record<string, 1 | -1>;
17
+ limit?: number;
18
+ skip?: number;
19
+ };
19
20
  };
20
21
  };
21
- type PopulateUnion<TPopulate extends Record<string, ReadonlyNonEmptyArray<string>>> = {
22
- [K in keyof TPopulate]: PopulateObject<K & string, TPopulate[K]>;
23
- }[keyof TPopulate];
24
22
  export type FilterDTO<TSelect extends ReadonlyNonEmptyArray<string> | [] = [], TPopulate extends Record<string, ReadonlyNonEmptyArray<string>> | {} = {}, TSearch extends ReadonlyNonEmptyArray<string> | [] = [], TQuery extends Record<string, ZodTypeAny> = {}> = {
25
23
  size?: number;
26
24
  page?: number;
27
25
  sort?: Record<string, 1 | -1>;
28
26
  select?: TSelect extends ReadonlyNonEmptyArray<string> ? Partial<Record<TSelect[number], 0 | 1>> : never;
29
- populate?: keyof TPopulate extends never ? never : PopulateUnion<TPopulate> | PopulateUnion<TPopulate>[];
27
+ populate?: PopulateMap<TPopulate>;
30
28
  search?: TSearch extends ReadonlyNonEmptyArray<string> ? {
31
29
  searchKey: string;
32
30
  searchFields?: TSearch[number][];
@@ -5,7 +5,6 @@ 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
7
  const { selectableFields, populatableFields, searchableFields, queryableFields, maxSize = 100, defaultSize = 10, } = config;
8
- const populateKeys = populatableFields ? Object.keys(populatableFields) : [];
9
8
  const schemaShape = {
10
9
  size: zod_1.z.number().min(1).max(maxSize).optional().default(defaultSize),
11
10
  page: zod_1.z.number().min(1).optional().default(1),
@@ -16,12 +15,12 @@ function createFilterSchema(config) {
16
15
  .record(zod_1.z.enum([...selectableFields]), zod_1.z.union([zod_1.z.literal(0), zod_1.z.literal(1)]))
17
16
  .optional();
18
17
  }
19
- if (populateKeys.length) {
20
- const populateSchemas = [];
21
- for (const path of populateKeys) {
18
+ if (populatableFields && Object.keys(populatableFields).length) {
19
+ const populateShape = {};
20
+ for (const path in populatableFields) {
22
21
  const allowedSelects = populatableFields[path];
23
- const populateSchema = zod_1.z.object({
24
- path: zod_1.z.literal(path),
22
+ populateShape[path] = zod_1.z
23
+ .object({
25
24
  select: zod_1.z
26
25
  .record(zod_1.z.enum([...allowedSelects]), zod_1.z.union([zod_1.z.literal(0), zod_1.z.literal(1)]))
27
26
  .optional(),
@@ -33,19 +32,10 @@ function createFilterSchema(config) {
33
32
  skip: zod_1.z.number().optional(),
34
33
  })
35
34
  .optional(),
36
- });
37
- populateSchemas.push(populateSchema);
38
- }
39
- if (populateSchemas.length > 0) {
40
- let unionSchema;
41
- if (populateSchemas.length === 1) {
42
- unionSchema = populateSchemas[0];
43
- }
44
- else {
45
- unionSchema = zod_1.z.union(populateSchemas);
46
- }
47
- schemaShape.populate = zod_1.z.union([unionSchema, zod_1.z.array(unionSchema)]).optional();
35
+ })
36
+ .optional();
48
37
  }
38
+ schemaShape.populate = zod_1.z.object(populateShape).optional();
49
39
  }
50
40
  if (searchableFields?.length) {
51
41
  schemaShape.search = zod_1.z
@@ -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// Each populate path has its own select fields from populatableFields\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// Create a union type 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\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> ? Partial<Record<TSelect[number], 0 | 1>> : never;\n\tpopulate?: keyof TPopulate extends never ? never : PopulateUnion<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// Select - uses selectableFields for top-level document fields\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 - each path uses its own select fields from populatableFields\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\tif (populateSchemas.length > 0) {\n\t\t\tlet unionSchema: ZodTypeAny;\n\t\t\tif (populateSchemas.length === 1) {\n\t\t\t\tunionSchema = 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.union([unionSchema, z.array(unionSchema)]).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\treturn z.object(schemaShape) as z.ZodType<FilterDTO<TSelect, TPopulate, TSearch, TQuery>>;\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> {\n\tselectableFields?: TSelect;\n\tpopulatableFields?: TPopulate;\n\tsearchableFields?: TSearch;\n\tqueryableFields?: TQuery;\n\tmaxSize?: number;\n\tdefaultSize?: number;\n}\n\n// Mapped type for keyed populate\ntype PopulateMap<TPopulate extends Record<string, ReadonlyNonEmptyArray<string>>> = {\n\t[K in keyof TPopulate]?: {\n\t\tselect?: Partial<Record<TPopulate[K][number], 0 | 1>>;\n\t\tmatch?: Record<string, any>;\n\t\toptions?: { sort?: Record<string, 1 | -1>; limit?: number; skip?: number };\n\t};\n};\n\n// Filter DTO\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> ? Partial<Record<TSelect[number], 0 | 1>> : never;\n\tpopulate?: PopulateMap<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\n// Create the Zod schema\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 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(z.enum([...selectableFields] as [string, ...string[]]), z.union([z.literal(0), z.literal(1)]))\n\t\t\t.optional();\n\t}\n\n\t// Keyed populate\n\tif (populatableFields && Object.keys(populatableFields).length) {\n\t\tconst populateShape: Record<string, any> = {};\n\n\t\tfor (const path in populatableFields) {\n\t\t\tconst allowedSelects = populatableFields[path] as ReadonlyNonEmptyArray<string>;\n\n\t\t\tpopulateShape[path] = z\n\t\t\t\t.object({\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.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})\n\t\t\t\t.optional();\n\t\t}\n\n\t\tschemaShape.populate = z.object(populateShape).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/**\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,4 +1,4 @@
1
1
  export * from './common-validation';
2
2
  export * from './global-validation';
3
3
  export * from './validation-for-frontend';
4
- export { createFilterSchema, FilterSchemaConfig, PopulateObject } from './filter-schema.factory';
4
+ export { createFilterSchema, FilterSchemaConfig } from './filter-schema.factory';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"/","sources":["utilities/validation/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,sDAAoC;AACpC,sDAAoC;AACpC,4DAA0C;AAC1C,iEAAiG;AAAxF,2HAAA,kBAAkB,OAAA","sourcesContent":["export * from './common-validation';\nexport * from './global-validation';\nexport * from './validation-for-frontend';\nexport { createFilterSchema, FilterSchemaConfig, PopulateObject } from './filter-schema.factory';"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"/","sources":["utilities/validation/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,sDAAoC;AACpC,sDAAoC;AACpC,4DAA0C;AAC1C,iEAAiF;AAAxE,2HAAA,kBAAkB,OAAA","sourcesContent":["export * from './common-validation';\nexport * from './global-validation';\nexport * from './validation-for-frontend';\nexport { createFilterSchema, FilterSchemaConfig } from './filter-schema.factory';"]}