@nullix/zod-mongoose-studio 3.2.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 +2302 -835
- 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 -1316
- 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/34871c14-b0f4-4898-9ff0-debaff743355.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
|
@@ -1,225 +1,243 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { ref,
|
|
3
|
-
|
|
4
|
-
let debounceTimer: any;
|
|
2
|
+
import { nextTick, onBeforeUnmount, onMounted, ref, shallowRef } from "vue";
|
|
3
|
+
import { useStudioRunner } from "../composables/useStudioRunner";
|
|
5
4
|
|
|
6
5
|
const config = useRuntimeConfig();
|
|
7
|
-
const
|
|
8
|
-
const
|
|
6
|
+
const root = ref<HTMLElement | null>(null);
|
|
7
|
+
const mobileView = shallowRef<"schema" | "data" | "result" | "mongoose">(
|
|
8
|
+
"schema",
|
|
9
|
+
);
|
|
10
|
+
type Panel = "schema" | "data" | "result" | "mongoose";
|
|
11
|
+
const expandedPanel = shallowRef<Panel | null>(null);
|
|
12
|
+
let transitioning = false;
|
|
13
|
+
const mobileViews = [
|
|
14
|
+
{ key: "schema", label: "Schema" },
|
|
15
|
+
{ key: "data", label: "Data" },
|
|
16
|
+
{ key: "result", label: "Result" },
|
|
17
|
+
{ key: "mongoose", label: "Mongoose" },
|
|
18
|
+
] as const;
|
|
9
19
|
|
|
10
|
-
const
|
|
11
|
-
|
|
20
|
+
const {
|
|
21
|
+
sourceCode,
|
|
22
|
+
inputData,
|
|
23
|
+
schemaNames,
|
|
24
|
+
selectedSchema,
|
|
25
|
+
validation,
|
|
26
|
+
conversion,
|
|
27
|
+
isRunning,
|
|
28
|
+
isDirty,
|
|
29
|
+
hasRun,
|
|
30
|
+
error,
|
|
31
|
+
exampleError,
|
|
32
|
+
run,
|
|
33
|
+
selectSchema,
|
|
34
|
+
} = useStudioRunner();
|
|
12
35
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
// Export your schema so the Studio can transform it
|
|
19
|
-
export default UserSchema;`);
|
|
20
|
-
|
|
21
|
-
const outputDef = ref("// Definition output...");
|
|
22
|
-
const outputSchema = ref("// Mongoose Schema output...");
|
|
23
|
-
const outputTree = ref("// Mongoose Schema tree output...");
|
|
24
|
-
const outputPaths = ref("// Mongoose Schema paths output...");
|
|
25
|
-
const activeSchemaView = ref<"obj" | "tree" | "paths">("obj");
|
|
26
|
-
const activeSchemaViewOutput = computed(() => {
|
|
27
|
-
switch (activeSchemaView.value) {
|
|
28
|
-
case "obj":
|
|
29
|
-
return outputSchema;
|
|
30
|
-
case "tree":
|
|
31
|
-
return outputTree;
|
|
32
|
-
case "paths":
|
|
33
|
-
return outputPaths;
|
|
36
|
+
function onKeydown(event: KeyboardEvent) {
|
|
37
|
+
if (event.key === "Escape" && expandedPanel.value) {
|
|
38
|
+
togglePanel(expandedPanel.value);
|
|
39
|
+
return;
|
|
34
40
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
if (!root.value?.contains(document.activeElement)) return;
|
|
42
|
+
if (
|
|
43
|
+
!(event.metaKey || event.ctrlKey) ||
|
|
44
|
+
event.altKey ||
|
|
45
|
+
event.shiftKey ||
|
|
46
|
+
event.repeat
|
|
47
|
+
)
|
|
48
|
+
return;
|
|
49
|
+
if (event.key.toLowerCase() !== "s" && event.key.toLowerCase() !== "r")
|
|
50
|
+
return;
|
|
51
|
+
event.preventDefault();
|
|
52
|
+
event.stopPropagation();
|
|
53
|
+
void run();
|
|
54
|
+
}
|
|
40
55
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
56
|
+
function togglePanel(panel: Panel) {
|
|
57
|
+
if (transitioning) return;
|
|
58
|
+
const card = root.value?.querySelector<HTMLElement>(`.studio-${panel}`);
|
|
59
|
+
const change = () => {
|
|
60
|
+
expandedPanel.value = expandedPanel.value === panel ? null : panel;
|
|
61
|
+
};
|
|
62
|
+
if (card && "startViewTransition" in document) {
|
|
63
|
+
transitioning = true;
|
|
64
|
+
card.style.viewTransitionName = "studio-expanded-panel";
|
|
65
|
+
const transition = document.startViewTransition(() => {
|
|
66
|
+
change();
|
|
67
|
+
return nextTick();
|
|
47
68
|
});
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
const errText = `// Error\n${error.data?.message || error.message}`;
|
|
54
|
-
outputDef.value = errText;
|
|
55
|
-
outputSchema.value = errText;
|
|
56
|
-
outputTree.value = errText;
|
|
57
|
-
outputPaths.value = errText;
|
|
58
|
-
} finally {
|
|
59
|
-
isCompiling.value = false;
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
// Auto-compile with a slight delay as they type
|
|
64
|
-
watch(sourceCode, () => {
|
|
65
|
-
if (isDocsMode.value) return;
|
|
66
|
-
clearTimeout(debounceTimer);
|
|
67
|
-
debounceTimer = setTimeout(handleCompile, 600);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
// Initial compile for Docs Mode
|
|
71
|
-
if (isDocsMode.value) {
|
|
72
|
-
handleCompile();
|
|
69
|
+
void transition.finished.finally(() => {
|
|
70
|
+
card.style.viewTransitionName = "";
|
|
71
|
+
transitioning = false;
|
|
72
|
+
});
|
|
73
|
+
} else change();
|
|
73
74
|
}
|
|
75
|
+
|
|
76
|
+
onMounted(() => document.addEventListener("keydown", onKeydown, true));
|
|
77
|
+
onBeforeUnmount(() => document.removeEventListener("keydown", onKeydown, true));
|
|
74
78
|
</script>
|
|
75
79
|
|
|
76
80
|
<template>
|
|
77
81
|
<div
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
ref="root"
|
|
83
|
+
class="studio-shell"
|
|
84
|
+
:class="{
|
|
85
|
+
'studio-shell-docs': config.public.isDocsMode,
|
|
86
|
+
'is-focused': expandedPanel,
|
|
87
|
+
}"
|
|
84
88
|
>
|
|
85
|
-
<header
|
|
86
|
-
class="
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
</h1>
|
|
92
|
-
<UBadge v-if="isLocalMode" color="success" variant="soft"
|
|
93
|
-
>Local Mode</UBadge
|
|
94
|
-
>
|
|
95
|
-
<UBadge v-else color="info" variant="soft">Docs Mode</UBadge>
|
|
89
|
+
<header class="studio-header">
|
|
90
|
+
<div class="studio-brand">
|
|
91
|
+
<div>
|
|
92
|
+
<p class="studio-eyebrow">@nullix/zod-mongoose</p>
|
|
93
|
+
<h1>Studio</h1>
|
|
94
|
+
</div>
|
|
96
95
|
</div>
|
|
97
|
-
<div class="
|
|
98
|
-
<
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
@click="handleCompile"
|
|
96
|
+
<div class="studio-header-actions">
|
|
97
|
+
<span class="studio-mode">{{
|
|
98
|
+
config.public.isDocsMode ? "Playground" : "Local mode"
|
|
99
|
+
}}</span>
|
|
100
|
+
<span class="studio-shortcut" aria-hidden="true"
|
|
101
|
+
>⌘ / Ctrl + S or R</span
|
|
104
102
|
>
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
103
|
+
<button
|
|
104
|
+
type="button"
|
|
105
|
+
class="studio-run"
|
|
106
|
+
:disabled="isRunning || !config.public.isExecutionEnabled"
|
|
107
|
+
@click="run()"
|
|
109
108
|
>
|
|
109
|
+
{{ isRunning ? "Running…" : "Run" }}
|
|
110
|
+
</button>
|
|
110
111
|
</div>
|
|
111
112
|
</header>
|
|
112
113
|
|
|
113
|
-
<
|
|
114
|
-
class="
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
<
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
114
|
+
<div class="studio-subbar">
|
|
115
|
+
<div class="studio-subbar-copy">
|
|
116
|
+
<span
|
|
117
|
+
class="studio-pulse"
|
|
118
|
+
:class="{ 'is-busy': isRunning, error: error }"
|
|
119
|
+
/>
|
|
120
|
+
<span>{{
|
|
121
|
+
isRunning
|
|
122
|
+
? "Working through your schema…"
|
|
123
|
+
: error
|
|
124
|
+
? "Run failed"
|
|
125
|
+
: !hasRun
|
|
126
|
+
? "Waiting for first run"
|
|
127
|
+
: isDirty
|
|
128
|
+
? "Changes ready to run"
|
|
129
|
+
: "Schema and data in sync"
|
|
130
|
+
}}</span>
|
|
131
|
+
</div>
|
|
132
|
+
<label class="studio-schema-picker">
|
|
133
|
+
<span>ACTIVE SCHEMA</span>
|
|
134
|
+
<select
|
|
135
|
+
:value="selectedSchema"
|
|
136
|
+
:disabled="schemaNames.length < 2"
|
|
137
|
+
aria-label="Active schema"
|
|
138
|
+
@change="selectSchema(($event.target as HTMLSelectElement).value)"
|
|
139
|
+
>
|
|
140
|
+
<option v-if="!schemaNames.length" value="">
|
|
141
|
+
Run to find schemas
|
|
142
|
+
</option>
|
|
143
|
+
<option v-for="name in schemaNames" :key="name" :value="name">
|
|
144
|
+
{{ name }}
|
|
145
|
+
</option>
|
|
146
|
+
</select>
|
|
147
|
+
</label>
|
|
148
|
+
</div>
|
|
125
149
|
|
|
126
|
-
|
|
127
|
-
<
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
class="
|
|
150
|
+
<nav class="studio-mobile-nav" aria-label="Studio sections">
|
|
151
|
+
<button
|
|
152
|
+
v-for="view in mobileViews"
|
|
153
|
+
:key="view.key"
|
|
154
|
+
type="button"
|
|
155
|
+
:class="{ 'is-active': mobileView === view.key }"
|
|
156
|
+
@click="mobileView = view.key"
|
|
132
157
|
>
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
158
|
+
{{ view.label }}
|
|
159
|
+
</button>
|
|
160
|
+
</nav>
|
|
161
|
+
|
|
162
|
+
<div v-if="error" class="studio-error-banner" role="alert">
|
|
163
|
+
<strong>Run failed</strong><span>{{ error }}</span>
|
|
164
|
+
</div>
|
|
165
|
+
<div
|
|
166
|
+
v-if="exampleError"
|
|
167
|
+
class="studio-error-banner studio-example-banner"
|
|
168
|
+
role="status"
|
|
169
|
+
>
|
|
170
|
+
<strong>Example unavailable</strong><span>{{ exampleError }}</span>
|
|
171
|
+
</div>
|
|
172
|
+
|
|
173
|
+
<main class="studio-workspace">
|
|
174
|
+
<div class="studio-column studio-input-column">
|
|
175
|
+
<StudioSourcePanel
|
|
176
|
+
v-model="sourceCode"
|
|
177
|
+
:class="{
|
|
178
|
+
'is-mobile-active': mobileView === 'schema',
|
|
179
|
+
'is-expanded': expandedPanel === 'schema',
|
|
180
|
+
}"
|
|
181
|
+
>
|
|
182
|
+
<template #actions
|
|
183
|
+
><StudioExpandButton
|
|
184
|
+
label="schema editor"
|
|
185
|
+
:expanded="expandedPanel === 'schema'"
|
|
186
|
+
@toggle="togglePanel('schema')"
|
|
187
|
+
/></template>
|
|
188
|
+
</StudioSourcePanel>
|
|
189
|
+
<StudioDataPanel
|
|
190
|
+
v-model="inputData"
|
|
191
|
+
:is-running="isRunning"
|
|
192
|
+
:can-run="config.public.isExecutionEnabled"
|
|
193
|
+
:class="{
|
|
194
|
+
'is-mobile-active': mobileView === 'data',
|
|
195
|
+
'is-expanded': expandedPanel === 'data',
|
|
196
|
+
}"
|
|
197
|
+
@load-example="run('example')"
|
|
139
198
|
>
|
|
140
|
-
<template #
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
? 'i-heroicons-chevron-down'
|
|
149
|
-
: 'i-heroicons-chevron-right'
|
|
150
|
-
"
|
|
151
|
-
/>
|
|
152
|
-
</div>
|
|
153
|
-
</template>
|
|
154
|
-
<template #content>
|
|
155
|
-
<div class="flex-1 relative">
|
|
156
|
-
<ClientOnly>
|
|
157
|
-
<StudioOutput :model-value="outputDef" />
|
|
158
|
-
</ClientOnly>
|
|
159
|
-
</div>
|
|
160
|
-
</template>
|
|
161
|
-
</UCollapsible>
|
|
199
|
+
<template #actions
|
|
200
|
+
><StudioExpandButton
|
|
201
|
+
label="data editor"
|
|
202
|
+
:expanded="expandedPanel === 'data'"
|
|
203
|
+
@toggle="togglePanel('data')"
|
|
204
|
+
/></template>
|
|
205
|
+
</StudioDataPanel>
|
|
206
|
+
</div>
|
|
162
207
|
|
|
163
|
-
|
|
164
|
-
<
|
|
165
|
-
|
|
166
|
-
:class="
|
|
167
|
-
|
|
168
|
-
|
|
208
|
+
<div class="studio-column studio-output-column">
|
|
209
|
+
<StudioValidation
|
|
210
|
+
class="studio-result"
|
|
211
|
+
:class="{
|
|
212
|
+
'is-mobile-active': mobileView === 'result',
|
|
213
|
+
'is-expanded': expandedPanel === 'result',
|
|
214
|
+
}"
|
|
215
|
+
:result="validation"
|
|
216
|
+
:stale="isDirty"
|
|
169
217
|
>
|
|
170
|
-
<template #
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
size="xs"
|
|
195
|
-
variant="ghost"
|
|
196
|
-
:color="
|
|
197
|
-
activeSchemaView === 'paths' ? 'primary' : 'neutral'
|
|
198
|
-
"
|
|
199
|
-
@click="activeSchemaView = 'paths'"
|
|
200
|
-
>
|
|
201
|
-
.paths
|
|
202
|
-
</UButton>
|
|
203
|
-
</div>
|
|
204
|
-
</div>
|
|
205
|
-
<UIcon
|
|
206
|
-
:name="
|
|
207
|
-
open
|
|
208
|
-
? 'i-heroicons-chevron-down'
|
|
209
|
-
: 'i-heroicons-chevron-right'
|
|
210
|
-
"
|
|
211
|
-
/>
|
|
212
|
-
</div>
|
|
213
|
-
</template>
|
|
214
|
-
<template #content>
|
|
215
|
-
<div class="flex-1 relative">
|
|
216
|
-
<ClientOnly>
|
|
217
|
-
<StudioOutput :model-value="activeSchemaViewOutput.value" />
|
|
218
|
-
</ClientOnly>
|
|
219
|
-
</div>
|
|
220
|
-
</template>
|
|
221
|
-
</UCollapsible>
|
|
222
|
-
</UDashboardPanel>
|
|
223
|
-
</UDashboardGroup>
|
|
218
|
+
<template #actions
|
|
219
|
+
><StudioExpandButton
|
|
220
|
+
label="validation result"
|
|
221
|
+
:expanded="expandedPanel === 'result'"
|
|
222
|
+
@toggle="togglePanel('result')"
|
|
223
|
+
/></template>
|
|
224
|
+
</StudioValidation>
|
|
225
|
+
<StudioConversion
|
|
226
|
+
class="studio-mongoose"
|
|
227
|
+
:class="{
|
|
228
|
+
'is-mobile-active': mobileView === 'mongoose',
|
|
229
|
+
'is-expanded': expandedPanel === 'mongoose',
|
|
230
|
+
}"
|
|
231
|
+
:result="conversion"
|
|
232
|
+
>
|
|
233
|
+
<template #actions
|
|
234
|
+
><StudioExpandButton
|
|
235
|
+
label="Mongoose output"
|
|
236
|
+
:expanded="expandedPanel === 'mongoose'"
|
|
237
|
+
@toggle="togglePanel('mongoose')"
|
|
238
|
+
/></template>
|
|
239
|
+
</StudioConversion>
|
|
240
|
+
</div>
|
|
241
|
+
</main>
|
|
224
242
|
</div>
|
|
225
243
|
</template>
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { onBeforeUnmount, onMounted, shallowRef, watch } from "vue";
|
|
2
|
+
|
|
3
|
+
export type ValidationResult = {
|
|
4
|
+
status: "valid" | "invalid" | "json-error" | "parse-error" | "empty";
|
|
5
|
+
parsed?: string;
|
|
6
|
+
message?: string;
|
|
7
|
+
issues?: { path: string; message: string; code: string }[];
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type ConversionResult = {
|
|
11
|
+
definition?: string;
|
|
12
|
+
schemaObj?: string;
|
|
13
|
+
schemaTree?: string;
|
|
14
|
+
schemaPaths?: string;
|
|
15
|
+
error?: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type RunResponse = {
|
|
19
|
+
schemaNames: string[];
|
|
20
|
+
selectedSchema: string;
|
|
21
|
+
validation: ValidationResult;
|
|
22
|
+
conversion: ConversionResult;
|
|
23
|
+
example?: string;
|
|
24
|
+
exampleError?: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const STORAGE_KEY = "zod-mongoose-studio:v1";
|
|
28
|
+
|
|
29
|
+
export function useStudioRunner() {
|
|
30
|
+
const config = useRuntimeConfig();
|
|
31
|
+
const sourceCode = shallowRef(`// z and every @nullix/zod-mongoose helper are ready to use.
|
|
32
|
+
const UserSchema = z.object({
|
|
33
|
+
name: z.string().trim().min(2),
|
|
34
|
+
email: z.email(),
|
|
35
|
+
age: z.number().int().min(18).optional(),
|
|
36
|
+
role: z.enum(['reader', 'admin']).default('reader'),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const PostSchema = z.object({
|
|
40
|
+
title: z.string().min(1),
|
|
41
|
+
published: z.boolean().default(false),
|
|
42
|
+
});`);
|
|
43
|
+
const inputData = shallowRef(`{
|
|
44
|
+
"name": " Ada Lovelace ",
|
|
45
|
+
"email": "ada@example.com",
|
|
46
|
+
"age": 28
|
|
47
|
+
}`);
|
|
48
|
+
const schemaNames = shallowRef<string[]>([]);
|
|
49
|
+
const selectedSchema = shallowRef("");
|
|
50
|
+
const validation = shallowRef<ValidationResult>({ status: "empty", message: "Run the studio to see parsed data." });
|
|
51
|
+
const conversion = shallowRef<ConversionResult>({});
|
|
52
|
+
const isRunning = shallowRef(false);
|
|
53
|
+
const isDirty = shallowRef(false);
|
|
54
|
+
const hasRun = shallowRef(false);
|
|
55
|
+
const error = shallowRef("");
|
|
56
|
+
const exampleError = shallowRef("");
|
|
57
|
+
let debounceTimer: ReturnType<typeof setTimeout> | undefined;
|
|
58
|
+
let runId = 0;
|
|
59
|
+
|
|
60
|
+
async function run(action: "run" | "example" = "run") {
|
|
61
|
+
if (!config.public.isExecutionEnabled) return;
|
|
62
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
63
|
+
const currentRun = ++runId;
|
|
64
|
+
const submittedSource = sourceCode.value;
|
|
65
|
+
const submittedInput = inputData.value;
|
|
66
|
+
isRunning.value = true;
|
|
67
|
+
error.value = "";
|
|
68
|
+
exampleError.value = "";
|
|
69
|
+
try {
|
|
70
|
+
const response = await $fetch<RunResponse>(config.public.isDocsMode ? "/api/studio/run" : "/api/parse", {
|
|
71
|
+
method: "POST",
|
|
72
|
+
body: {
|
|
73
|
+
sourceCode: submittedSource,
|
|
74
|
+
inputData: submittedInput,
|
|
75
|
+
selectedSchema: selectedSchema.value || undefined,
|
|
76
|
+
action,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
if (currentRun !== runId) return;
|
|
80
|
+
schemaNames.value = response.schemaNames;
|
|
81
|
+
hasRun.value = true;
|
|
82
|
+
selectedSchema.value = response.selectedSchema;
|
|
83
|
+
validation.value = response.validation;
|
|
84
|
+
conversion.value = response.conversion;
|
|
85
|
+
exampleError.value = response.exampleError || "";
|
|
86
|
+
if (response.example && sourceCode.value === submittedSource && inputData.value === submittedInput) inputData.value = response.example;
|
|
87
|
+
isDirty.value = sourceCode.value !== submittedSource || (inputData.value !== submittedInput && inputData.value !== response.example);
|
|
88
|
+
} catch (caught: any) {
|
|
89
|
+
if (currentRun !== runId) return;
|
|
90
|
+
error.value = caught.data?.message || caught.message || "The studio could not run this schema.";
|
|
91
|
+
validation.value = { status: "empty", message: "Run the studio to see parsed data." };
|
|
92
|
+
conversion.value = {};
|
|
93
|
+
} finally {
|
|
94
|
+
if (currentRun === runId) isRunning.value = false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function selectSchema(name: string) {
|
|
99
|
+
selectedSchema.value = name;
|
|
100
|
+
isDirty.value = true;
|
|
101
|
+
if (config.public.isExecutionEnabled) void run();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function saveSession() {
|
|
105
|
+
if (!import.meta.client) return;
|
|
106
|
+
try {
|
|
107
|
+
sessionStorage.setItem(STORAGE_KEY, JSON.stringify({
|
|
108
|
+
sourceCode: sourceCode.value,
|
|
109
|
+
inputData: inputData.value,
|
|
110
|
+
selectedSchema: selectedSchema.value,
|
|
111
|
+
}));
|
|
112
|
+
} catch { /* Storage can be unavailable or full; the editor remains usable. */ }
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
watch(sourceCode, () => {
|
|
116
|
+
isDirty.value = true;
|
|
117
|
+
if (!config.public.isDocsMode) {
|
|
118
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
119
|
+
debounceTimer = setTimeout(() => void run(), 650);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
watch(inputData, () => { isDirty.value = true; }, { flush: "sync" });
|
|
123
|
+
watch([sourceCode, inputData, selectedSchema], saveSession, { flush: "sync" });
|
|
124
|
+
|
|
125
|
+
onMounted(() => {
|
|
126
|
+
try {
|
|
127
|
+
const saved = JSON.parse(sessionStorage.getItem(STORAGE_KEY) || "null");
|
|
128
|
+
if (saved && typeof saved === "object") {
|
|
129
|
+
if (typeof saved.sourceCode === "string" && saved.sourceCode.length <= 50_000) sourceCode.value = saved.sourceCode;
|
|
130
|
+
if (typeof saved.inputData === "string" && saved.inputData.length <= 100_000) inputData.value = saved.inputData;
|
|
131
|
+
if (typeof saved.selectedSchema === "string") selectedSchema.value = saved.selectedSchema;
|
|
132
|
+
}
|
|
133
|
+
} catch { /* Ignore invalid browser storage. */ }
|
|
134
|
+
if (config.public.isExecutionEnabled) void run();
|
|
135
|
+
});
|
|
136
|
+
onBeforeUnmount(() => { if (debounceTimer) clearTimeout(debounceTimer); });
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
sourceCode, inputData, schemaNames, selectedSchema, validation, conversion,
|
|
140
|
+
isRunning, isDirty, hasRun, error, exampleError, run, selectSchema,
|
|
141
|
+
};
|
|
142
|
+
}
|
package/app/pages/index.vue
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import editorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker";
|
|
2
2
|
import tsWorker from "monaco-editor/esm/vs/language/typescript/ts.worker?worker";
|
|
3
|
+
import jsonWorker from "monaco-editor/esm/vs/language/json/json.worker?worker";
|
|
3
4
|
|
|
4
5
|
export default defineNuxtPlugin(() => {
|
|
5
6
|
// Tell Monaco how to load its Web Workers via Vite
|
|
@@ -8,6 +9,7 @@ export default defineNuxtPlugin(() => {
|
|
|
8
9
|
if (label === "typescript" || label === "javascript") {
|
|
9
10
|
return new tsWorker();
|
|
10
11
|
}
|
|
12
|
+
if (label === "json") return new jsonWorker();
|
|
11
13
|
return new editorWorker();
|
|
12
14
|
},
|
|
13
15
|
};
|
package/bin/index.mjs
CHANGED
|
@@ -34,8 +34,13 @@ async function startStudio() {
|
|
|
34
34
|
const env = {
|
|
35
35
|
...process.env,
|
|
36
36
|
PORT: port.toString(),
|
|
37
|
+
NITRO_HOST: "127.0.0.1",
|
|
38
|
+
HOST: "127.0.0.1",
|
|
37
39
|
DOCS_MODE: "false", // CLI is NEVER docs mode
|
|
40
|
+
NUXT_PUBLIC_IS_DOCS_MODE: "false",
|
|
41
|
+
NUXT_PUBLIC_IS_EXECUTION_ENABLED: "true",
|
|
38
42
|
LOCAL_MODE: enableFs ? "true" : "false", // Enable FS unless opted out
|
|
43
|
+
NUXT_PUBLIC_IS_LOCAL_MODE: enableFs ? "true" : "false",
|
|
39
44
|
NODE_ENV: "production",
|
|
40
45
|
};
|
|
41
46
|
|
package/nuxt.config.ts
CHANGED
|
@@ -3,13 +3,14 @@ export default defineNuxtConfig({
|
|
|
3
3
|
|
|
4
4
|
runtimeConfig: {
|
|
5
5
|
public: {
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
isDocsMode: process.env.DOCS_MODE
|
|
6
|
+
// Standalone Studio development uses the loopback-only local endpoint.
|
|
7
|
+
// Embedders must explicitly opt into docs mode.
|
|
8
|
+
isDocsMode: process.env.DOCS_MODE === "true",
|
|
9
9
|
|
|
10
10
|
// By default, local file system access is OFF.
|
|
11
11
|
// The CLI script will explicitly pass LOCAL_MODE="true" to enable it.
|
|
12
12
|
isLocalMode: process.env.LOCAL_MODE === "true",
|
|
13
|
+
isExecutionEnabled: true,
|
|
13
14
|
},
|
|
14
15
|
},
|
|
15
16
|
css: ["~/assets/css/main.css"],
|