@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.
Files changed (118) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/_fonts/57NSSoFy1VLVs2gqly8Ls9awBnZMFyXGrefpmqvdqmc-zJfbBtpgM4cDmcXBsqZNW79_kFnlpPd62b48glgdydA.woff2 +0 -0
  3. package/.output/public/_fonts/8VR2wSMN-3U4NbWAVYXlkRV6hA0jFBXP-0RtL3X7fko-x2gYI4qfmkRdxyQQUPaBZdZdgl1TeVrquF_TxHeM4lM.woff2 +0 -0
  4. package/.output/public/_fonts/GsKUclqeNLJ96g5AU593ug6yanivOiwjW_7zESNPChw-jHA4tBeM1bjF7LATGUpfBuSTyomIFrWBTzjF7txVYfg.woff2 +0 -0
  5. package/.output/public/_fonts/Ld1FnTo3yTIwDyGfTQ5-Fws9AWsCbKfMvgxduXr7JcY-W25bL8NF1fjpLRSOgJb7RoZPHqGQNwMTM7S9tHVoxx8.woff2 +0 -0
  6. package/.output/public/_fonts/NdzqRASp2bovDUhQT1IRE_EMqKJ2KYQdTCfFcBvL8yw-KhwZiS86o3fErOe5GGMExHUemmI_dBfaEFxjISZrBd0.woff2 +0 -0
  7. package/.output/public/_fonts/iTkrULNFJJkTvihIg1Vqi5IODRH_9btXCioVF5l98I8-AndUyau2HR2felA_ra8V2mutQgschhasE5FD1dXGJX8.woff2 +0 -0
  8. package/.output/public/_nuxt/{qQyvCkbV.js → 5Tu2WUJz.js} +1 -1
  9. package/.output/public/_nuxt/B0N6xv5E.js +1 -0
  10. package/.output/public/_nuxt/{DjPT-NCE.js → B0cUlOSM.js} +1 -1
  11. package/.output/public/_nuxt/B9paGgN0.js +33 -0
  12. package/.output/public/_nuxt/{IxYCG8hB.js → BCIlnd0g.js} +1 -1
  13. package/.output/public/_nuxt/{ikxafTHA.js → BNDnV2op.js} +180 -184
  14. package/.output/public/_nuxt/{Ce-T_1aD.js → BO4hqv6q.js} +1 -1
  15. package/.output/public/_nuxt/{Bd32GOUy.js → BOsJJ_ZU.js} +1 -1
  16. package/.output/public/_nuxt/{BzhSDy30.js → C2BHp-MG.js} +1 -1
  17. package/.output/public/_nuxt/{Cs8GXJCB.js → CKULsFUz.js} +1 -1
  18. package/.output/public/_nuxt/CMH9iX_E.js +6 -0
  19. package/.output/public/_nuxt/{tzj4nvDE.js → CzbCpNnc.js} +2 -2
  20. package/.output/public/_nuxt/{CC3n3Aga.js → D87QMS6j.js} +1 -1
  21. package/.output/public/_nuxt/DA0pQuyP.js +27 -0
  22. package/.output/public/_nuxt/{B7P0wQ35.js → DcJ7oPvZ.js} +1 -1
  23. package/.output/public/_nuxt/{Ick_23I4.js → Dce9SnGa.js} +1 -1
  24. package/.output/public/_nuxt/{CAd-qHfI.js → DpKrXpkK.js} +1 -1
  25. package/.output/public/_nuxt/DsnIpnT9.js +1 -0
  26. package/.output/public/_nuxt/{CNZorjDf.js → I3Ucam2s.js} +1 -1
  27. package/.output/public/_nuxt/builds/latest.json +1 -1
  28. package/.output/public/_nuxt/builds/meta/51f1351c-0667-4235-9ddc-16582a51ce03.json +1 -0
  29. package/.output/public/_nuxt/{entry.J_CbC0oY.css → entry.B-vs1tRH.css} +1 -1
  30. package/.output/public/_nuxt/error-404.CTSXlFYa.css +1 -0
  31. package/.output/public/_nuxt/error-500.DqEfQnVZ.css +1 -0
  32. package/.output/public/_nuxt/{nDZOJ7rN.js → o-NCh2rp.js} +2 -2
  33. package/.output/public/_nuxt/{qwxNOyg7.js → sKEJiAHO.js} +1 -1
  34. package/.output/public/_nuxt/{81vqh61j.js → tqQNlhT9.js} +1 -1
  35. package/.output/public/_nuxt/ypRHVLFO.js +1 -0
  36. package/.output/server/chunks/_/error-500.mjs.map +1 -1
  37. package/.output/server/chunks/build/client.precomputed.mjs +1 -1
  38. package/.output/server/chunks/build/{error-404-BN-7rNE4.mjs → error-404-KRkA0DPI.mjs} +10 -4
  39. package/.output/server/chunks/build/error-404-KRkA0DPI.mjs.map +1 -0
  40. package/.output/server/chunks/build/error-404-styles.I0NYw-Bn.mjs +8 -0
  41. package/.output/server/chunks/build/error-404-styles.I0NYw-Bn.mjs.map +1 -0
  42. package/.output/server/chunks/build/{error-500-DhIQ-2yt.mjs → error-500-DIIFf55p.mjs} +10 -4
  43. package/.output/server/chunks/build/error-500-DIIFf55p.mjs.map +1 -0
  44. package/.output/server/chunks/build/error-500-styles.D1cs9hQy.mjs +8 -0
  45. package/.output/server/chunks/build/error-500-styles.D1cs9hQy.mjs.map +1 -0
  46. package/.output/server/chunks/build/index-CB1N4gax.mjs +557 -0
  47. package/.output/server/chunks/build/index-CB1N4gax.mjs.map +1 -0
  48. package/.output/server/chunks/build/server.mjs +326 -248
  49. package/.output/server/chunks/build/server.mjs.map +1 -1
  50. package/.output/server/chunks/build/styles.mjs +4 -4
  51. package/.output/server/chunks/nitro/nitro.mjs +2328 -861
  52. package/.output/server/chunks/nitro/nitro.mjs.map +1 -1
  53. package/.output/server/chunks/routes/api/editor-types.get.mjs +44 -0
  54. package/.output/server/chunks/routes/api/editor-types.get.mjs.map +1 -0
  55. package/.output/server/chunks/routes/api/parse.post.mjs +26 -1448
  56. package/.output/server/chunks/routes/api/parse.post.mjs.map +1 -1
  57. package/.output/server/chunks/routes/api/resolve.get.mjs +6 -0
  58. package/.output/server/chunks/routes/api/resolve.get.mjs.map +1 -1
  59. package/.output/server/chunks/routes/api/studio/run.post.mjs +35 -0
  60. package/.output/server/chunks/routes/api/studio/run.post.mjs.map +1 -0
  61. package/.output/server/chunks/routes/renderer.mjs +1 -1
  62. package/.output/server/chunks/routes/renderer.mjs.map +1 -1
  63. package/.output/server/index.mjs +7 -1
  64. package/.output/server/index.mjs.map +1 -1
  65. package/.output/server/node_modules/acorn/dist/acorn.mjs +6266 -0
  66. package/.output/server/node_modules/acorn/package.json +50 -0
  67. package/.output/server/node_modules/ip-address/dist/common.js +112 -4
  68. package/.output/server/node_modules/ip-address/dist/ipv4.js +322 -87
  69. package/.output/server/node_modules/ip-address/dist/ipv6.js +654 -199
  70. package/.output/server/node_modules/ip-address/dist/v4/constants.js +45 -2
  71. package/.output/server/node_modules/ip-address/dist/v6/constants.js +55 -3
  72. package/.output/server/node_modules/ip-address/dist/v6/helpers.js +12 -3
  73. package/.output/server/node_modules/ip-address/package.json +49 -37
  74. package/.output/server/node_modules/ufo/dist/index.mjs +2 -2
  75. package/.output/server/node_modules/ufo/package.json +9 -9
  76. package/.output/server/package.json +4 -3
  77. package/README.md +62 -19
  78. package/app/assets/css/main.css +1 -0
  79. package/app/assets/css/studio.css +131 -0
  80. package/app/components/StudioConversion.vue +41 -0
  81. package/app/components/StudioDataPanel.vue +21 -0
  82. package/app/components/StudioEditor.vue +78 -102
  83. package/app/components/StudioExpandButton.vue +11 -0
  84. package/app/components/StudioOutput.vue +7 -1
  85. package/app/components/StudioSourcePanel.vue +16 -0
  86. package/app/components/StudioValidation.vue +47 -0
  87. package/app/components/ZodMongooseStudio.vue +216 -198
  88. package/app/composables/useStudioRunner.ts +142 -0
  89. package/app/pages/index.vue +1 -1
  90. package/app/plugins/monaco.client.ts +2 -0
  91. package/bin/index.mjs +5 -0
  92. package/nuxt.config.ts +4 -3
  93. package/package.json +4 -2
  94. package/server/api/editor-types.get.ts +28 -0
  95. package/server/api/parse.post.ts +17 -175
  96. package/server/api/studio/run.post.ts +17 -0
  97. package/server/middleware/security.ts +29 -7
  98. package/server/utils/example-data.ts +80 -0
  99. package/server/utils/parse-request.ts +28 -0
  100. package/server/utils/runner-client.ts +40 -0
  101. package/server/utils/studio-execution.ts +176 -0
  102. package/.output/public/_nuxt/BD40YYqZ.js +0 -33
  103. package/.output/public/_nuxt/BlQmLjV4.js +0 -1
  104. package/.output/public/_nuxt/CJ4Cv9fY.js +0 -61
  105. package/.output/public/_nuxt/builds/meta/495a8ebb-444d-4414-9b98-c181aa84d817.json +0 -1
  106. package/.output/public/_nuxt/error-404.64pZEiDn.css +0 -1
  107. package/.output/public/_nuxt/error-500.DHOpI0Ir.css +0 -1
  108. package/.output/public/_nuxt/rH_O4qXT.js +0 -1
  109. package/.output/server/chunks/build/error-404-BN-7rNE4.mjs.map +0 -1
  110. package/.output/server/chunks/build/error-404-styles.Byx46oLR.mjs +0 -8
  111. package/.output/server/chunks/build/error-404-styles.Byx46oLR.mjs.map +0 -1
  112. package/.output/server/chunks/build/error-500-DhIQ-2yt.mjs.map +0 -1
  113. package/.output/server/chunks/build/error-500-styles.BL3UT0Wa.mjs +0 -8
  114. package/.output/server/chunks/build/error-500-styles.BL3UT0Wa.mjs.map +0 -1
  115. package/.output/server/chunks/build/index-B4T6zHp0.mjs +0 -1904
  116. package/.output/server/chunks/build/index-B4T6zHp0.mjs.map +0 -1
  117. package/.output/server/node_modules/zod/index.js +0 -4
  118. /package/.output/public/_nuxt/{editor.CdBKkuhR.css → monaco.CdBKkuhR.css} +0 -0
