@mongoosejs/studio 0.3.1 → 0.3.2

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/DEVGUIDE.md ADDED
@@ -0,0 +1,8 @@
1
+ ## Running Locally
2
+
3
+ To run this repo locally:
4
+
5
+ 1. Install dependencies with `npm install`
6
+ 2. Seed the database with `npm run seed`. Make sure you have a MongoDB instance running on `localhost:27017`.
7
+ 3. Run backend on port 7777. Make sure to seed backend as well.
8
+ 4. Start the server with `npm start`
@@ -23,16 +23,17 @@ const GetDashboardParams = new Archetype({
23
23
  }
24
24
  }).compile('GetDashboardParams');
25
25
 
26
- module.exports = ({ db }) => async function getDashboard(params) {
26
+ module.exports = ({ db, options }) => async function getDashboard(params) {
27
27
  const { $workspaceId, authorization, dashboardId, evaluate, roles } = new GetDashboardParams(params);
28
28
  const Dashboard = db.model('__Studio_Dashboard');
29
+ const mothershipUrl = options?._mothershipUrl ?? 'https://mongoose-js.netlify.app/.netlify/functions';
29
30
 
30
31
  await authorize('Dashboard.getDashboard', roles);
31
32
 
32
33
  const dashboard = await Dashboard.findOne({ _id: dashboardId });
33
34
  if (evaluate) {
34
35
  let result = null;
35
- const startExec = startDashboardEvaluate(dashboardId, $workspaceId, authorization);
36
+ const startExec = startDashboardEvaluate(dashboardId, $workspaceId, authorization, mothershipUrl);
36
37
  // Avoid unhandled promise rejection since we handle the promise later.
37
38
  startExec.catch(() => {});
38
39
  try {
@@ -47,7 +48,8 @@ module.exports = ({ db }) => async function getDashboard(params) {
47
48
  $workspaceId,
48
49
  authorization,
49
50
  null,
50
- { message: error.message }
51
+ { message: error.message },
52
+ mothershipUrl
51
53
  );
52
54
  });
53
55
  return { dashboard, dashboardResult, error: { message: error.message } };
@@ -62,7 +64,9 @@ module.exports = ({ db }) => async function getDashboard(params) {
62
64
  dashboardResult._id,
63
65
  $workspaceId,
64
66
  authorization,
65
- result
67
+ result,
68
+ undefined,
69
+ mothershipUrl
66
70
  );
67
71
  });
68
72
 
@@ -71,12 +75,12 @@ module.exports = ({ db }) => async function getDashboard(params) {
71
75
  return { dashboard, error: { message: error.message } };
72
76
  }
73
77
  } else {
74
- const { dashboardResults } = await getDashboardResults(dashboardId, $workspaceId, authorization);
78
+ const { dashboardResults } = await getDashboardResults(dashboardId, $workspaceId, authorization, mothershipUrl);
75
79
  return { dashboard, dashboardResults };
76
80
  }
77
81
  };
78
82
 
