@nullix/zod-mongoose-studio 3.3.0 → 3.3.1
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/.output/nitro.json +1 -1
- package/.output/public/_fonts/57NSSoFy1VLVs2gqly8Ls9awBnZMFyXGrefpmqvdqmc-zJfbBtpgM4cDmcXBsqZNW79_kFnlpPd62b48glgdydA.woff2 +0 -0
- package/.output/public/_fonts/8VR2wSMN-3U4NbWAVYXlkRV6hA0jFBXP-0RtL3X7fko-x2gYI4qfmkRdxyQQUPaBZdZdgl1TeVrquF_TxHeM4lM.woff2 +0 -0
- package/.output/public/_fonts/GsKUclqeNLJ96g5AU593ug6yanivOiwjW_7zESNPChw-jHA4tBeM1bjF7LATGUpfBuSTyomIFrWBTzjF7txVYfg.woff2 +0 -0
- package/.output/public/_fonts/Ld1FnTo3yTIwDyGfTQ5-Fws9AWsCbKfMvgxduXr7JcY-W25bL8NF1fjpLRSOgJb7RoZPHqGQNwMTM7S9tHVoxx8.woff2 +0 -0
- package/.output/public/_fonts/NdzqRASp2bovDUhQT1IRE_EMqKJ2KYQdTCfFcBvL8yw-KhwZiS86o3fErOe5GGMExHUemmI_dBfaEFxjISZrBd0.woff2 +0 -0
- package/.output/public/_fonts/iTkrULNFJJkTvihIg1Vqi5IODRH_9btXCioVF5l98I8-AndUyau2HR2felA_ra8V2mutQgschhasE5FD1dXGJX8.woff2 +0 -0
- package/.output/public/_nuxt/{qQyvCkbV.js → 5Tu2WUJz.js} +1 -1
- package/.output/public/_nuxt/B0N6xv5E.js +1 -0
- package/.output/public/_nuxt/{DjPT-NCE.js → B0cUlOSM.js} +1 -1
- package/.output/public/_nuxt/B9paGgN0.js +33 -0
- package/.output/public/_nuxt/{IxYCG8hB.js → BCIlnd0g.js} +1 -1
- package/.output/public/_nuxt/{ikxafTHA.js → BNDnV2op.js} +180 -184
- package/.output/public/_nuxt/{Ce-T_1aD.js → BO4hqv6q.js} +1 -1
- package/.output/public/_nuxt/{Bd32GOUy.js → BOsJJ_ZU.js} +1 -1
- package/.output/public/_nuxt/{BzhSDy30.js → C2BHp-MG.js} +1 -1
- package/.output/public/_nuxt/{Cs8GXJCB.js → CKULsFUz.js} +1 -1
- package/.output/public/_nuxt/CMH9iX_E.js +6 -0
- package/.output/public/_nuxt/{tzj4nvDE.js → CzbCpNnc.js} +2 -2
- package/.output/public/_nuxt/{CC3n3Aga.js → D87QMS6j.js} +1 -1
- package/.output/public/_nuxt/DA0pQuyP.js +27 -0
- package/.output/public/_nuxt/{B7P0wQ35.js → DcJ7oPvZ.js} +1 -1
- package/.output/public/_nuxt/{Ick_23I4.js → Dce9SnGa.js} +1 -1
- package/.output/public/_nuxt/{CAd-qHfI.js → DpKrXpkK.js} +1 -1
- package/.output/public/_nuxt/DsnIpnT9.js +1 -0
- package/.output/public/_nuxt/{CNZorjDf.js → I3Ucam2s.js} +1 -1
- package/.output/public/_nuxt/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/51f1351c-0667-4235-9ddc-16582a51ce03.json +1 -0
- package/.output/public/_nuxt/{entry.J_CbC0oY.css → entry.B-vs1tRH.css} +1 -1
- package/.output/public/_nuxt/error-404.CTSXlFYa.css +1 -0
- package/.output/public/_nuxt/error-500.DqEfQnVZ.css +1 -0
- package/.output/public/_nuxt/{nDZOJ7rN.js → o-NCh2rp.js} +2 -2
- package/.output/public/_nuxt/{qwxNOyg7.js → sKEJiAHO.js} +1 -1
- package/.output/public/_nuxt/{81vqh61j.js → tqQNlhT9.js} +1 -1
- package/.output/public/_nuxt/ypRHVLFO.js +1 -0
- package/.output/server/chunks/_/error-500.mjs.map +1 -1
- package/.output/server/chunks/build/client.precomputed.mjs +1 -1
- package/.output/server/chunks/build/{error-404-BN-7rNE4.mjs → error-404-KRkA0DPI.mjs} +10 -4
- package/.output/server/chunks/build/error-404-KRkA0DPI.mjs.map +1 -0
- package/.output/server/chunks/build/error-404-styles.I0NYw-Bn.mjs +8 -0
- package/.output/server/chunks/build/error-404-styles.I0NYw-Bn.mjs.map +1 -0
- package/.output/server/chunks/build/{error-500-DhIQ-2yt.mjs → error-500-DIIFf55p.mjs} +10 -4
- package/.output/server/chunks/build/error-500-DIIFf55p.mjs.map +1 -0
- package/.output/server/chunks/build/error-500-styles.D1cs9hQy.mjs +8 -0
- package/.output/server/chunks/build/error-500-styles.D1cs9hQy.mjs.map +1 -0
- package/.output/server/chunks/build/index-CB1N4gax.mjs +557 -0
- package/.output/server/chunks/build/index-CB1N4gax.mjs.map +1 -0
- package/.output/server/chunks/build/server.mjs +326 -248
- package/.output/server/chunks/build/server.mjs.map +1 -1
- package/.output/server/chunks/build/styles.mjs +4 -4
- package/.output/server/chunks/nitro/nitro.mjs +2328 -861
- package/.output/server/chunks/nitro/nitro.mjs.map +1 -1
- package/.output/server/chunks/routes/api/editor-types.get.mjs +44 -0
- package/.output/server/chunks/routes/api/editor-types.get.mjs.map +1 -0
- package/.output/server/chunks/routes/api/parse.post.mjs +26 -1448
- package/.output/server/chunks/routes/api/parse.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/resolve.get.mjs +6 -0
- package/.output/server/chunks/routes/api/resolve.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/studio/run.post.mjs +35 -0
- package/.output/server/chunks/routes/api/studio/run.post.mjs.map +1 -0
- package/.output/server/chunks/routes/renderer.mjs +1 -1
- package/.output/server/chunks/routes/renderer.mjs.map +1 -1
- package/.output/server/index.mjs +7 -1
- package/.output/server/index.mjs.map +1 -1
- package/.output/server/node_modules/acorn/dist/acorn.mjs +6266 -0
- package/.output/server/node_modules/acorn/package.json +50 -0
- package/.output/server/node_modules/ip-address/dist/common.js +112 -4
- package/.output/server/node_modules/ip-address/dist/ipv4.js +322 -87
- package/.output/server/node_modules/ip-address/dist/ipv6.js +654 -199
- package/.output/server/node_modules/ip-address/dist/v4/constants.js +45 -2
- package/.output/server/node_modules/ip-address/dist/v6/constants.js +55 -3
- package/.output/server/node_modules/ip-address/dist/v6/helpers.js +12 -3
- package/.output/server/node_modules/ip-address/package.json +49 -37
- package/.output/server/node_modules/ufo/dist/index.mjs +2 -2
- package/.output/server/node_modules/ufo/package.json +9 -9
- package/.output/server/package.json +4 -3
- package/README.md +62 -19
- package/app/assets/css/main.css +1 -0
- package/app/assets/css/studio.css +131 -0
- package/app/components/StudioConversion.vue +41 -0
- package/app/components/StudioDataPanel.vue +21 -0
- package/app/components/StudioEditor.vue +78 -102
- package/app/components/StudioExpandButton.vue +11 -0
- package/app/components/StudioOutput.vue +7 -1
- package/app/components/StudioSourcePanel.vue +16 -0
- package/app/components/StudioValidation.vue +47 -0
- package/app/components/ZodMongooseStudio.vue +216 -198
- package/app/composables/useStudioRunner.ts +142 -0
- package/app/pages/index.vue +1 -1
- package/app/plugins/monaco.client.ts +2 -0
- package/bin/index.mjs +5 -0
- package/nuxt.config.ts +4 -3
- package/package.json +4 -2
- package/server/api/editor-types.get.ts +28 -0
- package/server/api/parse.post.ts +17 -175
- package/server/api/studio/run.post.ts +17 -0
- package/server/middleware/security.ts +29 -7
- package/server/utils/example-data.ts +80 -0
- package/server/utils/parse-request.ts +28 -0
- package/server/utils/runner-client.ts +40 -0
- package/server/utils/studio-execution.ts +176 -0
- package/.output/public/_nuxt/BD40YYqZ.js +0 -33
- package/.output/public/_nuxt/BlQmLjV4.js +0 -1
- package/.output/public/_nuxt/CJ4Cv9fY.js +0 -61
- package/.output/public/_nuxt/builds/meta/495a8ebb-444d-4414-9b98-c181aa84d817.json +0 -1
- package/.output/public/_nuxt/error-404.64pZEiDn.css +0 -1
- package/.output/public/_nuxt/error-500.DHOpI0Ir.css +0 -1
- package/.output/public/_nuxt/rH_O4qXT.js +0 -1
- package/.output/server/chunks/build/error-404-BN-7rNE4.mjs.map +0 -1
- package/.output/server/chunks/build/error-404-styles.Byx46oLR.mjs +0 -8
- package/.output/server/chunks/build/error-404-styles.Byx46oLR.mjs.map +0 -1
- package/.output/server/chunks/build/error-500-DhIQ-2yt.mjs.map +0 -1
- package/.output/server/chunks/build/error-500-styles.BL3UT0Wa.mjs +0 -8
- package/.output/server/chunks/build/error-500-styles.BL3UT0Wa.mjs.map +0 -1
- package/.output/server/chunks/build/index-B4T6zHp0.mjs +0 -1904
- package/.output/server/chunks/build/index-B4T6zHp0.mjs.map +0 -1
- package/.output/server/node_modules/zod/index.js +0 -4
- /package/.output/public/_nuxt/{editor.CdBKkuhR.css → monaco.CdBKkuhR.css} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nullix/zod-mongoose-studio",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.1",
|
|
4
4
|
"description": "Monaco-powered TypeScript playground for @nullix/zod-mongoose",
|
|
5
5
|
"main": "./nuxt.config.ts",
|
|
6
6
|
"bin": {
|
|
@@ -39,12 +39,13 @@
|
|
|
39
39
|
"provenance": true
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
+
"acorn": "8.16.0",
|
|
42
43
|
"esbuild": "0.28.0",
|
|
43
44
|
"get-port-please": "3.2.0",
|
|
44
45
|
"mongoose": "9.6.3",
|
|
45
46
|
"open": "11.0.0",
|
|
46
47
|
"zod": "4.3.6",
|
|
47
|
-
"@nullix/zod-mongoose": "3.3.
|
|
48
|
+
"@nullix/zod-mongoose": "3.3.1"
|
|
48
49
|
},
|
|
49
50
|
"devDependencies": {
|
|
50
51
|
"@nuxt/ui": "4.6.0",
|
|
@@ -61,6 +62,7 @@
|
|
|
61
62
|
"dev": "nuxt dev -p 34003",
|
|
62
63
|
"generate": "nuxt generate",
|
|
63
64
|
"preview": "nuxt preview",
|
|
65
|
+
"test": "node --experimental-strip-types --test test/*.test.ts",
|
|
64
66
|
"postinstall": "nuxt prepare"
|
|
65
67
|
}
|
|
66
68
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join, relative } from "node:path";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { setHeader } from "h3";
|
|
5
|
+
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
const zodRoot = dirname(dirname(require.resolve("zod/v4")));
|
|
8
|
+
let cachedTypes: Promise<Record<string, string>> | undefined;
|
|
9
|
+
|
|
10
|
+
async function collect(directory: string, files: Record<string, string>) {
|
|
11
|
+
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
12
|
+
const path = join(directory, entry.name);
|
|
13
|
+
if (entry.isDirectory()) await collect(path, files);
|
|
14
|
+
else if (entry.name.endsWith(".d.ts")) {
|
|
15
|
+
files[`file:///node_modules/zod/${relative(zodRoot, path).replaceAll("\\", "/")}`] = await readFile(path, "utf8");
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default defineEventHandler((event) => {
|
|
21
|
+
setHeader(event, "Cache-Control", "public, max-age=3600");
|
|
22
|
+
cachedTypes ||= (async () => {
|
|
23
|
+
const files: Record<string, string> = {};
|
|
24
|
+
await collect(join(zodRoot, "v4"), files);
|
|
25
|
+
return files;
|
|
26
|
+
})();
|
|
27
|
+
return cachedTypes;
|
|
28
|
+
});
|
package/server/api/parse.post.ts
CHANGED
|
@@ -1,187 +1,29 @@
|
|
|
1
|
-
import { defineEventHandler,
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import util from "node:util";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { dirname } from "node:path";
|
|
1
|
+
import { defineEventHandler, createError } from "h3";
|
|
2
|
+
import { executeStudio, StudioExecutionError } from "../utils/studio-execution";
|
|
3
|
+
import { readStudioRequest } from "../utils/parse-request";
|
|
7
4
|
|
|
8
|
-
|
|
9
|
-
import * as mongoose from "mongoose";
|
|
10
|
-
import * as mongooseZod from "@nullix/zod-mongoose";
|
|
11
|
-
|
|
12
|
-
// Initialize Mongoose in ESM environment
|
|
13
|
-
mongooseZod.setMongoose(mongoose.default || mongoose);
|
|
14
|
-
|
|
15
|
-
// Shim __filename and __dirname for ESM environments (Nitro)
|
|
16
|
-
let _filename = "/sandbox/main.ts";
|
|
17
|
-
let _dirname = "/sandbox";
|
|
18
|
-
|
|
19
|
-
try {
|
|
20
|
-
_filename = fileURLToPath(import.meta.url);
|
|
21
|
-
_dirname = dirname(_filename);
|
|
22
|
-
} catch {
|
|
23
|
-
// Fallback if import.meta.url is not available or valid in the current context
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// The converter exposes a module-level Hookable instance. User code can add
|
|
27
|
-
// hooks to it, so parse requests must not overlap and must always clean it up
|
|
28
|
-
// before the next request starts.
|
|
5
|
+
const MAX_PENDING_PARSES = 3;
|
|
29
6
|
let parseQueue = Promise.resolve();
|
|
7
|
+
let pendingParses = 0;
|
|
30
8
|
|
|
31
|
-
async function
|
|
9
|
+
async function acquireLocalLock(): Promise<() => void> {
|
|
10
|
+
if (pendingParses >= MAX_PENDING_PARSES) throw createError({ statusCode: 503, message: "Studio is busy. Try again shortly." });
|
|
11
|
+
pendingParses++;
|
|
32
12
|
const previous = parseQueue;
|
|
33
13
|
let release!: () => void;
|
|
34
|
-
|
|
35
|
-
parseQueue = new Promise<void>((resolve) => {
|
|
36
|
-
release = resolve;
|
|
37
|
-
});
|
|
38
|
-
|
|
14
|
+
parseQueue = new Promise<void>((resolve) => { release = () => { pendingParses--; resolve(); }; });
|
|
39
15
|
await previous;
|
|
40
16
|
return release;
|
|
41
17
|
}
|
|
42
18
|
|
|
43
|
-
function getErrorMessage(error: unknown): string {
|
|
44
|
-
if (error instanceof Error) return error.message || error.name;
|
|
45
|
-
return String(error);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// @ts-ignore
|
|
49
|
-
if (typeof __filename === "undefined") {
|
|
50
|
-
// @ts-ignore
|
|
51
|
-
globalThis.__filename = _filename;
|
|
52
|
-
}
|
|
53
|
-
// @ts-ignore
|
|
54
|
-
if (typeof __dirname === "undefined") {
|
|
55
|
-
// @ts-ignore
|
|
56
|
-
globalThis.__dirname = _dirname;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
19
|
export default defineEventHandler(async (event) => {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const { sourceCode } = await readBody(event);
|
|
63
|
-
|
|
64
|
-
if (!sourceCode) {
|
|
65
|
-
throw createError({ statusCode: 400, message: "Missing sourceCode" });
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const releaseParseLock = await acquireParseLock();
|
|
69
|
-
|
|
70
|
-
// 1. Transpile exactly what the user wrote
|
|
71
|
-
try {
|
|
72
|
-
// A user hook can throw before the converter has completed. Since hooks are
|
|
73
|
-
// global within the server process, cleanup must happen for both success and
|
|
74
|
-
// failure, otherwise the user's hook would affect every later visitor.
|
|
75
|
-
mongooseZod.hooks.removeAllHooks();
|
|
76
|
-
|
|
77
|
-
let jsCode: string;
|
|
78
|
-
try {
|
|
79
|
-
jsCode = transformSync(sourceCode, {
|
|
80
|
-
loader: "ts",
|
|
81
|
-
format: "cjs",
|
|
82
|
-
target: "esnext",
|
|
83
|
-
// Provide these globals during transpilation just in case
|
|
84
|
-
define: {
|
|
85
|
-
__filename: '"/sandbox/main.ts"',
|
|
86
|
-
__dirname: '"/sandbox"',
|
|
87
|
-
},
|
|
88
|
-
}).code;
|
|
89
|
-
} catch (error: unknown) {
|
|
90
|
-
throw createError({
|
|
91
|
-
statusCode: 400,
|
|
92
|
-
message: `Compilation Error: ${getErrorMessage(error)}`,
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
// 2. Map imports securely
|
|
96
|
-
const sandboxRequire = (moduleName: string) => {
|
|
97
|
-
if (moduleName === "zod" || moduleName === "zod/v4") return zod;
|
|
98
|
-
if (moduleName === "@nullix/zod-mongoose") return mongooseZod;
|
|
99
|
-
if (moduleName === "mongoose") return mongoose.default || mongoose;
|
|
100
|
-
throw new Error(`Security Exception: Module "${moduleName}" is blocked.`);
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
const sandbox = {
|
|
104
|
-
require: sandboxRequire,
|
|
105
|
-
console: { log: () => {}, warn: () => {}, error: () => {} },
|
|
106
|
-
__filename: "/sandbox/main.ts",
|
|
107
|
-
__dirname: "/sandbox",
|
|
108
|
-
};
|
|
109
|
-
const context = vm.createContext(sandbox);
|
|
110
|
-
|
|
111
|
-
const script = new vm.Script(`
|
|
112
|
-
((__filename, __dirname) => {
|
|
113
|
-
let exports = {};
|
|
114
|
-
let module = { exports };
|
|
115
|
-
|
|
116
|
-
${jsCode}
|
|
117
|
-
|
|
118
|
-
return module.exports;
|
|
119
|
-
})("/sandbox/main.ts", "/sandbox");
|
|
120
|
-
`);
|
|
121
|
-
|
|
122
|
-
// 3. Execute code and grab the default export
|
|
123
|
-
const rawExport = script.runInContext(context, {
|
|
124
|
-
timeout: isDocsMode ? 1000 : 5000,
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
const schema = rawExport.default || rawExport;
|
|
128
|
-
|
|
129
|
-
if (!schema || !schema._def) {
|
|
130
|
-
throw new Error("You must `export default` a valid Zod schema.");
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// 4. Run BOTH functions on the user's schema
|
|
134
|
-
const extractedDef = mongooseZod.extractMongooseDef(schema);
|
|
135
|
-
const mongooseSchema = mongooseZod.toMongooseSchema(schema, { modelName: 'ParsedSchema' });
|
|
136
|
-
|
|
137
|
-
// Format the outputs nicely
|
|
138
|
-
const formatOpts = { depth: null, showHidden: false, colors: false };
|
|
139
|
-
|
|
140
|
-
// Mongoose schemas are massive class instances, so we extract the raw .obj definition to display
|
|
141
|
-
const formatValue = (val: any): any => {
|
|
142
|
-
if (typeof val === "function") {
|
|
143
|
-
return val.name || val.toString();
|
|
144
|
-
}
|
|
145
|
-
if (Array.isArray(val)) {
|
|
146
|
-
return val.map(formatValue);
|
|
147
|
-
}
|
|
148
|
-
if (
|
|
149
|
-
val !== null &&
|
|
150
|
-
typeof val === "object" &&
|
|
151
|
-
val.constructor === Object
|
|
152
|
-
) {
|
|
153
|
-
const formatted: any = {};
|
|
154
|
-
for (const key in val) {
|
|
155
|
-
formatted[key] = formatValue(val[key]);
|
|
156
|
-
}
|
|
157
|
-
return formatted;
|
|
158
|
-
}
|
|
159
|
-
return val;
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
const formattedDef = formatValue(extractedDef);
|
|
163
|
-
// schemaObj is a Mongoose Schema instance, we only care about its .obj for display
|
|
164
|
-
const formattedSchema = formatValue(mongooseSchema.obj);
|
|
165
|
-
// @ts-ignore
|
|
166
|
-
const formattedTree = formatValue(mongooseSchema.tree);
|
|
167
|
-
// @ts-ignore
|
|
168
|
-
const formattedPaths = formatValue(mongooseSchema.paths);
|
|
169
|
-
|
|
170
|
-
return {
|
|
171
|
-
definition: util.inspect(formattedDef, formatOpts),
|
|
172
|
-
schemaObj: util.inspect(formattedSchema, formatOpts),
|
|
173
|
-
schemaTree: util.inspect(formattedTree, formatOpts),
|
|
174
|
-
schemaPaths: util.inspect(formattedPaths, formatOpts),
|
|
175
|
-
};
|
|
176
|
-
} catch (err: any) {
|
|
177
|
-
const message = getErrorMessage(err);
|
|
178
|
-
const isCompilationError = err?.statusCode === 400;
|
|
179
|
-
throw createError({
|
|
180
|
-
statusCode: isCompilationError ? 400 : 500,
|
|
181
|
-
message: isCompilationError ? message : `Execution Error: ${message}`,
|
|
182
|
-
});
|
|
183
|
-
} finally {
|
|
184
|
-
mongooseZod.hooks.removeAllHooks();
|
|
185
|
-
releaseParseLock();
|
|
20
|
+
if (useRuntimeConfig(event).public.isDocsMode) {
|
|
21
|
+
throw createError({ statusCode: 403, message: "Local Studio execution is unavailable in docs mode." });
|
|
186
22
|
}
|
|
23
|
+
const body = await readStudioRequest(event);
|
|
24
|
+
const release = await acquireLocalLock();
|
|
25
|
+
try { return executeStudio(body, 5000); }
|
|
26
|
+
catch (error: any) {
|
|
27
|
+
throw createError({ statusCode: error instanceof StudioExecutionError ? 400 : 500, message: error?.message || "Studio execution failed." });
|
|
28
|
+
} finally { release(); }
|
|
187
29
|
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createError, defineEventHandler } from "h3";
|
|
2
|
+
import { readStudioRequest } from "../../utils/parse-request";
|
|
3
|
+
import { runInContainer } from "../../utils/runner-client";
|
|
4
|
+
|
|
5
|
+
export default defineEventHandler(async (event) => {
|
|
6
|
+
if (!useRuntimeConfig(event).public.isDocsMode) {
|
|
7
|
+
throw createError({ statusCode: 403, message: "Use the local Studio endpoint in CLI mode." });
|
|
8
|
+
}
|
|
9
|
+
const socketPath = process.env.STUDIO_RUNNER_SOCKET;
|
|
10
|
+
if (!socketPath) throw createError({ statusCode: 503, message: "Studio runner is not configured." });
|
|
11
|
+
const body = await readStudioRequest(event);
|
|
12
|
+
try { return await runInContainer(body, socketPath); }
|
|
13
|
+
catch (error: any) {
|
|
14
|
+
if (error?.statusCode) throw error;
|
|
15
|
+
throw createError({ statusCode: 503, message: "Studio runner is unavailable. Try again shortly." });
|
|
16
|
+
}
|
|
17
|
+
});
|
|
@@ -1,14 +1,25 @@
|
|
|
1
|
-
import { defineEventHandler, createError, getRequestIP } from "h3";
|
|
1
|
+
import { defineEventHandler, createError, getHeader, getRequestIP, setHeader } from "h3";
|
|
2
2
|
|
|
3
3
|
// Simple in-memory rate limiting map: IP -> array of timestamps
|
|
4
4
|
const rateLimitMap = new Map<string, number[]>();
|
|
5
|
-
const RATE_LIMIT =
|
|
5
|
+
const RATE_LIMIT = 8; // Allow quick edits and shortcut runs without throttling normal use.
|
|
6
6
|
const TIME_WINDOW = 1000; // per 1 second (1000ms)
|
|
7
|
+
const GLOBAL_RATE_LIMIT = 20;
|
|
8
|
+
let globalWindowStart = 0;
|
|
9
|
+
let globalRequests = 0;
|
|
7
10
|
|
|
8
11
|
export default defineEventHandler((event) => {
|
|
9
|
-
const isDocsMode =
|
|
12
|
+
const isDocsMode = useRuntimeConfig(event).public.isDocsMode;
|
|
10
13
|
const path = event.path;
|
|
11
14
|
|
|
15
|
+
if (!isDocsMode && path.startsWith("/api/")) {
|
|
16
|
+
const host = getHeader(event, "host") || "";
|
|
17
|
+
const origin = getHeader(event, "origin");
|
|
18
|
+
const localHost = /^(localhost|127\.0\.0\.1)(:\d+)?$/.test(host);
|
|
19
|
+
const localOrigin = !origin || /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/.test(origin);
|
|
20
|
+
if (!localHost || !localOrigin) throw createError({ statusCode: 403, message: "Local Studio accepts only loopback requests." });
|
|
21
|
+
}
|
|
22
|
+
|
|
12
23
|
// 1. Environment Check: Disable local resolving in Docs mode
|
|
13
24
|
if (isDocsMode && path.startsWith("/api/resolve")) {
|
|
14
25
|
throw createError({
|
|
@@ -17,8 +28,8 @@ export default defineEventHandler((event) => {
|
|
|
17
28
|
});
|
|
18
29
|
}
|
|
19
30
|
|
|
20
|
-
// 2. Rate
|
|
21
|
-
if (isDocsMode && path
|
|
31
|
+
// 2. Rate limiting for the parse endpoint in Docs Mode
|
|
32
|
+
if (isDocsMode && path === "/api/studio/run") {
|
|
22
33
|
const ip = getRequestIP(event) || "unknown";
|
|
23
34
|
const now = Date.now();
|
|
24
35
|
|
|
@@ -27,14 +38,25 @@ export default defineEventHandler((event) => {
|
|
|
27
38
|
// Filter timestamps within the current window
|
|
28
39
|
const recentRequests = timestamps.filter((t) => now - t < TIME_WINDOW);
|
|
29
40
|
|
|
30
|
-
if (
|
|
41
|
+
if (now - globalWindowStart >= TIME_WINDOW) {
|
|
42
|
+
globalWindowStart = now;
|
|
43
|
+
globalRequests = 0;
|
|
44
|
+
}
|
|
45
|
+
if (rateLimitMap.size > 1024) {
|
|
46
|
+
for (const [key, entries] of rateLimitMap) {
|
|
47
|
+
if (entries.every((time) => now - time >= TIME_WINDOW)) rateLimitMap.delete(key);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (recentRequests.length >= RATE_LIMIT || globalRequests >= GLOBAL_RATE_LIMIT || (rateLimitMap.size >= 1024 && !rateLimitMap.has(ip))) {
|
|
51
|
+
setHeader(event, "Retry-After", 1);
|
|
31
52
|
throw createError({
|
|
32
53
|
statusCode: 429,
|
|
33
|
-
message: "Too
|
|
54
|
+
message: "Too many runs. Wait a moment and try again.",
|
|
34
55
|
});
|
|
35
56
|
}
|
|
36
57
|
|
|
37
58
|
recentRequests.push(now);
|
|
38
59
|
rateLimitMap.set(ip, recentRequests);
|
|
60
|
+
globalRequests++;
|
|
39
61
|
}
|
|
40
62
|
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { ZodType } from "zod/v4";
|
|
2
|
+
import { getMongooseMeta } from "@nullix/zod-mongoose";
|
|
3
|
+
|
|
4
|
+
type Schema = ZodType & { _def: Record<string, any> };
|
|
5
|
+
|
|
6
|
+
function checks(schema: Schema): Record<string, any>[] {
|
|
7
|
+
return (schema._def.checks || []).map((check: any) => check._zod?.def || check.def || {});
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function sampleString(schema: Schema, key: string): string {
|
|
11
|
+
const rules = checks(schema);
|
|
12
|
+
const format = schema._def.format || rules.find((rule) => rule.format)?.format;
|
|
13
|
+
if (format === "email" || /email/i.test(key)) return "alex@example.com";
|
|
14
|
+
if (format === "uuid") return "123e4567-e89b-42d3-a456-426614174000";
|
|
15
|
+
if (format === "url") return "https://example.com";
|
|
16
|
+
if (format === "datetime") return "2026-01-01T12:00:00.000Z";
|
|
17
|
+
if (format === "date") return "2026-01-01";
|
|
18
|
+
|
|
19
|
+
const minimum = Math.max(1, ...rules.filter((rule) => rule.check === "min_length").map((rule) => rule.minimum));
|
|
20
|
+
const base = /name/i.test(key) ? "Alex Morgan" : /title/i.test(key) ? "Example title" : "example";
|
|
21
|
+
return base.padEnd(Math.min(minimum, 100), "x");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function sampleNumber(schema: Schema): number {
|
|
25
|
+
const rules = checks(schema);
|
|
26
|
+
const lower = rules.find((rule) => rule.check === "greater_than");
|
|
27
|
+
const upper = rules.find((rule) => rule.check === "less_than");
|
|
28
|
+
const integer = schema._def.type === "int" || rules.some((rule) => rule.check === "number_format" && rule.format === "safeint");
|
|
29
|
+
const minimum = lower ? Number(lower.value) + (lower.inclusive ? 0 : integer ? 1 : 0.1) : 1;
|
|
30
|
+
const maximum = upper ? Number(upper.value) - (upper.inclusive ? 0 : integer ? 1 : 0.1) : Infinity;
|
|
31
|
+
const candidate = Math.min(Math.max(21, minimum), maximum);
|
|
32
|
+
return integer ? Math.ceil(candidate) : candidate;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function createExample(schema: Schema, key = "value", depth = 0, seen = new Set<Schema>()): unknown {
|
|
36
|
+
if (depth > 8 || seen.has(schema)) throw new Error("This recursive schema needs a handwritten example.");
|
|
37
|
+
const nextSeen = new Set(seen).add(schema);
|
|
38
|
+
const def = schema._def;
|
|
39
|
+
const mongooseType = getMongooseMeta(schema).type as any;
|
|
40
|
+
if (mongooseType === "ObjectId" || mongooseType?.schemaName === "ObjectId") return "507f1f77bcf86cd799439011";
|
|
41
|
+
|
|
42
|
+
switch (def.type) {
|
|
43
|
+
case "optional":
|
|
44
|
+
case "default":
|
|
45
|
+
case "prefault":
|
|
46
|
+
return depth === 0 ? createExample(def.innerType, key, depth + 1, nextSeen) : undefined;
|
|
47
|
+
case "nullable":
|
|
48
|
+
case "nonoptional":
|
|
49
|
+
case "readonly":
|
|
50
|
+
case "catch":
|
|
51
|
+
return createExample(def.innerType, key, depth + 1, nextSeen);
|
|
52
|
+
case "pipe":
|
|
53
|
+
return createExample(def.in, key, depth + 1, nextSeen);
|
|
54
|
+
case "lazy":
|
|
55
|
+
return createExample(def.getter(), key, depth + 1, nextSeen);
|
|
56
|
+
case "object": {
|
|
57
|
+
const result: Record<string, unknown> = {};
|
|
58
|
+
for (const [field, fieldSchema] of Object.entries((schema as any).shape || def.shape || {})) {
|
|
59
|
+
const value = createExample(fieldSchema as Schema, field, depth + 1, nextSeen);
|
|
60
|
+
if (value !== undefined) result[field] = value;
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
case "array": {
|
|
65
|
+
const minimum = Math.max(0, ...checks(schema).filter((rule) => rule.check === "min_length").map((rule) => rule.minimum));
|
|
66
|
+
return Array.from({ length: Math.min(Math.max(1, minimum), 10) }, () => createExample(def.element, key, depth + 1, nextSeen));
|
|
67
|
+
}
|
|
68
|
+
case "tuple": return def.items.map((item: Schema) => createExample(item, key, depth + 1, nextSeen));
|
|
69
|
+
case "string": return sampleString(schema, key);
|
|
70
|
+
case "number":
|
|
71
|
+
case "int": return sampleNumber(schema);
|
|
72
|
+
case "boolean": return true;
|
|
73
|
+
case "enum": return Object.values(def.entries)[0];
|
|
74
|
+
case "literal": return def.values?.[0];
|
|
75
|
+
case "union": return createExample(def.options[0], key, depth + 1, nextSeen);
|
|
76
|
+
case "record": return { example: createExample(def.valueType, key, depth + 1, nextSeen) };
|
|
77
|
+
case "date": return "2026-01-01T12:00:00.000Z";
|
|
78
|
+
default: throw new Error(`Automatic examples do not yet support ${def.type || "this schema"}.`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createError } from "h3";
|
|
2
|
+
import type { H3Event } from "h3";
|
|
3
|
+
import type { ParseBody } from "./studio-execution";
|
|
4
|
+
|
|
5
|
+
const MAX_BODY_BYTES = 160_000;
|
|
6
|
+
|
|
7
|
+
export async function readStudioRequest(event: H3Event): Promise<ParseBody> {
|
|
8
|
+
const chunks: Buffer[] = [];
|
|
9
|
+
let size = 0;
|
|
10
|
+
for await (const chunk of event.node.req) {
|
|
11
|
+
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
12
|
+
size += bytes.length;
|
|
13
|
+
if (size > MAX_BODY_BYTES) throw createError({ statusCode: 413, message: "Studio input is too large." });
|
|
14
|
+
chunks.push(bytes);
|
|
15
|
+
}
|
|
16
|
+
let body: ParseBody;
|
|
17
|
+
try { body = JSON.parse(Buffer.concat(chunks).toString("utf8")); }
|
|
18
|
+
catch { throw createError({ statusCode: 400, message: "Invalid JSON request." }); }
|
|
19
|
+
if (typeof body?.sourceCode !== "string" || !body.sourceCode.trim()) {
|
|
20
|
+
throw createError({ statusCode: 400, message: "Add a Zod schema to the editor first." });
|
|
21
|
+
}
|
|
22
|
+
if (body.sourceCode.length > 50_000 || typeof body.inputData !== "string" || body.inputData.length > 100_000 ||
|
|
23
|
+
(body.selectedSchema !== undefined && typeof body.selectedSchema !== "string") ||
|
|
24
|
+
(body.action !== undefined && body.action !== "run" && body.action !== "example")) {
|
|
25
|
+
throw createError({ statusCode: 400, message: "Invalid or oversized Studio input." });
|
|
26
|
+
}
|
|
27
|
+
return body;
|
|
28
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createError } from "h3";
|
|
2
|
+
import { request } from "node:http";
|
|
3
|
+
import type { ParseBody } from "./studio-execution";
|
|
4
|
+
|
|
5
|
+
const MAX_RESPONSE_BYTES = 1_000_000;
|
|
6
|
+
|
|
7
|
+
export function runInContainer(body: ParseBody, socketPath: string): Promise<unknown> {
|
|
8
|
+
const payload = JSON.stringify(body);
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
const req = request({
|
|
11
|
+
socketPath,
|
|
12
|
+
path: "/run",
|
|
13
|
+
method: "POST",
|
|
14
|
+
headers: { "content-type": "application/json", "content-length": Buffer.byteLength(payload) },
|
|
15
|
+
}, (res) => {
|
|
16
|
+
const chunks: Buffer[] = [];
|
|
17
|
+
let size = 0;
|
|
18
|
+
res.on("data", (chunk: Buffer) => {
|
|
19
|
+
size += chunk.length;
|
|
20
|
+
if (size > MAX_RESPONSE_BYTES) {
|
|
21
|
+
req.destroy(new Error("Studio result is too large."));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
chunks.push(chunk);
|
|
25
|
+
});
|
|
26
|
+
res.on("end", () => {
|
|
27
|
+
try {
|
|
28
|
+
const result = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
29
|
+
if (res.statusCode !== 200) {
|
|
30
|
+
reject(createError({ statusCode: res.statusCode === 400 ? 400 : res.statusCode === 504 ? 504 : 503, message: result.error || "Studio runner failed." }));
|
|
31
|
+
} else resolve(result);
|
|
32
|
+
} catch { reject(createError({ statusCode: 502, message: "Invalid response from Studio runner." })); }
|
|
33
|
+
});
|
|
34
|
+
res.on("error", reject);
|
|
35
|
+
});
|
|
36
|
+
req.setTimeout(5000, () => req.destroy(new Error("Studio runner timed out.")));
|
|
37
|
+
req.on("error", reject);
|
|
38
|
+
req.end(payload);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import vm from "node:vm";
|
|
2
|
+
import { transformSync } from "esbuild";
|
|
3
|
+
import util from "node:util";
|
|
4
|
+
import { parse } from "acorn";
|
|
5
|
+
import * as zod from "zod/v4";
|
|
6
|
+
import mongoose from "mongoose";
|
|
7
|
+
import * as mongooseZod from "@nullix/zod-mongoose";
|
|
8
|
+
import { createExample } from "./example-data";
|
|
9
|
+
|
|
10
|
+
mongooseZod.setMongoose(mongoose);
|
|
11
|
+
|
|
12
|
+
export class StudioExecutionError extends Error {
|
|
13
|
+
statusCode = 400;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function errorMessage(error: unknown): string {
|
|
17
|
+
return error instanceof Error ? error.message || error.name : String(error);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function declaredNames(jsCode: string): string[] {
|
|
21
|
+
const file = parse(jsCode, { ecmaVersion: "latest", sourceType: "script" });
|
|
22
|
+
const names: string[] = [];
|
|
23
|
+
for (const statement of file.body) {
|
|
24
|
+
if (statement.type !== "VariableDeclaration") continue;
|
|
25
|
+
for (const declaration of statement.declarations) {
|
|
26
|
+
if (declaration.id.type === "Identifier") names.push(declaration.id.name);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return names;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function displayValue(value: unknown): string {
|
|
33
|
+
const seen = new WeakSet<object>();
|
|
34
|
+
return JSON.stringify(value, (_key, item) => {
|
|
35
|
+
if (typeof item === "bigint") return `${item.toString()}n`;
|
|
36
|
+
if (typeof item === "undefined") return "[undefined]";
|
|
37
|
+
if (typeof item === "number" && !Number.isFinite(item)) return String(item);
|
|
38
|
+
if (typeof item === "object" && item !== null) {
|
|
39
|
+
if (item instanceof Date) return item.toISOString();
|
|
40
|
+
if (typeof item.toHexString === "function") return item.toHexString();
|
|
41
|
+
if (seen.has(item)) return "[Circular]";
|
|
42
|
+
seen.add(item);
|
|
43
|
+
}
|
|
44
|
+
return item;
|
|
45
|
+
}, 2) || "undefined";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function formatMongoose(value: any): any {
|
|
49
|
+
if (typeof value === "function") return value.name || value.toString();
|
|
50
|
+
if (Array.isArray(value)) return value.map(formatMongoose);
|
|
51
|
+
if (value !== null && typeof value === "object" && value.constructor === Object) {
|
|
52
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, formatMongoose(entry)]));
|
|
53
|
+
}
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type ParseBody = {
|
|
58
|
+
sourceCode?: string;
|
|
59
|
+
inputData?: string;
|
|
60
|
+
selectedSchema?: string;
|
|
61
|
+
action?: "run" | "example";
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export function executeStudio(body: ParseBody, timeoutMs: number) {
|
|
65
|
+
const sourceCode = body.sourceCode!;
|
|
66
|
+
mongooseZod.hooks.removeAllHooks();
|
|
67
|
+
try {
|
|
68
|
+
let jsCode: string;
|
|
69
|
+
try {
|
|
70
|
+
jsCode = transformSync(sourceCode, { loader: "ts", format: "cjs", target: "esnext" }).code;
|
|
71
|
+
} catch (error) {
|
|
72
|
+
throw new StudioExecutionError(`Compilation error: ${errorMessage(error)}`);
|
|
73
|
+
}
|
|
74
|
+
const names = declaredNames(jsCode);
|
|
75
|
+
|
|
76
|
+
const sandboxRequire = (moduleName: string) => {
|
|
77
|
+
if (moduleName === "zod" || moduleName === "zod/v4") return zod;
|
|
78
|
+
if (moduleName === "@nullix/zod-mongoose") return mongooseZod;
|
|
79
|
+
if (moduleName === "mongoose") return mongoose;
|
|
80
|
+
throw new Error(`Module "${moduleName}" is unavailable in Studio.`);
|
|
81
|
+
};
|
|
82
|
+
const context = vm.createContext({
|
|
83
|
+
...mongooseZod,
|
|
84
|
+
z: zod.z,
|
|
85
|
+
mongoose,
|
|
86
|
+
require: sandboxRequire,
|
|
87
|
+
console: { log: () => {}, warn: () => {}, error: () => {} },
|
|
88
|
+
__filename: "/sandbox/main.ts",
|
|
89
|
+
__dirname: "/sandbox",
|
|
90
|
+
});
|
|
91
|
+
const captured = names.map((name) => `${JSON.stringify(name)}: typeof ${name} === "undefined" ? undefined : ${name}`).join(",");
|
|
92
|
+
const script = new vm.Script(`
|
|
93
|
+
(() => {
|
|
94
|
+
let exports = {};
|
|
95
|
+
let module = { exports };
|
|
96
|
+
${jsCode}
|
|
97
|
+
return { exported: module.exports, declared: { ${captured} } };
|
|
98
|
+
})()
|
|
99
|
+
`);
|
|
100
|
+
const result = script.runInContext(context, { timeout: timeoutMs });
|
|
101
|
+
const choices = new Map<string, zod.ZodType>();
|
|
102
|
+
for (const [name, value] of Object.entries(result.declared)) {
|
|
103
|
+
if (value instanceof zod.ZodType && ![...choices.values()].includes(value)) choices.set(name, value);
|
|
104
|
+
}
|
|
105
|
+
for (const [name, value] of Object.entries(result.exported || {})) {
|
|
106
|
+
if (value instanceof zod.ZodType && ![...choices.values()].includes(value)) choices.set(name, value);
|
|
107
|
+
}
|
|
108
|
+
const defaultExport = result.exported instanceof zod.ZodType ? result.exported : result.exported?.default;
|
|
109
|
+
if (defaultExport instanceof zod.ZodType && ![...choices.values()].includes(defaultExport)) choices.set("default", defaultExport);
|
|
110
|
+
if (!choices.size) throw new Error("Define a Zod schema, such as const UserSchema = z.object({ name: z.string() }).");
|
|
111
|
+
|
|
112
|
+
const selectedSchema = body.selectedSchema && choices.has(body.selectedSchema)
|
|
113
|
+
? body.selectedSchema : choices.keys().next().value as string;
|
|
114
|
+
const schema = choices.get(selectedSchema)!;
|
|
115
|
+
const schemaNames = [...choices.keys()];
|
|
116
|
+
|
|
117
|
+
let example: string | undefined;
|
|
118
|
+
let exampleError: string | undefined;
|
|
119
|
+
let inputData = body.inputData;
|
|
120
|
+
if (body.action === "example") {
|
|
121
|
+
try {
|
|
122
|
+
const fixture = result.declared.exampleInput;
|
|
123
|
+
const generated = fixture !== undefined ? fixture : createExample(schema as any);
|
|
124
|
+
const exampleResult = schema.safeParse(generated);
|
|
125
|
+
if (!exampleResult.success) throw new Error("The example does not satisfy this schema. Add or update a valid const exampleInput for custom rules.");
|
|
126
|
+
example = displayValue(generated);
|
|
127
|
+
inputData = example;
|
|
128
|
+
} catch (error) {
|
|
129
|
+
exampleError = errorMessage(error);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let validation: { status: "valid" | "invalid" | "json-error" | "parse-error" | "empty"; parsed?: string; issues?: { path: string; message: string; code: string }[]; message?: string } = { status: "empty" };
|
|
134
|
+
if (typeof inputData !== "string" || !inputData.trim()) {
|
|
135
|
+
validation = { status: "empty", message: "Add JSON data to try this schema." };
|
|
136
|
+
} else {
|
|
137
|
+
let input: unknown;
|
|
138
|
+
let inputValid = false;
|
|
139
|
+
try {
|
|
140
|
+
input = JSON.parse(inputData);
|
|
141
|
+
inputValid = true;
|
|
142
|
+
} catch (error) {
|
|
143
|
+
validation = { status: "json-error", message: errorMessage(error) };
|
|
144
|
+
}
|
|
145
|
+
if (inputValid) {
|
|
146
|
+
try {
|
|
147
|
+
const parsed = schema.safeParse(input);
|
|
148
|
+
validation = parsed.success
|
|
149
|
+
? { status: "valid", parsed: displayValue(parsed.data) }
|
|
150
|
+
: { status: "invalid", issues: parsed.error.issues.map((issue) => ({ path: issue.path.length ? issue.path.join(".") : "(root)", message: issue.message, code: issue.code })) };
|
|
151
|
+
} catch (error) {
|
|
152
|
+
validation = { status: "parse-error", message: `Validation could not run: ${errorMessage(error)}` };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let conversion: { definition?: string; schemaObj?: string; schemaTree?: string; schemaPaths?: string; error?: string };
|
|
158
|
+
try {
|
|
159
|
+
const extracted = mongooseZod.extractMongooseDef(schema);
|
|
160
|
+
const converted = mongooseZod.toMongooseSchema(schema, { modelName: "ParsedSchema" });
|
|
161
|
+
const inspect = (value: unknown) => util.inspect(formatMongoose(value), { depth: null, colors: false, showHidden: false });
|
|
162
|
+
conversion = {
|
|
163
|
+
definition: inspect(extracted),
|
|
164
|
+
schemaObj: inspect(converted.obj),
|
|
165
|
+
schemaTree: inspect((converted as any).tree),
|
|
166
|
+
schemaPaths: inspect((converted as any).paths),
|
|
167
|
+
};
|
|
168
|
+
} catch (error) {
|
|
169
|
+
conversion = { error: errorMessage(error) };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return { schemaNames, selectedSchema, validation, conversion, example, exampleError };
|
|
173
|
+
} finally {
|
|
174
|
+
mongooseZod.hooks.removeAllHooks();
|
|
175
|
+
}
|
|
176
|
+
}
|