@liminalfunctions/framework 1.0.68 → 1.0.71

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.
Files changed (43) hide show
  1. package/dist/F_Collection.d.ts +2 -0
  2. package/dist/F_Collection.js +6 -1
  3. package/dist/F_Collection.js.map +1 -1
  4. package/dist/F_Compile.js +18 -0
  5. package/dist/F_Compile.js.map +1 -1
  6. package/dist/code_generation/templates/utils.ts.mustache +1 -0
  7. package/dist/utils/complex_query_validator_from_zod.d.ts +20 -0
  8. package/dist/utils/complex_query_validator_from_zod.js +232 -0
  9. package/dist/utils/complex_query_validator_from_zod.js.map +1 -0
  10. package/dist/utils/query_object_to_mongodb_query.d.ts +1 -0
  11. package/dist/utils/query_object_to_mongodb_query.js +1 -0
  12. package/dist/utils/query_object_to_mongodb_query.js.map +1 -1
  13. package/dist/utils/query_validator_from_zod.js +2 -1
  14. package/dist/utils/query_validator_from_zod.js.map +1 -1
  15. package/dist/utils/zod_loop_seperator.js +4 -0
  16. package/dist/utils/zod_loop_seperator.js.map +1 -1
  17. package/package.json +1 -1
  18. package/src/F_Collection.ts +6 -1
  19. package/src/F_Compile.ts +17 -0
  20. package/src/code_generation/templates/utils.ts.mustache +1 -0
  21. package/src/utils/complex_query_validator_from_zod.ts +259 -0
  22. package/src/utils/query_object_to_mongodb_query.ts +1 -0
  23. package/src/utils/query_validator_from_zod.ts +3 -1
  24. package/src/utils/zod_loop_seperator.ts +5 -0
  25. package/test/0_4_query_validator_to_advanced_query.test.ts +402 -0
  26. package/test/1_0_basic_server.test.ts +1 -0
  27. package/test/1_4_advanced_queries.test.ts +400 -0
  28. package/test/2_0_client_library_basic_type_generation.test.ts +2 -2
  29. package/test/2_0_client_library_query_type_generation.test.ts +10 -0
  30. package/test/tmp/dist/types/brief_news_category_query.d.ts +1 -0
  31. package/test/tmp/dist/types/client_query.d.ts +1 -0
  32. package/test/tmp/dist/types/institution_query.d.ts +1 -0
  33. package/test/tmp/dist/types/project_query.d.ts +1 -0
  34. package/test/tmp/dist/utils/utils.js +4 -0
  35. package/test/tmp/dist/utils/utils.js.map +1 -1
  36. package/test/tmp/src/types/brief_news_category_query.ts +1 -0
  37. package/test/tmp/src/types/client_query.ts +1 -0
  38. package/test/tmp/src/types/institution_query.ts +1 -0
  39. package/test/tmp/src/types/project_query.ts +1 -0
  40. package/test/tmp/src/utils/utils.ts +1 -0
  41. /package/test/{0_4_cache.test.ts → 0_5_cache.test.ts} +0 -0
  42. /package/test/{0_5_malicious_keys.test.ts → 0_6_malicious_keys.test.ts} +0 -0
  43. /package/test/{0_6_array_children.test.ts → 0_7_array_children.test.ts} +0 -0
@@ -4,6 +4,7 @@ import mongoose, { Collection, Model, ObjectId } from "mongoose";
4
4
  import { F_Security_Model } from "./F_Security_Models/F_Security_Model.js";
5
5
  import { query_validator_from_zod } from "./utils/query_validator_from_zod.js";
6
6
  import { array_children_from_zod } from "./utils/array_children_from_zod.js";
7
+ import { complex_query_validator_from_zod } from "./utils/complex_query_validator_from_zod.js";
7
8
 
8
9
  export type CollectionType<Col extends F_Collection<string, Validator>, Validator extends z.ZodObject> = z.output<Col['validator']>;
9
10
 
@@ -24,6 +25,8 @@ export class F_Collection<Collection_ID extends string, ZodSchema extends z.ZodO
24
25
 
25
26
  query_validator_server: z.ZodType;
26
27
  query_validator_client: z.ZodType;
28
+ advanced_query_validator_server: z.ZodType;
29
+ advanced_query_validator_client: z.ZodType;
27
30
  put_validator: ReturnType<ZodSchema['partial']>;
28
31
  // TODO: Come back and find a way to select the particular partial I want
29
32
  // instead of partialing the whole object.
@@ -49,6 +52,8 @@ export class F_Collection<Collection_ID extends string, ZodSchema extends z.ZodO
49
52
  this.mongoose_model = mongoose_from_zod(collection_name, validator, database);
