@liminalfunctions/framework 1.0.0

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 (181) hide show
  1. package/.mocharc.json +5 -0
  2. package/dist/F_Client_Collection_Registry.d.ts +18 -0
  3. package/dist/F_Client_Collection_Registry.js +36 -0
  4. package/dist/F_Client_Collection_Registry.js.map +1 -0
  5. package/dist/F_Collection.d.ts +21 -0
  6. package/dist/F_Collection.js +36 -0
  7. package/dist/F_Collection.js.map +1 -0
  8. package/dist/F_Collection_Registry.d.ts +11 -0
  9. package/dist/F_Collection_Registry.js +18 -0
  10. package/dist/F_Collection_Registry.js.map +1 -0
  11. package/dist/F_Compile.d.ts +4 -0
  12. package/dist/F_Compile.js +298 -0
  13. package/dist/F_Compile.js.map +1 -0
  14. package/dist/F_Security_Models/F_SM_Open_Access.d.ts +11 -0
  15. package/dist/F_Security_Models/F_SM_Open_Access.js +14 -0
  16. package/dist/F_Security_Models/F_SM_Open_Access.js.map +1 -0
  17. package/dist/F_Security_Models/F_SM_Ownership.d.ts +12 -0
  18. package/dist/F_Security_Models/F_SM_Ownership.js +46 -0
  19. package/dist/F_Security_Models/F_SM_Ownership.js.map +1 -0
  20. package/dist/F_Security_Models/F_SM_Role_Membership.d.ts +19 -0
  21. package/dist/F_Security_Models/F_SM_Role_Membership.js +73 -0
  22. package/dist/F_Security_Models/F_SM_Role_Membership.js.map +1 -0
  23. package/dist/F_Security_Models/F_Security_Model.d.ts +41 -0
  24. package/dist/F_Security_Models/F_Security_Model.js +29 -0
  25. package/dist/F_Security_Models/F_Security_Model.js.map +1 -0
  26. package/dist/code_generation/generate_client_library.d.ts +4 -0
  27. package/dist/code_generation/generate_client_library.js +158 -0
  28. package/dist/code_generation/generate_client_library.js.map +1 -0
  29. package/dist/code_generation/templates/.gitignore.mustache +383 -0
  30. package/dist/code_generation/templates/collection.mustache +106 -0
  31. package/dist/code_generation/templates/main.mustache +24 -0
  32. package/dist/code_generation/templates/package.json.mustache +18 -0
  33. package/dist/code_generation/templates/tsconfig.json.mustache +14 -0
  34. package/dist/code_generation/templates/types.mustache +4 -0
  35. package/dist/code_generation/templates/utils.ts.mustache +17 -0
  36. package/dist/code_generation/utils/tab_indent.d.ts +1 -0
  37. package/dist/code_generation/utils/tab_indent.js +4 -0
  38. package/dist/code_generation/utils/tab_indent.js.map +1 -0
  39. package/dist/code_generation/utils/type_from_zod.d.ts +2 -0
  40. package/dist/code_generation/utils/type_from_zod.js +102 -0
  41. package/dist/code_generation/utils/type_from_zod.js.map +1 -0
  42. package/dist/utils/cache.d.ts +13 -0
  43. package/dist/utils/cache.js +101 -0
  44. package/dist/utils/cache.js.map +1 -0
  45. package/dist/utils/mongoose_from_zod.d.ts +13 -0
  46. package/dist/utils/mongoose_from_zod.js +164 -0
  47. package/dist/utils/mongoose_from_zod.js.map +1 -0
  48. package/dist/utils/pretty_print_zod.d.ts +2 -0
  49. package/dist/utils/pretty_print_zod.js +63 -0
  50. package/dist/utils/pretty_print_zod.js.map +1 -0
  51. package/dist/utils/query_object_to_mongodb_query.d.ts +3 -0
  52. package/dist/utils/query_object_to_mongodb_query.js +61 -0
  53. package/dist/utils/query_object_to_mongodb_query.js.map +1 -0
  54. package/dist/utils/query_validator_from_zod.d.ts +6 -0
  55. package/dist/utils/query_validator_from_zod.js +216 -0
  56. package/dist/utils/query_validator_from_zod.js.map +1 -0
  57. package/package.json +36 -0
  58. package/src/F_Collection.ts +50 -0
  59. package/src/F_Collection_Registry.ts +29 -0
  60. package/src/F_Compile.ts +368 -0
  61. package/src/F_Security_Models/F_SM_Open_Access.ts +21 -0
  62. package/src/F_Security_Models/F_SM_Ownership.ts +72 -0
  63. package/src/F_Security_Models/F_SM_Role_Membership.ts +87 -0
  64. package/src/F_Security_Models/F_Security_Model.ts +85 -0
  65. package/src/code_generation/generate_client_library.ts +197 -0
  66. package/src/code_generation/templates/.gitignore.mustache +383 -0
  67. package/src/code_generation/templates/collection.mustache +106 -0
  68. package/src/code_generation/templates/main.mustache +24 -0
  69. package/src/code_generation/templates/package.json.mustache +18 -0
  70. package/src/code_generation/templates/tsconfig.json.mustache +14 -0
  71. package/src/code_generation/templates/types.mustache +4 -0
  72. package/src/code_generation/templates/utils.ts.mustache +17 -0
  73. package/src/code_generation/utils/tab_indent.ts +3 -0
  74. package/src/code_generation/utils/type_from_zod.ts +140 -0
  75. package/src/utils/cache.ts +149 -0
  76. package/src/utils/mongoose_from_zod.ts +191 -0
  77. package/src/utils/pretty_print_zod.ts +75 -0
  78. package/src/utils/query_object_to_mongodb_query.ts +73 -0
  79. package/src/utils/query_validator_from_zod.ts +246 -0
  80. package/test/0_0_mongoose_from_zod.test.ts +260 -0
  81. package/test/0_1_query_validator_from_zod.test.ts +518 -0
  82. package/test/0_2_query_validator_to_mongodb_query.test.ts +365 -0
  83. package/test/0_3_cache.test.ts +204 -0
  84. package/test/1_0_basic_server.test.ts +530 -0
  85. package/test/1_1_security_ownership.test.ts +328 -0
  86. package/test/1_2_role_membership.test.ts +731 -0
  87. package/test/2_0_client_library_basic_type_generation.test.ts +444 -0
  88. package/test/2_0_client_library_query_type_generation.test.ts +352 -0
  89. package/test/2_1_client_library_generation.test.ts +255 -0
  90. package/test/tmp/dist/Brief_News_Category.d.ts +16 -0
  91. package/test/tmp/dist/Brief_News_Category.js +85 -0
  92. package/test/tmp/dist/Brief_News_Category.js.map +1 -0
  93. package/test/tmp/dist/Client.d.ts +19 -0
  94. package/test/tmp/dist/Client.js +97 -0
  95. package/test/tmp/dist/Client.js.map +1 -0
  96. package/test/tmp/dist/Institution.d.ts +18 -0
  97. package/test/tmp/dist/Institution.js +94 -0
  98. package/test/tmp/dist/Institution.js.map +1 -0
  99. package/test/tmp/dist/Project.d.ts +16 -0
  100. package/test/tmp/dist/Project.js +85 -0
  101. package/test/tmp/dist/Project.js.map +1 -0
  102. package/test/tmp/dist/index.d.ts +4 -0
  103. package/test/tmp/dist/index.js +14 -0
  104. package/test/tmp/dist/index.js.map +1 -0
  105. package/test/tmp/dist/types/brief_news_category.d.ts +7 -0
  106. package/test/tmp/dist/types/brief_news_category.js +2 -0
  107. package/test/tmp/dist/types/brief_news_category.js.map +1 -0
  108. package/test/tmp/dist/types/brief_news_category_post.d.ts +7 -0
  109. package/test/tmp/dist/types/brief_news_category_post.js +2 -0
  110. package/test/tmp/dist/types/brief_news_category_post.js.map +1 -0
  111. package/test/tmp/dist/types/brief_news_category_put.d.ts +7 -0
  112. package/test/tmp/dist/types/brief_news_category_put.js +2 -0
  113. package/test/tmp/dist/types/brief_news_category_put.js.map +1 -0
  114. package/test/tmp/dist/types/brief_news_category_query.d.ts +26 -0
  115. package/test/tmp/dist/types/brief_news_category_query.js +2 -0
  116. package/test/tmp/dist/types/brief_news_category_query.js.map +1 -0
  117. package/test/tmp/dist/types/client.d.ts +5 -0
  118. package/test/tmp/dist/types/client.js +2 -0
  119. package/test/tmp/dist/types/client.js.map +1 -0
  120. package/test/tmp/dist/types/client_post.d.ts +5 -0
  121. package/test/tmp/dist/types/client_post.js +2 -0
  122. package/test/tmp/dist/types/client_post.js.map +1 -0
  123. package/test/tmp/dist/types/client_put.d.ts +5 -0
  124. package/test/tmp/dist/types/client_put.js +2 -0
  125. package/test/tmp/dist/types/client_put.js.map +1 -0
  126. package/test/tmp/dist/types/client_query.d.ts +18 -0
  127. package/test/tmp/dist/types/client_query.js +2 -0
  128. package/test/tmp/dist/types/client_query.js.map +1 -0
  129. package/test/tmp/dist/types/institution.d.ts +4 -0
  130. package/test/tmp/dist/types/institution.js +2 -0
  131. package/test/tmp/dist/types/institution.js.map +1 -0
  132. package/test/tmp/dist/types/institution_post.d.ts +4 -0
  133. package/test/tmp/dist/types/institution_post.js +2 -0
  134. package/test/tmp/dist/types/institution_post.js.map +1 -0
  135. package/test/tmp/dist/types/institution_put.d.ts +4 -0
  136. package/test/tmp/dist/types/institution_put.js +2 -0
  137. package/test/tmp/dist/types/institution_put.js.map +1 -0
  138. package/test/tmp/dist/types/institution_query.d.ts +14 -0
  139. package/test/tmp/dist/types/institution_query.js +2 -0
  140. package/test/tmp/dist/types/institution_query.js.map +1 -0
  141. package/test/tmp/dist/types/project.d.ts +7 -0
  142. package/test/tmp/dist/types/project.js +2 -0
  143. package/test/tmp/dist/types/project.js.map +1 -0
  144. package/test/tmp/dist/types/project_post.d.ts +7 -0
  145. package/test/tmp/dist/types/project_post.js +2 -0
  146. package/test/tmp/dist/types/project_post.js.map +1 -0
  147. package/test/tmp/dist/types/project_put.d.ts +7 -0
  148. package/test/tmp/dist/types/project_put.js +2 -0
  149. package/test/tmp/dist/types/project_put.js.map +1 -0
  150. package/test/tmp/dist/types/project_query.d.ts +27 -0
  151. package/test/tmp/dist/types/project_query.js +2 -0
  152. package/test/tmp/dist/types/project_query.js.map +1 -0
  153. package/test/tmp/dist/utils/utils.d.ts +11 -0
  154. package/test/tmp/dist/utils/utils.js +13 -0
  155. package/test/tmp/dist/utils/utils.js.map +1 -0
  156. package/test/tmp/package-lock.json +573 -0
  157. package/test/tmp/package.json +18 -0
  158. package/test/tmp/src/Brief_News_Category.ts +94 -0
  159. package/test/tmp/src/Client.ts +106 -0
  160. package/test/tmp/src/Institution.ts +103 -0
  161. package/test/tmp/src/Project.ts +94 -0
  162. package/test/tmp/src/index.ts +20 -0
  163. package/test/tmp/src/types/brief_news_category.ts +7 -0
  164. package/test/tmp/src/types/brief_news_category_post.ts +7 -0
  165. package/test/tmp/src/types/brief_news_category_put.ts +7 -0
  166. package/test/tmp/src/types/brief_news_category_query.ts +26 -0
  167. package/test/tmp/src/types/client.ts +5 -0
  168. package/test/tmp/src/types/client_post.ts +5 -0
  169. package/test/tmp/src/types/client_put.ts +5 -0
  170. package/test/tmp/src/types/client_query.ts +18 -0
  171. package/test/tmp/src/types/institution.ts +4 -0
  172. package/test/tmp/src/types/institution_post.ts +4 -0
  173. package/test/tmp/src/types/institution_put.ts +4 -0
  174. package/test/tmp/src/types/institution_query.ts +14 -0
  175. package/test/tmp/src/types/project.ts +7 -0
  176. package/test/tmp/src/types/project_post.ts +7 -0
  177. package/test/tmp/src/types/project_put.ts +7 -0
  178. package/test/tmp/src/types/project_query.ts +27 -0
  179. package/test/tmp/src/utils/utils.ts +17 -0
  180. package/test/tmp/tsconfig.json +14 -0
  181. package/tsconfig.json +14 -0
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "{{server_name}}-client-library",
3
+ "version": "1.0.0",
4
+ "description": "auto-generated client library for {{server_name}}",
5
+ "license": "ISC",
6
+ "author": "",
7
+ "type": "module",
8
+ "main": "dist/index.js",
9
+ "scripts": {
10
+ "build": "tsc"
11
+ },
12
+ "dependencies": {
13
+ "ky": "^1.9.0"
14
+ },
15
+ "devDependencies": {
16
+ "tsx": "^4.19.3"
17
+ }
18
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "node16",
4
+ "noImplicitAny": true,
5
+ "removeComments": true,
6
+ "preserveConstEnums": true,
7
+ "outDir": "dist",
8
+ "sourceMap": true,
9
+ "declaration": true,
10
+ //"declarationMap": true//check this
11
+ },
12
+ "include": ["src/**/*"],
13
+ "exclude": []
14
+ }
@@ -0,0 +1,4 @@
1
+ import { {{type_return}} } from "./{{path_type_return}}.js";
2
+ import { {{type_query}} } from "./{{path_type_query}}.js";
3
+ import { {{type_put}} } from "./{{path_type_put}}.js";
4
+ import { {{type_post}} } from "./{{path_type_post}}.js";
@@ -0,0 +1,17 @@
1
+ // this is copied directly to ./utils/utils.ts
2
+
3
+ export function encode_search_params(params: {[key: string]: string | number | boolean | string[]}): {[key: string]: string | number | boolean }{
4
+ let retval: {[key: string]: string | number | boolean } = {}
5
+ for(let [key, value] of Object.entries(params)){
6
+ if(Array.isArray(value)){
7
+ retval[key] = value.join(',')
8
+ } else {
9
+ retval[key] = value;
10
+ }
11
+ }
12
+
13
+ return retval;
14
+ }
15
+
16
+ export type Response<Q> = { data: Q }
17
+ export type Response_Multiple<Q> = { data: Q[] }
@@ -0,0 +1,3 @@
1
+ export function indent(count: number, input: string){
2
+ return '\t'.repeat(count) + input;
3
+ }
@@ -0,0 +1,140 @@
1
+ import { z, ZodType } from "zod/v4"
2
+ import mongoose, { Schema } from "mongoose";
3
+ import { magic_values } from "../../utils/mongoose_from_zod.js"
4
+ import { indent } from "./tab_indent.js"
5
+
6
+ /*export function mongoose_from_zod<T>(schema_name: string, zod_definition: z.core.$ZodType) {
7
+ let mongoose_schema = schema_from_zod(zod_definition);
8
+ return mongoose.model<T>(schema_name, mongoose_schema);
9
+ }*/
10
+
11
+ /*export function schema_from_zod(zod_definition: z.core.$ZodType): any {
12
+ let mongoose_schema = schema_entry_from_zod(zod_definition as z.ZodType);
13
+ delete mongoose_schema.type.required;
14
+ delete mongoose_schema.type._id;
15
+ return mongoose_schema.type;
16
+ }*/
17
+
18
+ export function type_from_zod(zod_definition: z.ZodType, indent_level: number): string[] {
19
+ if(!zod_definition._zod) {
20
+ console.log('ISSUE');
21
+ console.log(zod_definition);
22
+ }
23
+
24
+ switch (zod_definition._zod.def.type) {
25
+ case "string":
26
+ return ['string'];
27
+ case "number":
28
+ case "int":
29
+ return ['number'];
30
+ case "object":
31
+ return parse_object(zod_definition._zod.def as z.core.$ZodObjectDef, indent_level);
32
+ case "boolean":
33
+ return ['boolean'];
34
+ case "date":
35
+ return ['Date'];
36
+ case "undefined":
37
+ return ['undefined']
38
+ case "null":
39
+ return ['null']
40
+ case "array":
41
+ return parse_array(zod_definition._zod.def as z.core.$ZodArrayDef, indent_level)
42
+ /*
43
+ case "any":
44
+ return ["any"]
45
+ case "nullable":
46
+ //stuff is nullable in mongodb by default, so just return the ordinary results of the parse
47
+ //@ts-expect-error
48
+ return type_from_zod((zod_definition as z.core.$ZodNullable)._zod.def.innerType)*/
49
+ case "map":
50
+ return parse_map(zod_definition._zod.def as z.core.$ZodMapDef, indent_level);
51
+ case "enum":
52
+ return parse_enum(zod_definition._zod.def as z.core.$ZodEnumDef)
53
+ case "readonly":
54
+ throw new Error(`Zod type not yet supported by type_from_zod: ${zod_definition._zod.def.type});`)
55
+ case "default":
56
+ return type_from_zod((zod_definition._zod.def as z.core.$ZodDefaultDef).innerType as ZodType, indent_level);
57
+ case "custom":
58
+ let result = [];
59
+ if(!magic_values.has(zod_definition)) {
60
+ throw new Error(`could not find custom parser in the magic value dictionary for type_from_zod`)
61
+ }
62
+ let { override_type } = magic_values.get(zod_definition);
63
+
64
+ if(override_type === 'mongodb_id'){
65
+ result = ['string'];
66
+ } else {
67
+ throw new Error(`could not find custom parser for ${override_type} in the magic value dictionary`)
68
+ }
69
+
70
+ return result;
71
+ default:
72
+ //console.error(zod_definition._zod.def)
73
+ throw new Error("Cannot process zod type: " + zod_definition._zod.def.type);
74
+ }
75
+ }
76
+
77
+ function parse_object(def: z.core.$ZodObjectDef, indent_level: number): string[] {
78
+ let retval = ['{']
79
+ for(let [key, value] of Object.entries(def.shape)){
80
+ //@ts-ignore
81
+ let key_phrase = (value.safeParse(undefined).success || value._zod.def.type === 'optional') ? `"${key}"?:` : `"${key}":`;
82
+
83
+ let non_optional_type = value;
84
+
85
+ //@ts-ignore
86
+ while(non_optional_type._zod.def.type === 'optional'){ non_optional_type = non_optional_type._zod.def.innerType;}
87
+ let type_value = type_from_zod(non_optional_type as ZodType, indent_level + 1)
88
+
89
+ if(type_value.length > 1 ){
90
+ retval.push(indent(indent_level + 1, `${key_phrase} ${type_value[0]}`))
91
+ retval.push(...type_value.slice(1))
92
+ } else {
93
+ retval.push(indent(indent_level + 1, `${key_phrase} ${type_value[0]}`))
94
+ }
95
+ }
96
+
97
+ retval.push(indent(indent_level, '}'));
98
+ return retval;
99
+ }
100
+
101
+ function parse_array(def: z.core.$ZodArrayDef, indent_level: number): any {
102
+ //@ts-ignore
103
+ let retval = type_from_zod(def.element as z.ZodType, indent_level + 1)
104
+ retval[retval.length - 1] = `${retval[retval.length - 1]}[]`
105
+ return retval;
106
+ }
107
+
108
+ function parse_enum(def: z.core.$ZodEnumDef): any {
109
+ return [ `("${Object.values(def.entries).join('" | "')}")`];
110
+ }
111
+
112
+ function parse_map(def: z.core.$ZodMapDef, indent_level: number): any {
113
+ let retval = ['{']
114
+ //@ts-ignore
115
+ let key_phrase = `[key: ${type_from_zod(def.keyType, indent_level + 1)}]:`;
116
+
117
+ //@ts-ignore
118
+ let type_value = type_from_zod(def.valueType, indent_level + 1)
119
+
120
+ if(type_value.length > 1 ){
121
+ retval.push(indent(indent_level + 1, `${key_phrase} ${type_value[0]}`))
122
+ retval.push(...type_value.slice(1))
123
+ } else {
124
+ retval.push(indent(indent_level + 1, `${key_phrase} ${type_value[0]}`))
125
+ }
126
+
127
+ retval.push(indent(indent_level, '}'));
128
+ return retval;
129
+ }
130
+
131
+ function parse_optional(def: z.core.$ZodOptionalDef): any {
132
+ //@ts-ignore
133
+ let type_definition = schema_entry_from_zod(def.innerType);
134
+ type_definition.required = false;
135
+ return type_definition;
136
+ }
137
+
138
+ function parse_mongodb_id(def: z.core.$ZodCustomDef): any {
139
+ return { type: Schema.Types.ObjectId };
140
+ }
@@ -0,0 +1,149 @@
1
+ export class Cache<T> {
2
+ timeout_map: Map<string, NodeJS.Timeout>;
3
+ base_cache: Map<string, T>;
4
+ refetch_map: Map<string, Promise<T>>;
5
+ duration: number;
6
+
7
+ constructor(duration: number) {
8
+ this.timeout_map = new Map<string, NodeJS.Timeout>();
9
+ this.base_cache = new Map<string, T>();
10
+ this.refetch_map = new Map<string, Promise<T>>();
11
+ this.duration = duration;
12
+ }
13
+
14
+ get(key: string): T {
15
+ this.reset_expiry_timer_for(key);
16
+ return this.base_cache.get(key);
17
+ }
18
+
19
+ set(key: string, value: T) {
20
+ this.base_cache.set(key, value);
21
+ this.reset_expiry_timer_for(key);
22
+ }
23
+
24
+ reset_expiry_timer_for(key: string) {
25
+ // if the cache already has a timeout for this key,
26
+ // stop the countdown. We don't want a key that was
27
+ //recently used getting deleted by an old timeout.
28
+ if(this.timeout_map.has(key)) {
29
+ clearTimeout(this.timeout_map.get(key));
30
+ }
31
+
32
+ // add a timeout to the timeout map. When the timeout runs out, it'll remove the key from the cache.
33
+ // TODO: should I also give the cache a maximum size? It'd be cool if it had the ability to max out
34
+ // at a certain number of elements and reacted by throwing away the oldest ones. Definitely a premature
35
+ // optimization, though.
36
+ if(this.base_cache.has(key)){
37
+ this.timeout_map.set(key, setTimeout(() => {
38
+ this.base_cache.delete(key);
39
+ this.timeout_map.delete(key);
40
+ }, this.duration));
41
+ }
42
+ }
43
+
44
+ delete(key: string) {
45
+ this.base_cache.delete(key);
46
+ clearTimeout(this.timeout_map.get(key));
47
+ this.timeout_map.delete(key);
48
+ }
49
+
50
+ async first_get_then_fetch(key: string, fetch_function: () => Promise<T>): Promise<T> {
51
+ // if we already have the key in the cache, just return it.
52
+ if (this.base_cache.has(key)) { return this.get(key); }
53
+
54
+ // ...otherwise, if we're already fetching the data, wait for
55
+ // the fetch to finish and return its results.
56
+ if(this.refetch_map.has(key)){
57
+ let result_value;
58
+ try{
59
+ result_value = await this.refetch_map.get(key);
60
+ } catch(err){
61
+ return Promise.reject(err);
62
+ }
63
+ return result_value;
64
+ }
65
+
66
+ // ...if we're not already fetching the data,...
67
+ // ...start to fetch it
68
+ let result_value;
69
+ let fetch_promise = fetch_function().finally(() => {
70
+ this.refetch_map.delete(key);
71
+ });
72
+ // ...store the fetch promise in the refetch_map so that any fetch
73
+ // operations on the same key can use the fetch that's already being
74
+ // executed instead of starting a new fetch
75
+ this.refetch_map.set(key, fetch_promise)
76
+
77
+ // ...wait for the results,...
78
+ try {
79
+ result_value = await fetch_promise;
80
+ } catch(err){
81
+ // ...throwing an error if necessary,...
82
+ return Promise.reject(err);
83
+ }
84
+
85
+ // ...and setting & returning the restults if there's no error.
86
+ this.set(key, result_value);
87
+ return result_value;
88
+ }
89
+
90
+ async first_fetch_then_refresh(key: string, fetch_function: () => Promise<T>): Promise<T> {
91
+ /*console.log('first_fetch_then_refresh')
92
+ console.log(this.refetch_map.has(key))
93
+ console.log(this.base_cache.has(key))*/
94
+
95
+ // if we're already refetching the value for the key, don't
96
+ // start refetching the value. It doesn't make sense to refetch
97
+ // a value we're already fetching. Wait for the existing refetch if necessary.
98
+ if(this.refetch_map.has(key)){
99
+ // if we already have the value, we can return anyway.
100
+ if (this.base_cache.has(key)) { return this.get(key); }
101
+
102
+ // otherwise, return whatever is returned by the existing fetch promise.
103
+ let result_value;
104
+ try{
105
+ result_value = await this.refetch_map.get(key);
106
+ } catch(err){
107
+ return Promise.reject(err);
108
+ }
109
+ return result_value;
110
+ }
111
+
112
+ // if we already have the key, start a fetch promise and return the existing key
113
+ if (this.base_cache.has(key)) {
114
+ let result_value = this.get(key);
115
+
116
+ let fetch_promise = fetch_function().finally(() => {
117
+ this.refetch_map.delete(key);
118
+ });
119
+ // ...store the fetch promise in the refetch_map so that any fetch
120
+ // operations on the same key can use the fetch that's already being
121
+ // executed instead of starting a new fetch
122
+ this.refetch_map.set(key, fetch_promise)
123
+
124
+ return result_value;
125
+ }
126
+
127
+ // if we don't have the key, generate a fetch promise and return whatever it returns.
128
+ let retval;
129
+ let fetch_promise = fetch_function().finally(() => {
130
+ this.refetch_map.delete(key);
131
+ });
132
+ // ...store the fetch promise in the refetch_map so that any fetch
133
+ // operations on the same key can use the fetch that's already being
134
+ // executed instead of starting a new fetch
135
+ this.refetch_map.set(key, fetch_promise)
136
+
137
+ // ...wait for the results,...
138
+ try {
139
+ retval = await fetch_promise;
140
+ } catch(err){
141
+ // ...throwing an error if necessary,...
142
+ return Promise.reject(err);
143
+ }
144
+
145
+ // ...and setting & returning the restults if there's no error.
146
+ this.set(key, retval);
147
+ return retval;
148
+ }
149
+ }
@@ -0,0 +1,191 @@
1
+ import { z } from "zod/v4"
2
+ import mongoose, { Schema } from "mongoose";
3
+
4
+ export const magic_values = z.registry<{ override_type: string }>();
5
+
6
+ //export const z_mongodb_id = z.string().length(24).describe('F_Mongodb_ID');
7
+ //export const mongodb_id = () => z_mongodb_id;
8
+ const underlying_mongodb_id_validator = z.string().length(24);
9
+ export const z_mongodb_id = z.custom<string>((val) => {
10
+ if(!val){ return false; }
11
+ return underlying_mongodb_id_validator.parse(val) === val;
12
+ }).meta({
13
+ "type": "string",
14
+ "format": "string",
15
+ }).register(magic_values, {override_type: 'mongodb_id'});
16
+ //export const z_mongodb_id = underlying_mongodb_id_validator.register(magic_values, {override_type: 'mongodb_id'});
17
+
18
+ export function mongoose_from_zod<T>(schema_name: string, zod_definition: z.core.$ZodType) {
19
+ let mongoose_schema = schema_from_zod(zod_definition);
20
+ return mongoose.model<T>(schema_name, mongoose_schema);
21
+ }
22
+
23
+ export function schema_from_zod(zod_definition: z.core.$ZodType): any {
24
+ let mongoose_schema = schema_entry_from_zod(zod_definition as z.ZodType);
25
+ delete mongoose_schema.type.required;
26
+ delete mongoose_schema.type._id;
27
+ return mongoose_schema.type;
28
+ }
29
+
30
+ export function schema_entry_from_zod(zod_definition: z.ZodType, loop_detector: Set<any> = new Set()): any {
31
+ if(!zod_definition) {
32
+ console.error('ISSUE');
33
+ console.error(zod_definition);
34
+ }
35
+
36
+ let result;
37
+ switch (zod_definition._zod.def.type) {
38
+ case "string":
39
+ result = parse_string(zod_definition._zod.def as z.core.$ZodStringDef);
40
+ result.required = !zod_definition.safeParse(undefined).success
41
+ return result;
42
+ case "number":
43
+ case "int":
44
+ result = parse_number(zod_definition._zod.def as z.core.$ZodNumberDef);
45
+ result.required = !zod_definition.safeParse(undefined).success
46
+ return result;
47
+ case "object":
48
+ result = parse_object(zod_definition._zod.def as z.core.$ZodObjectDef, loop_detector);
49
+ result.required = !zod_definition.safeParse(undefined).success
50
+ return result;
51
+ case "boolean":
52
+ result = parse_boolean(zod_definition._zod.def as z.core.$ZodBooleanDef);
53
+ result.required = !zod_definition.safeParse(undefined).success
54
+ return result;
55
+ case "date":
56
+ result = parse_date(zod_definition._zod.def as z.core.$ZodDateDef);
57
+ result.required = !zod_definition.safeParse(undefined).success
58
+ return result;
59
+ case "undefined":
60
+ throw new Error(`Zod type not yet supported: ${zod_definition._zod.def.type});`)
61
+ case "null":
62
+ throw new Error(`Zod type not yet supported: ${zod_definition._zod.def.type});`)
63
+ case "array" :
64
+ result = parse_array(zod_definition._zod.def as z.core.$ZodArrayDef, loop_detector);
65
+ result.required = !zod_definition.safeParse(undefined).success
66
+ return result;
67
+ case "nullable":
68
+ // stuff is nullable in mongodb by default, so just return the ordinary results of the parse
69
+ //@ts-expect-error
70
+ return schema_entry_from_zod((zod_definition as z.core.$ZodNullable)._zod.def.innerType, loop_detector)
71
+ case "optional":
72
+ return parse_optional(zod_definition._zod.def as z.core.$ZodOptionalDef, loop_detector);
73
+ case "map":
74
+ result = parse_map(zod_definition._zod.def as z.core.$ZodMapDef, loop_detector);
75
+ result.required = !zod_definition.safeParse(undefined).success
76
+ return result;
77
+ case "any" :
78
+ result = { type: Schema.Types.Mixed };
79
+ case "default":
80
+ result = parse_default(zod_definition._zod.def as z.core.$ZodDefaultDef, loop_detector);
81
+ result.required = true;
82
+ return result;
83
+ case "enum":
84
+ result = parse_enum(zod_definition._zod.def as z.core.$ZodEnumDef)
85
+ result.required = !zod_definition.safeParse(undefined).success
86
+ return result;
87
+ case "union":
88
+ result = parse_union(zod_definition._zod.def as z.core.$ZodUnionDef)
89
+ result.required = !zod_definition.safeParse(undefined).success
90
+ return result;
91
+ case "readonly":
92
+ throw new Error(`Zod type not yet supported: ${zod_definition._zod.def.type});`)
93
+ case "custom":
94
+ if(!magic_values.has(zod_definition)) {
95
+ throw new Error(`could not find custom parser in the magic value dictionary`)
96
+ }
97
+ let { override_type } = magic_values.get(zod_definition);
98
+
99
+ if(override_type === 'mongodb_id'){
100
+ result = parse_mongodb_id(zod_definition._zod.def as z.core.$ZodCustomDef)
101
+ } else {
102
+ throw new Error(`could not find custom parser for ${override_type} in the magic value dictionary`)
103
+ }
104
+
105
+ result.required = !zod_definition.safeParse(undefined).success;
106
+ return result;
107
+ default:
108
+ throw new Error("Cannot process zod type: " + zod_definition._zod.def.type);
109
+ }
110
+ }
111
+
112
+ function parse_object(def: z.core.$ZodObjectDef, loop_detector: Set<any>): any {
113
+ if(loop_detector.has(def)) {
114
+ return {type: Schema.Types.Mixed, required: true}
115
+ }
116
+ loop_detector.add(def);
117
+
118
+ let retval = {} as any;
119
+ for(let [key, value] of Object.entries(def.shape)){
120
+ //@ts-ignore
121
+ retval[key] = schema_entry_from_zod(value, loop_detector);
122
+ }
123
+ return {type: retval, required: true};
124
+ }
125
+
126
+ function parse_array(def: z.core.$ZodArrayDef, loop_detector: Set<any>): any {
127
+ //@ts-ignore
128
+ let retval = { type: [schema_entry_from_zod(def.element, loop_detector)] } as any;
129
+ retval.required = true;
130
+ return retval;
131
+ }
132
+
133
+ function parse_enum(def: z.core.$ZodEnumDef): any {
134
+ let retval = { type: String } as any;
135
+ retval.required = true;
136
+ return retval;
137
+ }
138
+
139
+ function parse_union(def: z.core.$ZodUnionDef): any {
140
+ let retval = { type: Schema.Types.Mixed } as any;
141
+ retval.required = true;
142
+ return retval;
143
+ }
144
+
145
+ function parse_map(def: z.core.$ZodMapDef, loop_detector: Set<any>): any {
146
+ if(def.keyType._zod.def.type !== 'string') { throw new Error('mongoDB only supports maps where the key is a string.'); }
147
+ //@ts-ignore
148
+ let retval = { type: Schema.Types.Map, of: schema_entry_from_zod(def.valueType, loop_detector), required: true}
149
+ retval.required = true;
150
+ return retval;
151
+ }
152
+
153
+ function parse_string(def: z.core.$ZodStringDef): any {
154
+ let retval = { type: String } as any;
155
+ // for fixing the optional issue
156
+ // https://github.com/colinhacks/zod/issues/4824
157
+ return retval;
158
+ }
159
+
160
+ function parse_number(def: z.core.$ZodNumberDef): any {
161
+ let retval = { type: Number } as any;
162
+ return retval;
163
+ }
164
+
165
+ function parse_boolean(def: z.core.$ZodBooleanDef): any {
166
+ let retval = { type: Boolean } as any;
167
+ return retval;
168
+ }
169
+
170
+ function parse_date(def: z.core.$ZodDateDef): any {
171
+ let retval = { type: Date } as any;
172
+ return retval;
173
+ }
174
+
175
+ function parse_default(def: z.core.$ZodDefaultDef, loop_detector: Set<any>): any {
176
+ //@ts-ignore
177
+ let type_definition = schema_entry_from_zod(def.innerType, loop_detector);
178
+ type_definition.default = def.defaultValue;
179
+ return type_definition;
180
+ }
181
+
182
+ function parse_optional(def: z.core.$ZodOptionalDef, loop_detector: Set<any>): any {
183
+ //@ts-ignore
184
+ let type_definition = schema_entry_from_zod(def.innerType, loop_detector);
185
+ type_definition.required = false;
186
+ return type_definition;
187
+ }
188
+
189
+ function parse_mongodb_id(def: z.core.$ZodCustomDef): any {
190
+ return { type: Schema.Types.ObjectId };
191
+ }
@@ -0,0 +1,75 @@
1
+ import { z } from "zod/v4"
2
+ import { magic_values } from "./mongoose_from_zod.js";
3
+
4
+ export function pretty_print(zod_definition: z.ZodObject){
5
+ console.log(parse_object(zod_definition._zod.def, new Set()));
6
+ }
7
+
8
+ function parse_any(zod_definition: z.ZodType, loop_detector: Set<any> = new Set()): any {
9
+ switch (zod_definition._zod.def.type) {
10
+ case "enum":
11
+ return parse_enum(zod_definition._zod.def as z.core.$ZodEnumDef);
12
+ case "string":
13
+ return 'string';
14
+ case "number":
15
+ case "int":
16
+ return 'number';
17
+ case "object":
18
+ return parse_object(zod_definition._zod.def as z.core.$ZodObjectDef, loop_detector);
19
+ case "boolean":
20
+ return 'boolean';
21
+ case "date":
22
+ return 'date';
23
+ case "array":
24
+ return parse_array(zod_definition._zod.def as z.core.$ZodArrayDef, loop_detector)
25
+ case "union":
26
+ return parse_union(zod_definition._zod.def as z.core.$ZodUnionDef,)
27
+ case "custom":
28
+ if(!magic_values.has(zod_definition)) {
29
+ throw new Error(`could not find custom parser in the magic value dictionary`)
30
+ }
31
+ let { override_type } = magic_values.get(zod_definition);
32
+
33
+ if(override_type === 'mongodb_id'){
34
+ return 'mongodb_id';
35
+ } else {
36
+ throw new Error(`could not find custom parser for ${override_type} in the magic value dictionary`)
37
+ }
38
+ case "default":
39
+ //@ts-ignore
40
+ return parse_any((zod_definition._zod.def as z.core.$ZodDefaultDef).innerType, loop_detector)
41
+ case "optional":
42
+ //@ts-ignore
43
+ return parse_any((zod_definition._zod.def as z.core.$ZodOptionalDef).innerType, loop_detector)
44
+ default:
45
+ return `unknown type ${zod_definition._zod.def.type}`
46
+ }
47
+ }
48
+
49
+ function parse_array(def: z.core.$ZodArrayDef, loop_detector: Set<any>) {
50
+ //@ts-ignore
51
+ return [parse_any(def.element, loop_detector)];
52
+ }
53
+
54
+ function parse_object(def: z.core.$ZodObjectDef, loop_detector: Set<any>) {
55
+ if(loop_detector.has(def)) {
56
+ return 'RECURSION';
57
+ }
58
+ loop_detector.add(def);
59
+
60
+ let retval = {} as any;
61
+ for(let [key, value] of Object.entries(def.shape)){
62
+ //@ts-expect-error
63
+ retval[key] = parse_any(value, loop_detector);
64
+ }
65
+ return retval;
66
+ }
67
+
68
+ function parse_union(def: z.core.$ZodUnionDef) {
69
+ //@ts-expect-error
70
+ return { or: def.options.map(ele => parse_any(ele)) }
71
+ }
72
+
73
+ function parse_enum(definition: z.core.$ZodEnumDef) {
74
+ return { enum: definition.entries}
75
+ }