@@ -0,0 +1,131 @@
1
+ .studio-shell {
2
+ --studio-bg: #f5f7f9;
3
+ --studio-surface: #fff;
4
+ --studio-border: #dce3ea;
5
+ --studio-text: #172b3d;
6
+ --studio-muted: #627588;
7
+ --studio-accent: #2e608a;
8
+ display: flex;
9
+ flex-direction: column;
10
+ height: var(--studio-height, 100dvh);
11
+ min-height: 540px;
12
+ overflow: hidden;
13
+ color: var(--studio-text);
14
+ background: var(--studio-bg);
15
+ font-family: 'Public Sans', system-ui, sans-serif;
16
+ font-size: 13px;
17
+ }
18
+ .studio-shell-docs { height: var(--studio-height, calc(100dvh - var(--ui-header-height, 64px))); }
19
+ .studio-shell-docs.studio-standalone { height: var(--studio-height, 100dvh); }
20
+ .studio-header, .studio-subbar, .studio-card-head { display: flex; align-items: center; justify-content: space-between; }
21
+ .studio-header { gap: 20px; min-height: 68px; padding: 12px 22px; border-bottom: 1px solid var(--studio-border); background: var(--studio-surface); }
22
+ .studio-brand h1 { margin: 0; font-size: 20px; font-weight: 700; line-height: 1.2; letter-spacing: -.025em; }
23
+ .studio-eyebrow { margin: 0 0 3px; color: var(--studio-muted); font-size: 11px; font-weight: 600; line-height: 1.3; }
24
+ .studio-header-actions, .studio-subbar-copy, .studio-schema-picker, .studio-card-heading { display: flex; align-items: center; gap: 12px; }
25
+ .studio-mode { color: var(--studio-muted); font-size: 12px; white-space: nowrap; }
26
+ .studio-shortcut { color: #8795a2; font-size: 11px; white-space: nowrap; }
27
+ .studio-run { min-height: 36px; padding: 0 17px; border: 1px solid #2e608a; border-radius: 6px; color: #fff; background: #2e608a; font: 600 12px 'Public Sans', system-ui, sans-serif; cursor: pointer; }
28
+ .studio-run:hover:not(:disabled) { background: #264e70; }
29
+ .studio-run:disabled, .studio-example-button:disabled { opacity: .55; cursor: wait; }
30
+ .studio-run:focus-visible, .studio-example-button:focus-visible, .studio-tabs button:focus-visible, .studio-mobile-nav button:focus-visible, .studio-schema-picker select:focus-visible { outline: 2px solid #2e608a; outline-offset: 2px; }
31
+ .studio-subbar { gap: 16px; min-height: 46px; padding: 7px 22px; border-bottom: 1px solid var(--studio-border); background: #fff; }
32
+ .studio-subbar-copy { gap: 8px; color: var(--studio-muted); font-size: 11px; }
33
+ .studio-pulse { width: 7px; height: 7px; border-radius: 50%; background: #48a178; }
34
+ .studio-pulse.is-busy { background: #d3913f; }
35
+ .studio-pulse.error { background: #a33737; }
36
+ .studio-schema-picker span { color: var(--studio-muted); font-size: 11px; }
37
+ .studio-schema-picker select { min-width: 165px; max-width: 260px; padding: 6px 28px 6px 9px; border: 1px solid var(--studio-border); border-radius: 5px; background: #fff; color: var(--studio-text); font: 12px 'Public Sans', system-ui, sans-serif; cursor: pointer; }
38
+ .studio-schema-picker select:disabled { color: var(--studio-muted); cursor: default; }
39
+ .studio-workspace { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 8px; min-height: 0; flex: 1; padding: 8px; }
40
+ .studio-column { display: grid; min-width: 0; min-height: 0; gap: 8px; }
41
+ .studio-input-column { grid-template-rows: minmax(0, 1.18fr) minmax(0, .82fr); }
42
+ .studio-output-column { grid-template-rows: minmax(0, .82fr) minmax(0, 1.18fr); }
43
+ .studio-card { display: flex; flex-direction: column; min-width: 0; min-height: 0; overflow: hidden; border: 1px solid var(--studio-border); border-radius: 6px; background: var(--studio-surface); }
44
+ .studio-card-head { gap: 12px; min-height: 59px; padding: 10px 14px; border-bottom: 1px solid var(--studio-border); }
45
+ .studio-card-heading { min-width: 0; }
46
+ .studio-card h2 { margin: 0; font-size: 14px; font-weight: 700; line-height: 1.25; }
47
+ .studio-editor-surface, .studio-conversion-output { position: relative; min-height: 0; flex: 1; background: #1e1e1e; }
48
+ .studio-card-foot { min-height: 33px; padding: 8px 14px; border-top: 1px solid var(--studio-border); color: var(--studio-muted); font-size: 11px; line-height: 1.4; }
49
+ .studio-card-foot code { color: #264e70; font-family: ui-monospace, monospace; }
50
+ .studio-example-button { padding: 6px 10px; border: 1px solid var(--studio-border); border-radius: 5px; color: var(--studio-accent); background: #fff; font: 600 11px 'Public Sans', system-ui, sans-serif; white-space: nowrap; cursor: pointer; }
51
+ .studio-example-button:hover:not(:disabled) { background: #f2f7fa; }
52
+ .studio-status { padding: 4px 8px; border-radius: 4px; color: var(--studio-muted); background: #edf1f5; font-size: 11px; font-weight: 600; }
53
+ .studio-status-valid { color: #237351; background: #e5f5ec; }
54
+ .studio-status-invalid { color: #a33737; background: #fceaea; }
55
+ .studio-status-stale { color: #906221; background: #fff4dc; }
56
+ .studio-validation-body { min-height: 0; flex: 1; overflow: auto; padding: 16px; }
57
+ .studio-result-intro { display: flex; align-items: flex-start; gap: 10px; padding-bottom: 13px; }
58
+ .studio-result-intro strong { display: block; margin: 1px 0 3px; font-size: 12px; }
59
+ .studio-result-intro p { margin: 0; color: var(--studio-muted); font-size: 11px; line-height: 1.5; }
60
+ .studio-result-icon { display: flex; align-items: center; justify-content: center; width: 24px; height: 24px; flex: none; border-radius: 4px; color: var(--studio-accent); background: #e2ecf4; font: 700 11px ui-monospace, monospace; }
61
+ .studio-result-icon-valid { color: #237351; background: #e5f5ec; }
62
+ .studio-result-icon-invalid { color: #a33737; background: #fceaea; }
63
+ .studio-parsed { margin: 0; padding: 13px; overflow: auto; border: 1px solid var(--studio-border); border-radius: 5px; color: var(--studio-text); background: #f8fafc; font: 12px/1.65 ui-monospace, monospace; white-space: pre-wrap; overflow-wrap: anywhere; }
64
+ .studio-issues { display: grid; gap: 8px; margin: 0; padding: 0; list-style: none; }
65
+ .studio-issues li { display: flex; gap: 12px; align-items: flex-start; padding: 10px; border: 1px solid #f1cdcd; border-radius: 5px; background: #fff7f7; color: #8c3333; font-size: 11px; line-height: 1.5; }
66
+ .studio-issues code { min-width: 82px; font-family: ui-monospace, monospace; overflow-wrap: anywhere; }
67
+ .studio-tabs { display: flex; gap: 3px; padding: 6px 9px; border-bottom: 1px solid var(--studio-border); background: #fff; }
68
+ .studio-tabs button { padding: 6px 10px; border: 0; border-radius: 4px; color: var(--studio-muted); background: transparent; font: 600 11px 'Public Sans', system-ui, sans-serif; cursor: pointer; }
69
+ .studio-tabs button:hover { color: var(--studio-accent); }
70
+ .studio-tabs button.is-active { color: var(--studio-accent); background: #e2ecf4; }
71
+ .studio-error-banner { display: flex; gap: 12px; align-items: flex-start; padding: 9px 22px; border-bottom: 1px solid #f1cdcd; color: #8c3333; background: #fff1f1; font-size: 11px; }
72
+ .studio-error-banner strong { white-space: nowrap; }
73
+ .studio-error-banner span { min-width: 0; overflow-wrap: anywhere; }
74
+ .studio-example-banner { border-color: #ead6ad; color: #805a20; background: #fff8e8; }
75
+ .studio-mobile-nav { display: none; }
76
+ @media (max-width: 900px) {
77
+ .studio-header { min-height: 60px; padding: 11px 15px; }
78
+ .studio-subbar { padding-inline: 15px; }
79
+ .studio-workspace { display: block; padding: 7px; }
80
+ .studio-column { display: contents; }
81
+ .studio-card { display: none; height: 100%; }
82
+ .studio-card.is-mobile-active { display: flex; }
83
+ .studio-mobile-nav { display: grid; grid-template-columns: repeat(4, 1fr); gap: 3px; padding: 5px 7px; border-bottom: 1px solid var(--studio-border); background: #fff; }
84
+ .studio-mobile-nav button { min-height: 34px; border: 0; border-radius: 4px; color: var(--studio-muted); background: transparent; font-size: 11px; font-weight: 600; cursor: pointer; }
85
+ .studio-mobile-nav button.is-active { color: var(--studio-accent); background: #e2ecf4; }
86
+ }
87
+ @media (max-width: 560px) {
88
+ .studio-header { gap: 8px; }
89
+ .studio-brand h1 { font-size: 17px; }
90
+ .studio-mode, .studio-shortcut, .studio-schema-picker span { display: none; }
91
+ .studio-run { padding-inline: 12px; }
92
+ .studio-schema-picker select { min-width: 120px; max-width: 150px; }
93
+ .studio-card-head { padding-inline: 11px; }
94
+ }
95
+ .dark .studio-shell {
96
+ --studio-bg: #0f1722;
97
+ --studio-surface: #172231;
98
+ --studio-border: #334255;
99
+ --studio-text: #e9eef5;
100
+ --studio-muted: #a5b2c1;
101
+ --studio-accent: #8cbce5;
102
+ }
103
+ .dark .studio-subbar, .dark .studio-tabs, .dark .studio-mobile-nav { background: var(--studio-surface); }
104
+ .dark .studio-schema-picker select, .dark .studio-example-button { color: var(--studio-text); background: #202e40; border-color: var(--studio-border); }
105
+ .dark .studio-example-button:hover:not(:disabled) { background: #2b3b50; }
106
+ .dark .studio-run { color: #fff; background: #2e608a; }
107
+ .dark .studio-card-foot code { color: #a5d3fa; }
108
+ .dark .studio-status { color: #b5c4d2; background: #2b3b4b; }
109
+ .dark .studio-status-valid { color: #a6e9c3; background: #214637; }
110
+ .dark .studio-status-invalid { color: #f5b7b7; background: #4c3034; }
111
+ .dark .studio-status-stale { color: #f0d194; background: #4a3e29; }
112
+ .dark .studio-result-icon, .dark .studio-tabs button.is-active, .dark .studio-mobile-nav button.is-active { color: #a5d3fa; background: #25435e; }
113
+ .dark .studio-result-icon-valid { color: #a6e9c3; background: #214637; }
114
+ .dark .studio-result-icon-invalid { color: #f5b7b7; background: #4c3034; }
115
+ .dark .studio-parsed { color: var(--studio-text); background: #101b29; border-color: var(--studio-border); }
116
+ .dark .studio-issues li, .dark .studio-error-banner { color: #f5b7b7; background: #392a30; border-color: #75434b; }
117
+ .dark .studio-example-banner { color: #f0d194; background: #403624; border-color: #76613b; }
118
+ .dark .studio-editor-surface, .dark .studio-conversion-output { background: #1e1e1e; }
119
+ .studio-editor-surface, .studio-conversion-output { background: #fff; }
120
+ .studio-editor-fallback { display: block; width: 100%; height: 100%; box-sizing: border-box; resize: none; border: 0; outline: none; padding: 16px; color: var(--studio-text); background: var(--studio-surface); font: 13px/1.75 ui-monospace, monospace; }
121
+ .studio-shell { position: relative; }
122
+ .studio-card-actions { display: flex; align-items: center; gap: 8px; }
123
+ .studio-expand-button { display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px; flex: none; border: 1px solid var(--studio-border); border-radius: 5px; color: var(--studio-muted); background: var(--studio-surface); cursor: pointer; }
124
+ .studio-expand-button:hover { color: var(--studio-accent); border-color: var(--studio-accent); }
125
+ .studio-expand-button:focus-visible { outline: 2px solid var(--studio-accent); outline-offset: 2px; }
126
+ .studio-shell.is-focused .studio-card:not(.is-expanded) { visibility: hidden; }
127
+ .studio-card.is-expanded { display: flex; position: absolute; inset: 8px; z-index: 30; height: auto; box-shadow: 0 12px 40px #0002; }
128
+ ::view-transition-old(studio-expanded-panel), ::view-transition-new(studio-expanded-panel) { animation-duration: .28s; animation-timing-function: cubic-bezier(.2,.7,.2,1); }
129
+ @media (prefers-reduced-motion: reduce) {
130
+ ::view-transition-old(studio-expanded-panel), ::view-transition-new(studio-expanded-panel) { animation-duration: .01ms; }
131
+ }
@@ -0,0 +1,41 @@
1
+ <script setup lang="ts">
2
+ import { computed, shallowRef } from "vue";
3
+ import type { ConversionResult } from "../composables/useStudioRunner";
4
+
5
+ const props = defineProps<{ result: ConversionResult }>();
6
+ const active = shallowRef<"definition" | "obj" | "tree" | "paths">("definition");
7
+ const views = [
8
+ { key: "definition", label: "Definition" },
9
+ { key: "obj", label: ".obj" },
10
+ { key: "tree", label: ".tree" },
11
+ { key: "paths", label: ".paths" },
12
+ ] as const;
13
+ const output = computed(() => {
14
+ if (props.result.error) return `// Conversion error\n${props.result.error}`;
15
+ switch (active.value) {
16
+ case "definition": return props.result.definition || "// Run a schema to see its Mongoose definition.";
17
+ case "obj": return props.result.schemaObj || "// No schema object yet.";
18
+ case "tree": return props.result.schemaTree || "// No schema tree yet.";
19
+ case "paths": return props.result.schemaPaths || "// No schema paths yet.";
20
+ }
21
+ });
22
+ </script>
23
+
24
+ <template>
25
+ <section class="studio-card studio-conversion" aria-labelledby="conversion-title">
26
+ <div class="studio-card-head">
27
+ <div class="studio-card-heading">
28
+ <div><p class="studio-eyebrow">Mongoose</p><h2 id="conversion-title">Schema output</h2></div>
29
+ </div>
30
+ <slot name="actions" />
31
+ </div>
32
+ <div class="studio-tabs" role="tablist" aria-label="Mongoose output view">
33
+ <button v-for="view in views" :key="view.key" type="button" role="tab"
34
+ :aria-selected="active === view.key" :class="{ 'is-active': active === view.key }"
35
+ @click="active = view.key">{{ view.label }}</button>
36
+ </div>
37
+ <div class="studio-conversion-output" role="tabpanel">
38
+ <ClientOnly><StudioOutput :model-value="output" /></ClientOnly>
39
+ </div>
40
+ </section>
41
+ </template>
@@ -0,0 +1,21 @@
1
+ <script setup lang="ts">
2
+ defineProps<{ isRunning: boolean; canRun: boolean }>();
3
+ const emit = defineEmits<{ loadExample: [] }>();
4
+ const input = defineModel<string>({ required: true });
5
+ </script>
6
+
7
+ <template>
8
+ <section class="studio-card studio-data" aria-labelledby="data-title">
9
+ <div class="studio-card-head">
10
+ <div class="studio-card-heading">
11
+ <div><p class="studio-eyebrow">JSON</p><h2 id="data-title">Input data</h2></div>
12
+ </div>
13
+ <div class="studio-card-actions">
14
+ <button type="button" class="studio-example-button" :disabled="isRunning || !canRun" @click="emit('loadExample')">Load example</button>
15
+ <slot name="actions" />
16
+ </div>
17
+ </div>
18
+ <div class="studio-editor-surface"><ClientOnly><StudioEditor v-model="input" language="json" label="JSON data editor" /></ClientOnly></div>
19
+ <div class="studio-card-foot">Enter a JSON value, then run to see what Zod returns.</div>
20
+ </section>
21
+ </template>
@@ -1,114 +1,90 @@
1
1
  <script setup lang="ts">
2
- import { ref, shallowRef, onMounted, onBeforeUnmount, watch } from "vue";
3
-
4
- const props = defineProps<{ modelValue: string }>();
5
- const emit = defineEmits(["update:modelValue"]);
6
-
7
- const editorContainer = ref<HTMLElement | null>(null);
8
- // shallowRef is REQUIRED for Monaco instances in Vue 3!
9
- const editorInstance = shallowRef<any>(null);
2
+ import { onBeforeUnmount, onMounted, ref, shallowRef, watch } from "vue";
3
+
4
+ const model = defineModel<string>({ required: true });
5
+ const props = withDefaults(defineProps<{
6
+ language?: "typescript" | "json";
7
+ label?: string;
8
+ }>(), { language: "typescript", label: "Code editor" });
9
+
10
+ const container = ref<HTMLElement | null>(null);
11
+ const editor = shallowRef<import("monaco-editor").editor.IStandaloneCodeEditor | null>(null);
12
+ const editorUnavailable = shallowRef(false);
13
+ const colorMode = useColorMode();
14
+ let typeLibrariesLoaded: Promise<void> | undefined;
15
+
16
+ const helperNames = [
17
+ "bufferMongooseGetter", "callHookSync", "extractMongooseDef", "genTimestampsSchema",
18
+ "getFrontendMode", "getMongoose", "getMongooseMeta", "hooks", "mongooseRegistry",
19
+ "objectStrictness", "populateZodSchema", "setFrontendMode", "setMongoose",
20
+ "toMongooseSchema", "toStrictModel", "unwrapZodSchema", "withMongoose",
21
+ "zBuffer", "zObjectId", "zPoint", "zPolygon", "zRef",
22
+ ];
10
23
 
11
24
  onMounted(async () => {
12
- if (!editorContainer.value) return;
13
-
14
- // Dynamically import monaco
15
- const monaco = await import("monaco-editor");
16
-
17
- monaco.typescript.typescriptDefaults.setCompilerOptions({
18
- target: monaco.typescript.ScriptTarget.ESNext,
19
- allowNonTsExtensions: true,
20
- moduleResolution: monaco.typescript.ModuleResolutionKind.NodeJs,
21
- module: monaco.typescript.ModuleKind.ESNext, // Allow ES6 exports
22
- noEmit: true,
23
- });
24
-
25
- // 1. Zod v4 shim (so imports don't error in the editor)
26
- monaco.typescript.typescriptDefaults.addExtraLib(
27
- `
28
- declare module 'zod/v4' {
29
- export const z: any;
30
- export type ZodTypeAny = any;
31
- export type ZodObject<T> = any;
32
- export type ZodString = any;
33
- export type ZodNumber = any;
34
- }
35
- `,
36
- "file:///node_modules/@types/zod-v4/index.d.ts",
37
- );
38
-
39
- monaco.typescript.typescriptDefaults.addExtraLib(
40
- `
41
- declare module '@nullix/zod-mongoose' {
42
- import { z } from 'zod/v4';
43
- import mongoose, { SchemaOptions } from 'mongoose';
44
- import * as hookable from 'hookable';
45
-
46
- interface MongooseMeta extends Record<string, any> {
47
- explicitId?: boolean;
48
- }
49
- declare const mongooseRegistry: z.core.$ZodRegistry<MongooseMeta, z.core.$ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
50
- declare function withMongoose<T extends z.ZodTypeAny>(schema: T, meta: MongooseMeta): T;
51
-
52
- type ToMongooseType<T extends z.ZodTypeAny> = any; // Simplified for Monaco perf
53
-
54
- export declare function extractMongooseDef<T extends z.ZodTypeAny>(schema: T, visited?: Map<z.ZodTypeAny, any>, isField?: boolean): ToMongooseType<T> & Record<string, any>;
55
-
56
- interface ToMongooseSchemaOptions extends SchemaOptions {
57
- plugins?: Array<(schema: mongoose.Schema, options?: any) => void>;
58
- }
59
-
60
- export declare function toMongooseSchema<T extends z.ZodTypeAny>(schema: T, options?: ToMongooseSchemaOptions): mongoose.Schema<z.infer<T>>;
61
-
62
- export declare const zObjectId: (options?: MongooseMeta) => any;
63
- export declare const zBuffer: (options?: MongooseMeta) => any;
64
- export declare const zRef: <T extends z.ZodTypeAny>(ref: string, schema: T, options?: MongooseMeta) => any;
65
- export declare const genTimestampsSchema: <CrAt = "createdAt", UpAt = "updatedAt">(createdAtField?: any, updatedAtField?: any) => any;
66
-
67
- export type PopulatedSchema<T, K extends keyof T> = any;
68
- export declare const bufferMongooseGetter: (value: unknown) => any;
69
- export declare const getMongoose: () => any;
70
- export declare const setFrontendMode: (enabled: boolean) => void;
71
- export declare const getFrontendMode: () => boolean;
72
-
73
- export type MongooseZodHooks = any;
74
- export declare const hooks: any;
75
- export declare function callHookSync<Name extends keyof MongooseZodHooks>(name: Name, ...args: Parameters<MongooseZodHooks[Name]>): void;
25
+ if (!container.value) return;
26
+ try {
27
+ const monaco = await import("monaco-editor");
28
+ if (!container.value) return;
29
+ if (props.language === "typescript") {
30
+ const tsMonaco: any = await import("monaco-editor/esm/vs/language/typescript/monaco.contribution.js");
31
+ tsMonaco.typescriptDefaults.setCompilerOptions({
32
+ target: tsMonaco.ScriptTarget.ESNext,
33
+ allowNonTsExtensions: true,
34
+ moduleResolution: tsMonaco.ModuleResolutionKind.NodeJs,
35
+ module: tsMonaco.ModuleKind.ESNext,
36
+ noEmit: true,
37
+ });
38
+ typeLibrariesLoaded ||= $fetch<Record<string, string>>("/api/editor-types").then((files) => {
39
+ for (const [path, content] of Object.entries(files)) tsMonaco.typescriptDefaults.addExtraLib(content, path);
40
+ }).catch(() => {
41
+ // Types improve completion, but a missing endpoint must not hide the editor.
42
+ });
43
+ await typeLibrariesLoaded;
44
+ tsMonaco.typescriptDefaults.addExtraLib(`
45
+ declare const z: typeof import("zod/v4").z;
46
+ declare const mongoose: any;
47
+ ${helperNames.map((name) => `declare const ${name}: any;`).join("\n")}
48
+ declare module '@nullix/zod-mongoose' {
49
+ ${helperNames.map((name) => `export const ${name}: any;`).join("\n")}
50
+ }
51
+ `, "file:///node_modules/@types/studio/globals.d.ts");
76
52
  }
77
- `,
78
- "file:///node_modules/@nullix/zod-mongoose/index.d.ts",
79
- );
80
-
81
- editorInstance.value = monaco.editor.create(editorContainer.value, {
82
- value: props.modelValue,
83
- language: "typescript",
84
- theme: "vs-dark",
85
- minimap: { enabled: false },
86
- automaticLayout: true,
87
- fontSize: 14,
88
- padding: { top: 16 },
89
- });
90
53
 
91
- editorInstance.value.onDidChangeModelContent(() => {
92
- emit("update:modelValue", editorInstance.value?.getValue() || "");
93
- });
54
+ editor.value = monaco.editor.create(container.value, {
55
+ value: model.value,
56
+ language: props.language,
57
+ theme: colorMode.value === "dark" ? "vs-dark" : "vs",
58
+ minimap: { enabled: false },
59
+ automaticLayout: true,
60
+ fontSize: 13,
61
+ fontFamily: "'SFMono-Regular', Consolas, 'Liberation Mono', monospace",
62
+ lineHeight: 23,
63
+ padding: { top: 16, bottom: 16 },
64
+ scrollBeyondLastLine: false,
65
+ roundedSelection: false,
66
+ wordWrap: "on",
67
+ renderLineHighlight: "line",
68
+ scrollbar: { verticalScrollbarSize: 7, horizontalScrollbarSize: 7 },
69
+ ariaLabel: props.label,
70
+ });
71
+ editor.value.onDidChangeModelContent(() => { model.value = editor.value?.getValue() || ""; });
72
+ } catch {
73
+ editorUnavailable.value = true;
74
+ }
94
75
  });
95
76
 
96
- watch(
97
- () => props.modelValue,
98
- (newValue) => {
99
- if (editorInstance.value && editorInstance.value.getValue() !== newValue) {
100
- editorInstance.value.setValue(newValue);
101
- }
102
- },
103
- );
104
-
105
- onBeforeUnmount(() => {
106
- if (editorInstance.value) {
107
- editorInstance.value.dispose();
108
- }
77
+ watch(model, (value) => {
78
+ if (editor.value && editor.value.getValue() !== value) editor.value.setValue(value);
109
79
  });
80
+ watch(() => colorMode.value, (mode) => {
81
+ editor.value?.updateOptions({ theme: mode === "dark" ? "vs-dark" : "vs" });
82
+ });
83
+
84
+ onBeforeUnmount(() => { editor.value?.dispose(); });
110
85
  </script>
111
86
 
112
87
  <template>
113
- <div ref="editorContainer" class="w-full h-full" />
88
+ <textarea v-if="editorUnavailable" v-model="model" class="studio-editor-fallback" :aria-label="label" spellcheck="false" />
89
+ <div v-else ref="container" class="studio-monaco h-full w-full" />
114
90
  </template>
@@ -0,0 +1,11 @@
1
+ <script setup lang="ts">
2
+ defineProps<{ expanded: boolean; label: string }>();
3
+ const emit = defineEmits<{ toggle: [] }>();
4
+ </script>
5
+
6
+ <template>
7
+ <button type="button" class="studio-expand-button" :aria-label="expanded ? `Restore ${label}` : `Expand ${label}`" :title="expanded ? 'Restore panel' : 'Expand panel'" @click="emit('toggle')">
8
+ <svg v-if="expanded" viewBox="0 0 20 20" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true"><path d="M7 2v5H2m16 6h-5v5M2 7l5-5m6 16 5-5" /></svg>
9
+ <svg v-else viewBox="0 0 20 20" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true"><path d="M2 7V2h5m6 16h5v-5M2 2l5 5m6 6 5 5" /></svg>
10
+ </button>
11
+ </template>
@@ -4,6 +4,7 @@ import { ref, shallowRef, onMounted, onBeforeUnmount, watch } from "vue";
4
4
  const props = defineProps<{ modelValue: string }>();
5
5
  const outputContainer = ref<HTMLElement | null>(null);
6
6
  const editorInstance = shallowRef<any>(null);
7
+ const colorMode = useColorMode();
7
8
 
8
9
  onMounted(async () => {
9
10
  if (!outputContainer.value) return;
@@ -13,12 +14,14 @@ onMounted(async () => {
13
14
  editorInstance.value = monaco.editor.create(outputContainer.value, {
14
15
  value: props.modelValue,
15
16
  language: "typescript",
16
- theme: "vs-dark",
17
+ theme: colorMode.value === "dark" ? "vs-dark" : "vs",
17
18
  minimap: { enabled: false },
18
19
  automaticLayout: true,
19
20
  readOnly: true,
20
21
  fontSize: 14,
21
22
  padding: { top: 16 },
23
+ scrollBeyondLastLine: false,
24
+ wordWrap: "on",
22
25
  });
23
26
  });
24
27
 
@@ -36,6 +39,9 @@ onBeforeUnmount(() => {
36
39
  editorInstance.value.dispose();
37
40
  }
38
41
  });
42
+ watch(() => colorMode.value, (mode) => {
43
+ editorInstance.value?.updateOptions({ theme: mode === "dark" ? "vs-dark" : "vs" });
44
+ });
39
45
  </script>
40
46
 
41
47
  <template>
@@ -0,0 +1,16 @@
1
+ <script setup lang="ts">
2
+ const source = defineModel<string>({ required: true });
3
+ </script>
4
+
5
+ <template>
6
+ <section class="studio-card studio-schema" aria-labelledby="schema-title">
7
+ <div class="studio-card-head">
8
+ <div class="studio-card-heading">
9
+ <div><p class="studio-eyebrow">TypeScript</p><h2 id="schema-title">Schema</h2></div>
10
+ </div>
11
+ <slot name="actions" />
12
+ </div>
13
+ <div class="studio-editor-surface"><ClientOnly><StudioEditor v-model="source" label="Zod schema TypeScript editor" /></ClientOnly></div>
14
+ <div class="studio-card-foot">Use <code>z</code> and zod-mongoose helpers directly. You can define multiple schemas.</div>
15
+ </section>
16
+ </template>
@@ -0,0 +1,47 @@
1
+ <script setup lang="ts">
2
+ import type { ValidationResult } from "../composables/useStudioRunner";
3
+
4
+ defineProps<{ result: ValidationResult; stale: boolean }>();
5
+ </script>
6
+
7
+ <template>
8
+ <section class="studio-card studio-validation" aria-labelledby="validation-title">
9
+ <div class="studio-card-head">
10
+ <div class="studio-card-heading">
11
+ <div>
12
+ <p class="studio-eyebrow">Zod</p>
13
+ <h2 id="validation-title">Validation</h2>
14
+ </div>
15
+ </div>
16
+ <div class="studio-card-actions">
17
+ <span v-if="stale" class="studio-status studio-status-stale">Edited</span>
18
+ <span v-else-if="result.status === 'valid'" class="studio-status studio-status-valid">Valid</span>
19
+ <span v-else-if="result.status === 'invalid' || result.status === 'json-error' || result.status === 'parse-error'" class="studio-status studio-status-invalid">Invalid</span>
20
+ <span v-else class="studio-status">Ready</span>
21
+ <slot name="actions" />
22
+ </div>
23
+ </div>
24
+
25
+ <div class="studio-validation-body">
26
+ <div v-if="result.status === 'valid'" class="studio-result-intro">
27
+ <span class="studio-result-icon studio-result-icon-valid">✓</span>
28
+ <div><strong>Parsed successfully</strong><p>Defaults, transforms and stripped fields appear below.</p></div>
29
+ </div>
30
+ <div v-else-if="result.status === 'invalid'" class="studio-result-intro">
31
+ <span class="studio-result-icon studio-result-icon-invalid">!</span>
32
+ <div><strong>{{ result.issues?.length }} validation issue{{ result.issues?.length === 1 ? '' : 's' }}</strong><p>Update the JSON input and run again.</p></div>
33
+ </div>
34
+ <div v-else class="studio-result-intro">
35
+ <span class="studio-result-icon">{ }</span>
36
+ <div><strong>{{ result.status === 'json-error' ? 'JSON could not be read' : result.status === 'parse-error' ? 'Validation could not run' : 'Ready to validate' }}</strong><p>{{ result.message }}</p></div>
37
+ </div>
38
+
39
+ <pre v-if="result.status === 'valid'" class="studio-parsed" aria-label="Parsed data">{{ result.parsed }}</pre>
40
+ <ul v-else-if="result.status === 'invalid'" class="studio-issues">
41
+ <li v-for="(issue, index) in result.issues" :key="`${issue.path}-${index}`">
42
+ <code>{{ issue.path }}</code><span>{{ issue.message }}</span>
43
+ </li>
44
+ </ul>
45
+ </div>
46
+ </section>
47
+ </template>