50
53
  this.query_validator_server = query_validator_from_zod(validator, 'server');
51
54
  this.query_validator_client = query_validator_from_zod(validator, 'client');
55
+ this.advanced_query_validator_server = complex_query_validator_from_zod(validator, 'server').optional()
56
+ this.advanced_query_validator_client = complex_query_validator_from_zod(validator, 'client').optional()
52
57
  // TODO: we can make this more closely match the mongoDB PUT operation and allow updates to eg array.3.element fields
53
58
 
54
59
  if(!Object.hasOwn(this.validator._zod.def.shape, '_id')){
@@ -247,7 +252,7 @@ export class F_Collection<Collection_ID extends string, ZodSchema extends z.ZodO
247
252
  let deleted_document_data;
248
253
 
249
254
  // if we have any update hooks, run the update operation in a transaction
250
- if(this.update_hooks.length > 0){
255
+ if(this.delete_hooks.length > 0){
251
256
  await mongoose.connection.transaction(async (session) => {
252
257
  // update the document
253
258
  let deleted_document = await this.mongoose_model.findOneAndDelete(find, {returnDocument: 'after', session: session, lean: true})
package/src/F_Compile.ts CHANGED
@@ -151,6 +151,23 @@ export function compile<Collection_ID extends string, ZodSchema extends z.ZodObj
151
151
  res.json({ error: `You do not have permission to fetch documents from ${collection.collection_id}.` });
152
152
  return;
153
153
  }
154
+
155
+ if(validated_query_args.advanced_query){
156
+ let parsed_advanced_query: unknown;
157
+ try { parsed_advanced_query = JSON.parse(validated_query_args.advanced_query); } catch(err) {
158
+ res.status(400);
159
+ res.json({ error: `The advanced_query field was not in JSON format` });
160
+ return
161
+ }
162
+ let { data: advanced_query, error: parsed_advanced_query_error, success: parsed_advanced_query_success } = collection.advanced_query_validator_server.safeParse(parsed_advanced_query);
163
+ if(!parsed_advanced_query_success){
164
+ res.status(400);
165
+ res.json({ error: parsed_advanced_query_error.issues });
166
+ return;
167
+ }
168
+ find = { $and: [find, advanced_query]}
169
+ }
170
+
154
171
 
155
172
  let documents;
156
173
  try {
@@ -3,6 +3,7 @@
3
3
  export function encode_search_params(params: {[key: string]: string | number | boolean | string[] | Date}): {[key: string]: string | number | boolean }{
4
4
  let retval: {[key: string]: string | number | boolean } = {}
5
5
  for(let [key, value] of Object.entries(params)){
6
+ if(key === 'advanced_query') { retval[key] = JSON.stringify(value); continue; }
6
7
  if(Array.isArray(value)){
7
8
  retval[key] = value.join(',')
8
9
  } else if(value instanceof Date){
@@ -0,0 +1,259 @@
1
+ import { z } from "zod/v4"
2
+ import { $ZodLooseShape } from "zod/v4/core";
3
+ import { z_mongodb_id, z_mongodb_id_nullable, z_mongodb_id_optional } from "./mongoose_from_zod.js";
4
+ import { find_loops, validator_group } from './zod_loop_seperator.js'
5
+ import escapeStringRegexp from "escape-string-regexp";
6
+
7
+ type type_filters = {
8
+ path: string,
9
+ filter: z.ZodType,
10
+ sortable: boolean,
11
+ }[]
12
+ type Mode = 'client' | 'server'
13
+
14
+ export function complex_query_validator_from_zod(zod_definition: z.ZodObject, mode: Mode = 'server'){
15
+ let loops = find_loops(zod_definition as z.ZodType);
16
+
17
+ let object_filter = {} as $ZodLooseShape;
18
+ let object_filters = parse_object(zod_definition._zod.def, '', loops, mode);
19
+ for(let filter of object_filters){
20
+ object_filter[filter.path.slice(1)] = filter.filter;
21
+ }
22
+ let compiled_object_filter = z.object(object_filter)
23
+
24
+ let and = z.object({
25
+ get $and(){ return z.array(z.union([and, or, compiled_object_filter])); },
26
+ });
27
+ let or = z.object({
28
+ get $or(){ return z.array(z.union([and, or, compiled_object_filter])); },
29
+ });
30
+
31
+ return z.union([and, or]);
32
+ }
33
+
34
+ function parse_any(zod_definition: z.ZodTypeAny, prefix: string, loop_detector: Map<any, validator_group>, mode: Mode = 'server'): type_filters {
35
+ switch (zod_definition._zod.def.type) {
36
+ case "enum":
37
+ return parse_enum(zod_definition._zod.def as z.core.$ZodEnumDef, prefix, mode);
38
+ case "string":
39
+ return parse_string(prefix, mode);
40
+ case "number":
41
+ case "int":
42
+ return parse_number(prefix, mode);
43
+ case "object":
44
+ return parse_object(zod_definition._zod.def as z.core.$ZodObjectDef, prefix, loop_detector, mode);
45
+ case "boolean":
46
+ return parse_boolean(prefix, mode);
47
+ case "date":
48
+ return parse_date(prefix, mode);
49
+ case "array":
50
+ return parse_array(zod_definition._zod.def as z.core.$ZodArrayDef, prefix, loop_detector, mode)
51
+ case "union":
52
+ return parse_union(zod_definition._zod.def as z.core.$ZodUnionDef, prefix, mode)
53
+ case "custom":
54
+ if(!zod_definition.meta()) {
55
+ throw new Error(`could not find custom parser in the magic value dictionary`)
56
+ }
57
+ let { framework_override_type } = zod_definition.meta();
58
+
59
+ if(framework_override_type === 'mongodb_id'){
60
+ return parse_mongodb_id(prefix, mode);
61
+ } else {
62
+ throw new Error(`could not find custom parser for ${framework_override_type} in the magic value dictionary`)
63
+ }
64
+ case "default":
65
+ //@ts-ignore
66
+ return parse_any((zod_definition._zod.def as z.core.$ZodDefaultDef).innerType, prefix, loop_detector, mode)
67
+ default:
68
+ return []
69
+ }
70
+ }
71
+
72
+ function parse_array(def: z.core.$ZodArrayDef, prefix: string, loop_detector: Map<any, validator_group>, mode: Mode) {
73
+ let simple_children = ['enum', 'string', 'number', 'int', 'boolean']
74
+ if(simple_children.includes(def.element._zod.def.type)) {
75
+ //@ts-ignore
76
+ return parse_any(def.element, prefix, loop_detector, mode).filter(ele => ele.path == prefix);
77
+ } else if(def.element._zod.def.type === 'custom'){
78
+ //@ts-ignore
79
+ if(!def.element.meta()) {
80
+ return [];
81
+ }
82
+ //@ts-ignore
83
+ let { framework_override_type } = def.element.meta();
84
+
85
+ if(framework_override_type === 'mongodb_id'){
86
+ return parse_mongodb_id(prefix, mode).filter(ele => ele.path == prefix);
87
+ }
88
+ }
89
+
90
+ return [];
91
+ }
92
+
93
+ function parse_object(def: z.core.$ZodObjectDef, prefix: string, loop_detector: Map<any, validator_group>, mode: Mode): type_filters {
94
+ if(loop_detector.has(def)) {
95
+ return [];
96
+ }
97
+
98
+ let retval = [] as type_filters;
99
+ for(let [key, value] of Object.entries(def.shape)){
100
+ //@ts-ignore
101
+ let filters = parse_any(value, `${prefix}.${key}`, loop_detector, mode);
102
+ retval.push(...filters);
103
+ }
104
+ return retval;
105
+ }
106
+
107
+ function parse_union(def: z.core.$ZodUnionDef, prefix: string, mode: Mode): type_filters {
108
+ let simple_children = ['enum', 'string', 'number', 'int', 'boolean']
109
+ let filter_queue = def.options.slice().filter(ele => simple_children.includes(ele._zod.def.type));
110
+ if(filter_queue.length === 0){ return []; }
111
+ let root = filter_queue.shift();
112
+ for(let filter of filter_queue){
113
+ //@ts-expect-error
114
+ root = root.or(filter);
115
+ }
116
+ return [
117
+ {
118
+ path: prefix,
119
+ //@ts-expect-error
120
+ filter: root,
121
+ sortable: true,
122
+ }
123
+ ];
124
+ }
125
+
126
+ function parse_string(prefix: string, mode: Mode): type_filters {
127
+ return [
128
+ {
129
+ path: prefix,
130
+ filter: z.union([
131
+ z.object({
132
+ $eq: z.string()
133
+ }),
134
+ z.object({
135
+ $in: z.array(z.string())
136
+ }),
137
+ z.object({
138
+ $nin: z.array(z.string())
139
+ }),
140
+ z.object({
141
+ $regex: z.transform((val) => {
142
+ if(typeof val !== 'string'){ return false; }
143
+ return escapeStringRegexp(val);
144
+ })
145
+ }),
146
+ ]).optional(),
147
+ sortable: true,
148
+ },
149
+ ];
150
+ }
151
+
152
+ function parse_enum(definition: z.core.$ZodEnumDef, prefix: string, mode: Mode): type_filters {
153
+ return [
154
+ {
155
+ path: prefix,
156
+ filter: z.union([
157
+ z.object({
158
+ $eq: z.enum(definition.entries)
159
+ }),
160
+ z.object({
161
+ $in: z.array(z.enum(definition.entries))
162
+ }),
163
+ ]).optional(),
164
+ sortable: true,
165
+ }
166
+ ];
167
+ }
168
+
169
+ function parse_boolean(prefix: string, mode: Mode): type_filters {
170
+ return [{
171
+ path: prefix,
172
+ filter: z.object({
173
+ $eq: z.boolean()
174
+ }).optional(),
175
+ sortable: true,
176
+ }];
177
+ }
178
+
179
+ function parse_number(prefix: string, mode: Mode): type_filters {
180
+ return [{
181
+ path: prefix,
182
+ filter: z.union([
183
+ z.object({
184
+ $eq: z.number()
185
+ }),
186
+ z.object({
187
+ $gt: z.number()
188
+ }),
189
+ z.object({
190
+ $lt: z.number()
191
+ }),
192
+ z.object({
193
+ $gte: z.number()
194
+ }),
195
+ z.object({
196
+ $lte: z.number()
197
+ }),
198
+ ]).optional(),
199
+ sortable: true,
200
+ }];
201
+ }
202
+
203
+ function parse_date(prefix: string, mode: Mode): type_filters {
204
+ let date_parser = mode === 'client' ? z.date() : z.coerce.date();
205
+ return [{
206
+ path: prefix,
207
+ filter: z.union([
208
+ z.object({
209
+ $eq: date_parser
210
+ }),
211
+ z.object({
212
+ $gt: date_parser
213
+ }),
214
+ z.object({
215
+ $lt: date_parser
216
+ }),
217
+ z.object({
218
+ $gte: date_parser
219
+ }),
220
+ z.object({
221
+ $lte: date_parser
222
+ }),
223
+ ]).optional(),
224
+ sortable: true,
225
+ }];
226
+ }
227
+
228
+
229
+ function parse_mongodb_id(prefix: string, mode: Mode): type_filters {
230
+ return [
231
+ {
232
+ path: prefix,
233
+ filter: z.union([
234
+ z.object({
235
+ $eq: z_mongodb_id
236
+ }),
237
+ z.object({
238
+ $gt: z_mongodb_id
239
+ }),
240
+ z.object({
241
+ $lt: z_mongodb_id
242
+ }),
243
+ z.object({
244
+ $gte: z_mongodb_id
245
+ }),
246
+ z.object({
247
+ $lte: z_mongodb_id
248
+ }),
249
+ z.object({
250
+ $in: z.array(z_mongodb_id)
251
+ }),
252
+ z.object({
253
+ $nin: z.array(z_mongodb_id)
254
+ }),
255
+ ]).optional(),
256
+ sortable: true,
257
+ }
258
+ ];
259
+ }
@@ -25,6 +25,7 @@ export let query_meta_map = {
25
25
  'sort': true,
26
26
  'sort_order': true,
27
27
  //'projection': true,
28
+ 'advanced_query': true,
28
29
  }
29
30
 
30
31
  export function convert_null(query_object: any){
@@ -2,6 +2,7 @@ import { z } from "zod/v4"
2
2
  import { $ZodLooseShape } from "zod/v4/core";
3
3
  import { z_mongodb_id, z_mongodb_id_nullable, z_mongodb_id_optional } from "./mongoose_from_zod.js";
4
4
  import { find_loops, validator_group } from './zod_loop_seperator.js'
5
+ import { complex_query_validator_from_zod } from "./complex_query_validator_from_zod.js";
5
6
 
6
7
  type type_filters = {
7
8
  path: string,
@@ -16,7 +17,8 @@ export function query_validator_from_zod(zod_definition: z.ZodObject, mode: Mode
16
17
  let retval = {
17
18
  limit: z.coerce.number().int().optional(),
18
19
  cursor: z_mongodb_id_optional,
19
- sort_order: z.enum([/*'asc', 'desc', */'ascending', 'descending']).optional()
20
+ sort_order: z.enum([/*'asc', 'desc', */'ascending', 'descending']).optional(),
21
+ advanced_query: z.string().optional(),
20
22
  } as $ZodLooseShape;
21
23
 
22
24
  let object_filters = parse_object(zod_definition._zod.def, '', loops, mode);
@@ -29,6 +29,11 @@ function discover_loops(
29
29
  console.error(zod_definition);
30
30
  }
31
31
 
32
+ if(!zod_definition._zod) {
33
+ console.error('ISSUE 2');
34
+ console.error(zod_definition);
35
+ }
36
+
32
37
  switch (zod_definition._zod.def.type) {
33
38
  case "object":
34
39
  parse_object(zod_definition as z.ZodObject, validator_groups);