@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.
- package/.mocharc.json +5 -0
- package/dist/F_Client_Collection_Registry.d.ts +18 -0
- package/dist/F_Client_Collection_Registry.js +36 -0
- package/dist/F_Client_Collection_Registry.js.map +1 -0
- package/dist/F_Collection.d.ts +21 -0
- package/dist/F_Collection.js +36 -0
- package/dist/F_Collection.js.map +1 -0
- package/dist/F_Collection_Registry.d.ts +11 -0
- package/dist/F_Collection_Registry.js +18 -0
- package/dist/F_Collection_Registry.js.map +1 -0
- package/dist/F_Compile.d.ts +4 -0
- package/dist/F_Compile.js +298 -0
- package/dist/F_Compile.js.map +1 -0
- package/dist/F_Security_Models/F_SM_Open_Access.d.ts +11 -0
- package/dist/F_Security_Models/F_SM_Open_Access.js +14 -0
- package/dist/F_Security_Models/F_SM_Open_Access.js.map +1 -0
- package/dist/F_Security_Models/F_SM_Ownership.d.ts +12 -0
- package/dist/F_Security_Models/F_SM_Ownership.js +46 -0
- package/dist/F_Security_Models/F_SM_Ownership.js.map +1 -0
- package/dist/F_Security_Models/F_SM_Role_Membership.d.ts +19 -0
- package/dist/F_Security_Models/F_SM_Role_Membership.js +73 -0
- package/dist/F_Security_Models/F_SM_Role_Membership.js.map +1 -0
- package/dist/F_Security_Models/F_Security_Model.d.ts +41 -0
- package/dist/F_Security_Models/F_Security_Model.js +29 -0
- package/dist/F_Security_Models/F_Security_Model.js.map +1 -0
- package/dist/code_generation/generate_client_library.d.ts +4 -0
- package/dist/code_generation/generate_client_library.js +158 -0
- package/dist/code_generation/generate_client_library.js.map +1 -0
- package/dist/code_generation/templates/.gitignore.mustache +383 -0
- package/dist/code_generation/templates/collection.mustache +106 -0
- package/dist/code_generation/templates/main.mustache +24 -0
- package/dist/code_generation/templates/package.json.mustache +18 -0
- package/dist/code_generation/templates/tsconfig.json.mustache +14 -0
- package/dist/code_generation/templates/types.mustache +4 -0
- package/dist/code_generation/templates/utils.ts.mustache +17 -0
- package/dist/code_generation/utils/tab_indent.d.ts +1 -0
- package/dist/code_generation/utils/tab_indent.js +4 -0
- package/dist/code_generation/utils/tab_indent.js.map +1 -0
- package/dist/code_generation/utils/type_from_zod.d.ts +2 -0
- package/dist/code_generation/utils/type_from_zod.js +102 -0
- package/dist/code_generation/utils/type_from_zod.js.map +1 -0
- package/dist/utils/cache.d.ts +13 -0
- package/dist/utils/cache.js +101 -0
- package/dist/utils/cache.js.map +1 -0
- package/dist/utils/mongoose_from_zod.d.ts +13 -0
- package/dist/utils/mongoose_from_zod.js +164 -0
- package/dist/utils/mongoose_from_zod.js.map +1 -0
- package/dist/utils/pretty_print_zod.d.ts +2 -0
- package/dist/utils/pretty_print_zod.js +63 -0
- package/dist/utils/pretty_print_zod.js.map +1 -0
- package/dist/utils/query_object_to_mongodb_query.d.ts +3 -0
- package/dist/utils/query_object_to_mongodb_query.js +61 -0
- package/dist/utils/query_object_to_mongodb_query.js.map +1 -0
- package/dist/utils/query_validator_from_zod.d.ts +6 -0
- package/dist/utils/query_validator_from_zod.js +216 -0
- package/dist/utils/query_validator_from_zod.js.map +1 -0
- package/package.json +36 -0
- package/src/F_Collection.ts +50 -0
- package/src/F_Collection_Registry.ts +29 -0
- package/src/F_Compile.ts +368 -0
- package/src/F_Security_Models/F_SM_Open_Access.ts +21 -0
- package/src/F_Security_Models/F_SM_Ownership.ts +72 -0
- package/src/F_Security_Models/F_SM_Role_Membership.ts +87 -0
- package/src/F_Security_Models/F_Security_Model.ts +85 -0
- package/src/code_generation/generate_client_library.ts +197 -0
- package/src/code_generation/templates/.gitignore.mustache +383 -0
- package/src/code_generation/templates/collection.mustache +106 -0
- package/src/code_generation/templates/main.mustache +24 -0
- package/src/code_generation/templates/package.json.mustache +18 -0
- package/src/code_generation/templates/tsconfig.json.mustache +14 -0
- package/src/code_generation/templates/types.mustache +4 -0
- package/src/code_generation/templates/utils.ts.mustache +17 -0
- package/src/code_generation/utils/tab_indent.ts +3 -0
- package/src/code_generation/utils/type_from_zod.ts +140 -0
- package/src/utils/cache.ts +149 -0
- package/src/utils/mongoose_from_zod.ts +191 -0
- package/src/utils/pretty_print_zod.ts +75 -0
- package/src/utils/query_object_to_mongodb_query.ts +73 -0
- package/src/utils/query_validator_from_zod.ts +246 -0
- package/test/0_0_mongoose_from_zod.test.ts +260 -0
- package/test/0_1_query_validator_from_zod.test.ts +518 -0
- package/test/0_2_query_validator_to_mongodb_query.test.ts +365 -0
- package/test/0_3_cache.test.ts +204 -0
- package/test/1_0_basic_server.test.ts +530 -0
- package/test/1_1_security_ownership.test.ts +328 -0
- package/test/1_2_role_membership.test.ts +731 -0
- package/test/2_0_client_library_basic_type_generation.test.ts +444 -0
- package/test/2_0_client_library_query_type_generation.test.ts +352 -0
- package/test/2_1_client_library_generation.test.ts +255 -0
- package/test/tmp/dist/Brief_News_Category.d.ts +16 -0
- package/test/tmp/dist/Brief_News_Category.js +85 -0
- package/test/tmp/dist/Brief_News_Category.js.map +1 -0
- package/test/tmp/dist/Client.d.ts +19 -0
- package/test/tmp/dist/Client.js +97 -0
- package/test/tmp/dist/Client.js.map +1 -0
- package/test/tmp/dist/Institution.d.ts +18 -0
- package/test/tmp/dist/Institution.js +94 -0
- package/test/tmp/dist/Institution.js.map +1 -0
- package/test/tmp/dist/Project.d.ts +16 -0
- package/test/tmp/dist/Project.js +85 -0
- package/test/tmp/dist/Project.js.map +1 -0
- package/test/tmp/dist/index.d.ts +4 -0
- package/test/tmp/dist/index.js +14 -0
- package/test/tmp/dist/index.js.map +1 -0
- package/test/tmp/dist/types/brief_news_category.d.ts +7 -0
- package/test/tmp/dist/types/brief_news_category.js +2 -0
- package/test/tmp/dist/types/brief_news_category.js.map +1 -0
- package/test/tmp/dist/types/brief_news_category_post.d.ts +7 -0
- package/test/tmp/dist/types/brief_news_category_post.js +2 -0
- package/test/tmp/dist/types/brief_news_category_post.js.map +1 -0
- package/test/tmp/dist/types/brief_news_category_put.d.ts +7 -0
- package/test/tmp/dist/types/brief_news_category_put.js +2 -0
- package/test/tmp/dist/types/brief_news_category_put.js.map +1 -0
- package/test/tmp/dist/types/brief_news_category_query.d.ts +26 -0
- package/test/tmp/dist/types/brief_news_category_query.js +2 -0
- package/test/tmp/dist/types/brief_news_category_query.js.map +1 -0
- package/test/tmp/dist/types/client.d.ts +5 -0
- package/test/tmp/dist/types/client.js +2 -0
- package/test/tmp/dist/types/client.js.map +1 -0
- package/test/tmp/dist/types/client_post.d.ts +5 -0
- package/test/tmp/dist/types/client_post.js +2 -0
- package/test/tmp/dist/types/client_post.js.map +1 -0
- package/test/tmp/dist/types/client_put.d.ts +5 -0
- package/test/tmp/dist/types/client_put.js +2 -0
- package/test/tmp/dist/types/client_put.js.map +1 -0
- package/test/tmp/dist/types/client_query.d.ts +18 -0
- package/test/tmp/dist/types/client_query.js +2 -0
- package/test/tmp/dist/types/client_query.js.map +1 -0
- package/test/tmp/dist/types/institution.d.ts +4 -0
- package/test/tmp/dist/types/institution.js +2 -0
- package/test/tmp/dist/types/institution.js.map +1 -0
- package/test/tmp/dist/types/institution_post.d.ts +4 -0
- package/test/tmp/dist/types/institution_post.js +2 -0
- package/test/tmp/dist/types/institution_post.js.map +1 -0
- package/test/tmp/dist/types/institution_put.d.ts +4 -0
- package/test/tmp/dist/types/institution_put.js +2 -0
- package/test/tmp/dist/types/institution_put.js.map +1 -0
- package/test/tmp/dist/types/institution_query.d.ts +14 -0
- package/test/tmp/dist/types/institution_query.js +2 -0
- package/test/tmp/dist/types/institution_query.js.map +1 -0
- package/test/tmp/dist/types/project.d.ts +7 -0
- package/test/tmp/dist/types/project.js +2 -0
- package/test/tmp/dist/types/project.js.map +1 -0
- package/test/tmp/dist/types/project_post.d.ts +7 -0
- package/test/tmp/dist/types/project_post.js +2 -0
- package/test/tmp/dist/types/project_post.js.map +1 -0
- package/test/tmp/dist/types/project_put.d.ts +7 -0
- package/test/tmp/dist/types/project_put.js +2 -0
- package/test/tmp/dist/types/project_put.js.map +1 -0
- package/test/tmp/dist/types/project_query.d.ts +27 -0
- package/test/tmp/dist/types/project_query.js +2 -0
- package/test/tmp/dist/types/project_query.js.map +1 -0
- package/test/tmp/dist/utils/utils.d.ts +11 -0
- package/test/tmp/dist/utils/utils.js +13 -0
- package/test/tmp/dist/utils/utils.js.map +1 -0
- package/test/tmp/package-lock.json +573 -0
- package/test/tmp/package.json +18 -0
- package/test/tmp/src/Brief_News_Category.ts +94 -0
- package/test/tmp/src/Client.ts +106 -0
- package/test/tmp/src/Institution.ts +103 -0
- package/test/tmp/src/Project.ts +94 -0
- package/test/tmp/src/index.ts +20 -0
- package/test/tmp/src/types/brief_news_category.ts +7 -0
- package/test/tmp/src/types/brief_news_category_post.ts +7 -0
- package/test/tmp/src/types/brief_news_category_put.ts +7 -0
- package/test/tmp/src/types/brief_news_category_query.ts +26 -0
- package/test/tmp/src/types/client.ts +5 -0
- package/test/tmp/src/types/client_post.ts +5 -0
- package/test/tmp/src/types/client_put.ts +5 -0
- package/test/tmp/src/types/client_query.ts +18 -0
- package/test/tmp/src/types/institution.ts +4 -0
- package/test/tmp/src/types/institution_post.ts +4 -0
- package/test/tmp/src/types/institution_put.ts +4 -0
- package/test/tmp/src/types/institution_query.ts +14 -0
- package/test/tmp/src/types/project.ts +7 -0
- package/test/tmp/src/types/project_post.ts +7 -0
- package/test/tmp/src/types/project_put.ts +7 -0
- package/test/tmp/src/types/project_query.ts +27 -0
- package/test/tmp/src/utils/utils.ts +17 -0
- package/test/tmp/tsconfig.json +14 -0
- 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,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,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
|
+
}
|