79
- async function completeDashboardEvaluate(dashboardResultId, workspaceId, authorization, result, error) {
83
+ async function completeDashboardEvaluate(dashboardResultId, workspaceId, authorization, result, error, mothershipUrl) {
80
84
  if (!workspaceId) {
81
85
  return {};
82
86
  }
@@ -84,7 +88,7 @@ async function completeDashboardEvaluate(dashboardResultId, workspaceId, authori
84
88
  if (authorization) {
85
89
  headers.Authorization = authorization;
86
90
  }
87
- const response = await fetch('https://mongoose-js.netlify.app/.netlify/functions/completeDashboardEvaluate', {
91
+ const response = await fetch(`${mothershipUrl}/completeDashboardEvaluate`, {
88
92
  method: 'POST',
89
93
  headers,
90
94
  body: JSON.stringify({
@@ -106,7 +110,7 @@ async function completeDashboardEvaluate(dashboardResultId, workspaceId, authori
106
110
  return await response.json();
107
111
  }
108
112
 
109
- async function startDashboardEvaluate(dashboardId, workspaceId, authorization) {
113
+ async function startDashboardEvaluate(dashboardId, workspaceId, authorization, _mothershipUrl) {
110
114
  if (!workspaceId) {
111
115
  return {};
112
116
  }
@@ -114,7 +118,7 @@ async function startDashboardEvaluate(dashboardId, workspaceId, authorization) {
114
118
  if (authorization) {
115
119
  headers.Authorization = authorization;
116
120
  }
117
- const response = await fetch('https://mongoose-js.netlify.app/.netlify/functions/startDashboardEvaluate', {
121
+ const response = await fetch(`${_mothershipUrl}/startDashboardEvaluate`, {
118
122
  method: 'POST',
119
123
  headers,
120
124
  body: JSON.stringify({
@@ -134,7 +138,7 @@ async function startDashboardEvaluate(dashboardId, workspaceId, authorization) {
134
138
  return await response.json();
135
139
  }
136
140
 
137
- async function getDashboardResults(dashboardId, workspaceId, authorization) {
141
+ async function getDashboardResults(dashboardId, workspaceId, authorization, mothershipUrl) {
138
142
  if (!workspaceId) {
139
143
  return {};
140
144
  }
@@ -142,7 +146,7 @@ async function getDashboardResults(dashboardId, workspaceId, authorization) {
142
146
  if (authorization) {
143
147
  headers.Authorization = authorization;
144
148
  }
145
- const response = await fetch('https://mongoose-js.netlify.app/.netlify/functions/getDashboardResults', {
149
+ const response = await fetch(`${mothershipUrl}/getDashboardResults`, {
146
150
  method: 'POST',
147
151
  headers,
148
152
  body: JSON.stringify({
@@ -1822,6 +1822,8 @@ module.exports = {
1822
1822
  }
1823
1823
  },
1824
1824
  async mounted() {
1825
+ window.pageState = this;
1826
+
1825
1827
  this.chatThreadId = this.threadId;
1826
1828
  const { chatThreads } = await api.ChatThread.listChatThreads();
1827
1829
  this.chatThreads = chatThreads;
@@ -2730,7 +2732,9 @@ module.exports = {
2730
2732
  return this.dashboardResults.length > 0 ? this.dashboardResults[0] : null;
2731
2733
  }
2732
2734
  },
2733
- mounted: async function() {
2735
+ mounted: async function () {
2736
+ window.pageState = this;
2737
+
2734
2738
  document.addEventListener('click', this.handleDocumentClick);
2735
2739
  this.showEditor = this.$route.query.edit;
2736
2740
  await this.loadInitial();
@@ -7120,6 +7124,7 @@ module.exports = app => app.component('models', {
7120
7124
  this.destroyMap();
7121
7125
  },
7122
7126
  async mounted() {
7127
+ window.pageState = this;
7123
7128
  this.onScroll = () => this.checkIfScrolledToBottom();
7124
7129
  document.addEventListener('scroll', this.onScroll, true);
7125
7130
  this.onPopState = () => this.initSearchFromUrl();
@@ -8294,7 +8299,8 @@ module.exports = app => app.component('navbar', {
8294
8299
  showFlyout: false,
8295
8300
  darkMode: typeof localStorage !== 'undefined' && localStorage.getItem('studio-theme') === 'dark'
8296
8301
  }),
8297
- mounted: function() {
8302
+ mounted: function () {
8303
+ window.navbar = this;
8298
8304
  const mobileMenuMask = document.querySelector('#mobile-menu-mask');
8299
8305
  const mobileMenu = document.querySelector('#mobile-menu');
8300
8306
  const openBtn = document.querySelector('#open-mobile-menu');
@@ -8348,7 +8354,7 @@ module.exports = app => app.component('navbar', {
8348
8354
  } else {
8349
8355
  return 'https://www.npmjs.com/package/@mongoosejs/task';
8350
8356
  }
8351
-
8357
+
8352
8358
  }
8353
8359
  },
8354
8360
  methods: {
@@ -61661,7 +61667,7 @@ var src_default = VueToastificationPlugin;
61661
61667
  (module) {
61662
61668
 
61663
61669
  "use strict";
61664
- module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.3.1","description":"A Mongoose-native MongoDB UI with schema-aware autocomplete, AI-assisted queries, and dashboards that understand your models - not just your data.","homepage":"https://mongoosestudio.app/","repository":{"type":"git","url":"https://github.com/mongoosejs/studio"},"license":"Apache-2.0","dependencies":{"@ai-sdk/anthropic":"2.x","@ai-sdk/google":"2.x","@ai-sdk/openai":"2.x","ace-builds":"^1.43.6","ai":"5.x","archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"^0.2.0","marked":"15.0.12","node-inspect-extracted":"3.x","regexp.escape":"^2.0.1","tailwindcss":"3.4.0","vue":"3.x","vue-toastification":"^2.0.0-rc.5","webpack":"5.x","xss":"^1.0.15"},"peerDependencies":{"mongoose":"7.x || 8.x || ^9.0.0"},"optionalPeerDependencies":{"@mongoosejs/task":"0.5.x || 0.6.x"},"devDependencies":{"@masteringjs/eslint-config":"0.1.1","axios":"1.2.2","dedent":"^1.6.0","eslint":"9.30.0","express":"4.x","mocha":"10.2.0","mongodb-memory-server":"^11.0.1","mongoose":"9.x","sinon":"^21.0.1"},"scripts":{"lint":"eslint .","tailwind":"tailwindcss -o ./frontend/public/tw.css","tailwind:watch":"tailwindcss -o ./frontend/public/tw.css --watch","test":"mocha test/*.test.js","test:frontend":"mocha test/frontend/*.test.js"}}');
61670
+ module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.3.2","description":"A Mongoose-native MongoDB UI with schema-aware autocomplete, AI-assisted queries, and dashboards that understand your models - not just your data.","homepage":"https://mongoosestudio.app/","repository":{"type":"git","url":"https://github.com/mongoosejs/studio"},"license":"Apache-2.0","dependencies":{"@ai-sdk/anthropic":"2.x","@ai-sdk/google":"2.x","@ai-sdk/openai":"2.x","ace-builds":"^1.43.6","ai":"5.x","archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"^0.2.0","marked":"15.0.12","node-inspect-extracted":"3.x","regexp.escape":"^2.0.1","tailwindcss":"3.4.0","vue":"3.x","vue-toastification":"^2.0.0-rc.5","webpack":"5.x","xss":"^1.0.15"},"peerDependencies":{"mongoose":"7.x || 8.x || ^9.0.0"},"optionalPeerDependencies":{"@mongoosejs/task":"0.5.x || 0.6.x"},"devDependencies":{"@masteringjs/eslint-config":"0.1.1","axios":"1.2.2","dedent":"^1.6.0","eslint":"9.30.0","express":"4.x","mocha":"10.2.0","mongodb-memory-server":"^11.0.1","mongoose":"9.x","sinon":"^21.0.1"},"scripts":{"lint":"eslint .","seed":"node seed/index.js","start":"node ./local.js","tailwind":"tailwindcss -o ./frontend/public/tw.css","tailwind:watch":"tailwindcss -o ./frontend/public/tw.css --watch","test":"mocha test/*.test.js","test:frontend":"mocha test/frontend/*.test.js"}}');
61665
61671
 
61666
61672
  /***/ }
61667
61673
 
@@ -192,6 +192,8 @@ module.exports = {
192
192
  }
193
193
  },
194
194
  async mounted() {
195
+ window.pageState = this;
196
+
195
197
  this.chatThreadId = this.threadId;
196
198
  const { chatThreads } = await api.ChatThread.listChatThreads();
197
199
  this.chatThreads = chatThreads;
@@ -157,7 +157,9 @@ module.exports = {
157
157
  return this.dashboardResults.length > 0 ? this.dashboardResults[0] : null;
158
158
  }
159
159
  },
160
- mounted: async function() {
160
+ mounted: async function () {
161
+ window.pageState = this;
162
+
161
163
  document.addEventListener('click', this.handleDocumentClick);
162
164
  this.showEditor = this.$route.query.edit;
163
165
  await this.loadInitial();
@@ -79,6 +79,7 @@ module.exports = app => app.component('models', {
79
79
  this.destroyMap();
80
80
  },
81
81
  async mounted() {
82
+ window.pageState = this;
82
83
  this.onScroll = () => this.checkIfScrolledToBottom();
83
84
  document.addEventListener('scroll', this.onScroll, true);
84
85
  this.onPopState = () => this.initSearchFromUrl();
@@ -17,7 +17,8 @@ module.exports = app => app.component('navbar', {
17
17
  showFlyout: false,
18
18
  darkMode: typeof localStorage !== 'undefined' && localStorage.getItem('studio-theme') === 'dark'
19
19
  }),
20
- mounted: function() {
20
+ mounted: function () {
21
+ window.navbar = this;
21
22
  const mobileMenuMask = document.querySelector('#mobile-menu-mask');
22
23
  const mobileMenu = document.querySelector('#mobile-menu');
23
24
  const openBtn = document.querySelector('#open-mobile-menu');
@@ -71,7 +72,7 @@ module.exports = app => app.component('navbar', {
71
72
  } else {
72
73
  return 'https://www.npmjs.com/package/@mongoosejs/task';
73
74
  }
74
-
75
+
75
76
  }
76
77
  },
77
78
  methods: {
package/local.js ADDED
@@ -0,0 +1,38 @@
1
+ 'use strict';
2
+
3
+ const mongoose = require('mongoose');
4
+ const connect = require('./seed/connect');
5
+
6
+ const express = require('express');
7
+ const studio = require('./express');
8
+
9
+ const getModelDescriptions = require('./backend/helpers/getModelDescriptions');
10
+
11
+ Error.stackTraceLimit = 25;
12
+
13
+ run().catch(err => {
14
+ console.error(err);
15
+ process.exit(-1);
16
+ });
17
+
18
+ async function run() {
19
+ const app = express();
20
+ await connect();
21
+
22
+ console.log(getModelDescriptions(mongoose.connection));
23
+
24
+ app.use('/studio', await studio(
25
+ null,
26
+ null,
27
+ {
28
+ __build: true,
29
+ __watch: process.env.WATCH,
30
+ _mothershipUrl: 'http://localhost:7777/.netlify/functions',
31
+ apiKey: 'TACO',
32
+ openAIAPIKey: process.env.OPENAI_API_KEY
33
+ })
34
+ );
35
+
36
+ await app.listen(3333);
37
+ console.log('Listening on port 3333');
38
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mongoosejs/studio",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "A Mongoose-native MongoDB UI with schema-aware autocomplete, AI-assisted queries, and dashboards that understand your models - not just your data.",
5
5
  "homepage": "https://mongoosestudio.app/",
6
6
  "repository": {
@@ -46,6 +46,8 @@
46
46
  },
47
47
  "scripts": {
48
48
  "lint": "eslint .",
49
+ "seed": "node seed/index.js",
50
+ "start": "node ./local.js",
49
51
  "tailwind": "tailwindcss -o ./frontend/public/tw.css",
50
52
  "tailwind:watch": "tailwindcss -o ./frontend/public/tw.css --watch",
51
53
  "test": "mocha test/*.test.js",
@@ -0,0 +1,23 @@
1
+ 'use strict';
2
+
3
+ const mongoose = require('mongoose');
4
+
5
+ const uri = 'mongodb://127.0.0.1:27017/mongoose_studio_test';
6
+
7
+ module.exports = async function connect() {
8
+ await mongoose.connect(uri);
9
+
10
+ mongoose.model('User', new mongoose.Schema({
11
+ name: String,
12
+ email: String,
13
+ role: String,
14
+ plan: String,
15
+ status: String,
16
+ isDeleted: Boolean,
17
+ lastLoginAt: Date,
18
+ createdAt: Date,
19
+ updatedAt: Date
20
+ }, { collection: 'User' }));
21
+
22
+ return mongoose.connection;
23
+ };
package/seed/index.js ADDED
@@ -0,0 +1,101 @@
1
+ 'use strict';
2
+
3
+ const connect = require('./connect');
4
+ const mongoose = require('mongoose');
5
+
6
+ if (require.main === module) {
7
+ run().catch(err => {
8
+ console.error(err);
9
+ process.exit(1);
10
+ });
11
+ }
12
+
13
+ async function run() {
14
+ await connect();
15
+ const { User } = mongoose.models;
16
+ const dashboardCollection = mongoose.connection.collection('studio__dashboards');
17
+ const now = new Date();
18
+
19
+ const users = [
20
+ {
21
+ name: 'Ada Lovelace',
22
+ email: 'ada@example.com',
23
+ role: 'admin',
24
+ plan: 'enterprise',
25
+ status: 'active',
26
+ isDeleted: false,
27
+ lastLoginAt: new Date('2026-03-11T14:21:00.000Z'),
28
+ createdAt: new Date('2025-10-03T09:15:00.000Z'),
29
+ updatedAt: now
30
+ },
31
+ {
32
+ name: 'Grace Hopper',
33
+ email: 'grace@example.com',
34
+ role: 'analyst',
35
+ plan: 'pro',
36
+ status: 'active',
37
+ isDeleted: false,
38
+ lastLoginAt: new Date('2026-03-10T17:42:00.000Z'),
39
+ createdAt: new Date('2025-11-16T13:05:00.000Z'),
40
+ updatedAt: now
41
+ },
42
+ {
43
+ name: 'Linus Torvalds',
44
+ email: 'linus@example.com',
45
+ role: 'editor',
46
+ plan: 'starter',
47
+ status: 'invited',
48
+ isDeleted: false,
49
+ lastLoginAt: null,
50
+ createdAt: new Date('2026-01-09T11:30:00.000Z'),
51
+ updatedAt: now
52
+ },
53
+ {
54
+ name: 'Margaret Hamilton',
55
+ email: 'margaret@example.com',
56
+ role: 'viewer',
57
+ plan: 'pro',
58
+ status: 'inactive',
59
+ isDeleted: false,
60
+ lastLoginAt: new Date('2026-02-22T08:00:00.000Z'),
61
+ createdAt: new Date('2025-12-01T16:20:00.000Z'),
62
+ updatedAt: now
63
+ }
64
+ ];
65
+
66
+ const dashboard = {
67
+ title: 'User directory table',
68
+ description: 'Sample dashboard for testing the dashboard-table component.',
69
+ code: `const users = await db.model('User')
70
+ .find({ isDeleted: false })
71
+ .sort({ createdAt: -1 })
72
+ .lean();
73
+
74
+ return {
75
+ $table: {
76
+ columns: ['Name', 'Email', 'Role', 'Plan', 'Status', 'Last Login'],
77
+ rows: users.map(user => [
78
+ user.name,
79
+ user.email,
80
+ user.role,
81
+ user.plan,
82
+ user.status,
83
+ user.lastLoginAt ? new Date(user.lastLoginAt).toISOString().slice(0, 10) : 'Never'
84
+ ])
85
+ }
86
+ };`
87
+ };
88
+
89
+ await User.deleteMany({ email: { $in: users.map(user => user.email) } });
90
+ await dashboardCollection.deleteMany({ title: dashboard.title });
91
+
92
+ const insertedUsers = await User.insertMany(users);
93
+ const insertedDashboard = await dashboardCollection.insertOne(dashboard);
94
+
95
+ console.log(`Inserted ${insertedUsers.length} users`);
96
+ console.log(`Inserted dashboard ${insertedDashboard.insertedId.toString()}`);
97
+
98
+ await mongoose.disconnect();
99
+ }
100
+
101
+ module.exports = run;