@mongoosejs/studio 0.2.9 → 0.2.11
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/backend/actions/Model/executeDocumentScript.js +61 -0
- package/backend/actions/Model/index.js +2 -0
- package/backend/actions/Model/listModels.js +36 -1
- package/backend/actions/Model/streamDocumentChanges.js +123 -0
- package/backend/actions/Task/cancelTask.js +24 -0
- package/backend/actions/Task/createTask.js +33 -0
- package/backend/actions/Task/getTasks.js +62 -0
- package/backend/actions/Task/index.js +7 -0
- package/backend/actions/Task/rescheduleTask.js +39 -0
- package/backend/actions/Task/runTask.js +25 -0
- package/backend/actions/index.js +1 -0
- package/backend/authorize.js +2 -0
- package/backend/index.js +7 -1
- package/eslint.config.js +4 -1
- package/express.js +4 -2
- package/frontend/public/app.js +14590 -13420
- package/frontend/public/tw.css +357 -4
- package/frontend/src/api.js +100 -0
- package/frontend/src/dashboard-result/dashboard-document/dashboard-document.html +4 -5
- package/frontend/src/dashboard-result/dashboard-document/dashboard-document.js +13 -14
- package/frontend/src/document/document.html +80 -0
- package/frontend/src/document/document.js +206 -19
- package/frontend/src/document/execute-script/execute-script.css +35 -0
- package/frontend/src/document/execute-script/execute-script.html +67 -0
- package/frontend/src/document/execute-script/execute-script.js +142 -0
- package/frontend/src/index.js +48 -4
- package/frontend/src/navbar/navbar.html +15 -2
- package/frontend/src/navbar/navbar.js +11 -0
- package/frontend/src/routes.js +13 -5
- package/frontend/src/tasks/task-details/task-details.html +284 -0
- package/frontend/src/tasks/task-details/task-details.js +182 -0
- package/frontend/src/tasks/tasks.css +0 -0
- package/frontend/src/tasks/tasks.html +220 -0
- package/frontend/src/tasks/tasks.js +372 -0
- package/package.json +4 -1
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Archetype = require('archetype');
|
|
4
|
+
const authorize = require('../../authorize');
|
|
5
|
+
const mongoose = require('mongoose');
|
|
6
|
+
const util = require('util');
|
|
7
|
+
const vm = require('vm');
|
|
8
|
+
|
|
9
|
+
const ExecuteDocumentScriptParams = new Archetype({
|
|
10
|
+
model: {
|
|
11
|
+
$type: 'string',
|
|
12
|
+
$required: true
|
|
13
|
+
},
|
|
14
|
+
documentId: {
|
|
15
|
+
$type: 'string',
|
|
16
|
+
$required: true
|
|
17
|
+
},
|
|
18
|
+
script: {
|
|
19
|
+
$type: 'string',
|
|
20
|
+
$required: true
|
|
21
|
+
},
|
|
22
|
+
roles: {
|
|
23
|
+
$type: ['string']
|
|
24
|
+
}
|
|
25
|
+
}).compile('ExecuteDocumentScriptParams');
|
|
26
|
+
|
|
27
|
+
module.exports = ({ db }) => async function executeDocumentScript(params) {
|
|
28
|
+
const { model, documentId, script, roles } = new ExecuteDocumentScriptParams(params);
|
|
29
|
+
|
|
30
|
+
await authorize('Model.executeDocumentScript', roles);
|
|
31
|
+
|
|
32
|
+
const Model = db.models[model];
|
|
33
|
+
if (Model == null) {
|
|
34
|
+
throw new Error(`Model ${model} not found`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const doc = await Model.findById(documentId).setOptions({ sanitizeFilter: true }).orFail();
|
|
38
|
+
|
|
39
|
+
const logs = [];
|
|
40
|
+
if (!db.Types) {
|
|
41
|
+
db.Types = mongoose.Types;
|
|
42
|
+
}
|
|
43
|
+
const sandbox = { db, mongoose, doc, console: {}, ObjectId: mongoose.Types.ObjectId };
|
|
44
|
+
|
|
45
|
+
sandbox.console.log = function() {
|
|
46
|
+
const args = Array.from(arguments);
|
|
47
|
+
logs.push(args.map(arg => typeof arg === 'object' ? util.inspect(arg) : arg).join(' '));
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const context = vm.createContext(sandbox);
|
|
51
|
+
const result = await vm.runInContext(wrappedScript(script), context);
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
result,
|
|
55
|
+
logs: logs.join('\n')
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const wrappedScript = script => `(async () => {
|
|
60
|
+
${script}
|
|
61
|
+
})()`;
|
|
@@ -6,6 +6,7 @@ exports.createDocument = require('./createDocument');
|
|
|
6
6
|
exports.deleteDocument = require('./deleteDocument');
|
|
7
7
|
exports.deleteDocuments = require('./deleteDocuments');
|
|
8
8
|
exports.dropIndex = require('./dropIndex');
|
|
9
|
+
exports.executeDocumentScript = require('./executeDocumentScript');
|
|
9
10
|
exports.exportQueryResults = require('./exportQueryResults');
|
|
10
11
|
exports.getDocument = require('./getDocument');
|
|
11
12
|
exports.getDocuments = require('./getDocuments');
|
|
@@ -13,6 +14,7 @@ exports.getDocumentsStream = require('./getDocumentsStream');
|
|
|
13
14
|
exports.getCollectionInfo = require('./getCollectionInfo');
|
|
14
15
|
exports.getIndexes = require('./getIndexes');
|
|
15
16
|
exports.listModels = require('./listModels');
|
|
17
|
+
exports.streamDocumentChanges = require('./streamDocumentChanges');
|
|
16
18
|
exports.streamChatMessage = require('./streamChatMessage');
|
|
17
19
|
exports.updateDocument = require('./updateDocument');
|
|
18
20
|
exports.updateDocuments = require('./updateDocuments');
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const authorize = require('../../authorize');
|
|
5
|
+
const getRefFromSchemaType = require('../../helpers/getRefFromSchemaType');
|
|
6
|
+
const removeSpecifiedPaths = require('../../helpers/removeSpecifiedPaths');
|
|
5
7
|
|
|
6
8
|
const ListModelsParams = new Archetype({
|
|
7
9
|
roles: {
|
|
@@ -15,8 +17,41 @@ module.exports = ({ db }) => async function listModels(params) {
|
|
|
15
17
|
|
|
16
18
|
const readyState = db.connection?.readyState ?? db.readyState;
|
|
17
19
|
|
|
20
|
+
const models = Object.keys(db.models).filter(key => !key.startsWith('__Studio_')).sort();
|
|
21
|
+
|
|
22
|
+
const modelSchemaPaths = {};
|
|
23
|
+
for (const modelName of models) {
|
|
24
|
+
const Model = db.models[modelName];
|
|
25
|
+
const schemaPaths = {};
|
|
26
|
+
modelSchemaPaths[modelName] = schemaPaths;
|
|
27
|
+
for (const path of Object.keys(Model.schema.paths)) {
|
|
28
|
+
const schemaType = Model.schema.paths[path];
|
|
29
|
+
schemaPaths[path] = {
|
|
30
|
+
instance: schemaType.instance,
|
|
31
|
+
path,
|
|
32
|
+
ref: getRefFromSchemaType(schemaType),
|
|
33
|
+
required: schemaType.options?.required,
|
|
34
|
+
enum: schemaType.options?.enum
|
|
35
|
+
};
|
|
36
|
+
if (schemaType.schema) {
|
|
37
|
+
schemaPaths[path].schema = {};
|
|
38
|
+
for (const subpath of Object.keys(schemaType.schema.paths)) {
|
|
39
|
+
schemaPaths[path].schema[subpath] = {
|
|
40
|
+
instance: schemaType.schema.paths[subpath].instance,
|
|
41
|
+
path: subpath,
|
|
42
|
+
ref: getRefFromSchemaType(schemaType.schema.paths[subpath]),
|
|
43
|
+
required: schemaType.schema.paths[subpath].options?.required,
|
|
44
|
+
enum: schemaType.schema.paths[subpath].options?.enum
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
removeSpecifiedPaths(schemaPaths, '.$*');
|
|
50
|
+
}
|
|
51
|
+
|
|
18
52
|
return {
|
|
19
|
-
models
|
|
53
|
+
models,
|
|
54
|
+
modelSchemaPaths,
|
|
20
55
|
readyState
|
|
21
56
|
};
|
|
22
57
|
};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Archetype = require('archetype');
|
|
4
|
+
const authorize = require('../../authorize');
|
|
5
|
+
|
|
6
|
+
const StreamDocumentChangesParams = new Archetype({
|
|
7
|
+
model: {
|
|
8
|
+
$type: 'string',
|
|
9
|
+
$required: true
|
|
10
|
+
},
|
|
11
|
+
documentId: {
|
|
12
|
+
$type: 'string',
|
|
13
|
+
$required: true
|
|
14
|
+
},
|
|
15
|
+
roles: {
|
|
16
|
+
$type: ['string']
|
|
17
|
+
}
|
|
18
|
+
}).compile('StreamDocumentChangesParams');
|
|
19
|
+
|
|
20
|
+
module.exports = ({ db, changeStream }) => async function* streamDocumentChanges(params) {
|
|
21
|
+
const { model, documentId, roles } = new StreamDocumentChangesParams(params);
|
|
22
|
+
|
|
23
|
+
await authorize('Model.streamDocumentChanges', roles);
|
|
24
|
+
|
|
25
|
+
const Model = db.models[model];
|
|
26
|
+
if (Model == null) {
|
|
27
|
+
throw new Error(`Model ${model} not found`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!changeStream) {
|
|
31
|
+
throw new Error('Change streams are not enabled');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const collectionName = Model.collection.name;
|
|
35
|
+
const targetId = String(documentId);
|
|
36
|
+
|
|
37
|
+
const queue = [];
|
|
38
|
+
let resolveQueue = null;
|
|
39
|
+
let streamError = null;
|
|
40
|
+
let streamEnded = false;
|
|
41
|
+
|
|
42
|
+
function enqueue(payload) {
|
|
43
|
+
queue.push(payload);
|
|
44
|
+
if (resolveQueue) {
|
|
45
|
+
const resolve = resolveQueue;
|
|
46
|
+
resolveQueue = null;
|
|
47
|
+
resolve();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function handleChange(change) {
|
|
52
|
+
if (!change || change.ns?.coll !== collectionName) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (!change.documentKey || change.documentKey._id == null) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (String(change.documentKey._id) !== targetId) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
enqueue({
|
|
63
|
+
type: 'change',
|
|
64
|
+
operationType: change.operationType,
|
|
65
|
+
documentKey: change.documentKey,
|
|
66
|
+
ns: change.ns,
|
|
67
|
+
updateDescription: change.updateDescription,
|
|
68
|
+
clusterTime: change.clusterTime
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function handleError(err) {
|
|
73
|
+
streamError = err || new Error('Change stream error');
|
|
74
|
+
enqueue({ type: 'error', message: streamError.message });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function handleEnd() {
|
|
78
|
+
streamEnded = true;
|
|
79
|
+
enqueue({ type: 'end' });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
changeStream.on('change', handleChange);
|
|
83
|
+
changeStream.on('error', handleError);
|
|
84
|
+
changeStream.on('end', handleEnd);
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
while (true) {
|
|
88
|
+
if (streamError) {
|
|
89
|
+
throw streamError;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (queue.length === 0) {
|
|
93
|
+
await new Promise(resolve => {
|
|
94
|
+
resolveQueue = resolve;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (streamError) {
|
|
99
|
+
throw streamError;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
while (queue.length > 0) {
|
|
103
|
+
const payload = queue.shift();
|
|
104
|
+
if (payload?.type === 'end') {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
yield payload;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (streamEnded) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
} finally {
|
|
115
|
+
changeStream.off('change', handleChange);
|
|
116
|
+
changeStream.off('error', handleError);
|
|
117
|
+
changeStream.off('end', handleEnd);
|
|
118
|
+
if (resolveQueue) {
|
|
119
|
+
resolveQueue();
|
|
120
|
+
resolveQueue = null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Archetype = require('archetype');
|
|
4
|
+
const mongoose = require('mongoose');
|
|
5
|
+
|
|
6
|
+
const CancelTaskParams = new Archetype({
|
|
7
|
+
taskId: {
|
|
8
|
+
$type: mongoose.Types.ObjectId,
|
|
9
|
+
$required: true
|
|
10
|
+
}
|
|
11
|
+
}).compile('CancelTaskParams');
|
|
12
|
+
|
|
13
|
+
module.exports = ({ db }) => async function cancelTask(params) {
|
|
14
|
+
params = new CancelTaskParams(params);
|
|
15
|
+
const { taskId } = params;
|
|
16
|
+
const { Task } = db.models;
|
|
17
|
+
|
|
18
|
+
const task = await Task.findOne({ _id: taskId }).orFail();
|
|
19
|
+
|
|
20
|
+
const cancelledTask = await Task.cancelTask({ _id: taskId });
|
|
21
|
+
return {
|
|
22
|
+
task: cancelledTask
|
|
23
|
+
};
|
|
24
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Archetype = require('archetype');
|
|
4
|
+
|
|
5
|
+
const CreateTaskParams = new Archetype({
|
|
6
|
+
name: {
|
|
7
|
+
$type: 'string',
|
|
8
|
+
$required: true
|
|
9
|
+
},
|
|
10
|
+
scheduledAt: {
|
|
11
|
+
$type: Date,
|
|
12
|
+
$required: true
|
|
13
|
+
},
|
|
14
|
+
repeatAfterMS: {
|
|
15
|
+
$type: 'number'
|
|
16
|
+
},
|
|
17
|
+
payload: {
|
|
18
|
+
$type: Archetype.Any
|
|
19
|
+
}
|
|
20
|
+
}).compile('CreateTaskParams');
|
|
21
|
+
|
|
22
|
+
module.exports = ({ db }) => async function createTask(params) {
|
|
23
|
+
params = new CreateTaskParams(params);
|
|
24
|
+
|
|
25
|
+
const { name, scheduledAt, payload, repeatAfterMS } = params;
|
|
26
|
+
const { Task } = db.models;
|
|
27
|
+
|
|
28
|
+
const task = await Task.schedule(name, scheduledAt, payload, repeatAfterMS);
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
task
|
|
32
|
+
};
|
|
33
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Archetype = require('archetype');
|
|
4
|
+
|
|
5
|
+
const GetTasksParams = new Archetype({
|
|
6
|
+
start: {
|
|
7
|
+
$type: Date
|
|
8
|
+
},
|
|
9
|
+
end: {
|
|
10
|
+
$type: Date
|
|
11
|
+
},
|
|
12
|
+
status: {
|
|
13
|
+
$type: 'string'
|
|
14
|
+
},
|
|
15
|
+
name: {
|
|
16
|
+
$type: 'string'
|
|
17
|
+
}
|
|
18
|
+
}).compile('GetTasksParams');
|
|
19
|
+
|
|
20
|
+
module.exports = ({ db }) => async function getTasks(params) {
|
|
21
|
+
params = new GetTasksParams(params);
|
|
22
|
+
const { start, end, status, name } = params;
|
|
23
|
+
const { Task } = db.models;
|
|
24
|
+
|
|
25
|
+
const filter = {};
|
|
26
|
+
|
|
27
|
+
if (start && end) {
|
|
28
|
+
filter.scheduledAt = { $gte: start, $lt: end };
|
|
29
|
+
} else if (start) {
|
|
30
|
+
filter.scheduledAt = { $gte: start };
|
|
31
|
+
}
|
|
32
|
+
if (status) {
|
|
33
|
+
filter.status = status;
|
|
34
|
+
}
|
|
35
|
+
if (name) {
|
|
36
|
+
filter.name = { $regex: name, $options: 'i' };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const tasks = await Task.find(filter);
|
|
40
|
+
|
|
41
|
+
// Define all possible statuses
|
|
42
|
+
const allStatuses = ['pending', 'in_progress', 'succeeded', 'failed', 'cancelled', 'unknown'];
|
|
43
|
+
|
|
44
|
+
// Initialize groupedTasks with all statuses
|
|
45
|
+
const groupedTasks = allStatuses.reduce((groups, status) => {
|
|
46
|
+
groups[status] = [];
|
|
47
|
+
return groups;
|
|
48
|
+
}, {});
|
|
49
|
+
|
|
50
|
+
// Group tasks by status
|
|
51
|
+
tasks.forEach(task => {
|
|
52
|
+
const taskStatus = task.status || 'unknown';
|
|
53
|
+
if (groupedTasks.hasOwnProperty(taskStatus)) {
|
|
54
|
+
groupedTasks[taskStatus].push(task);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
tasks,
|
|
60
|
+
groupedTasks
|
|
61
|
+
};
|
|
62
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Archetype = require('archetype');
|
|
4
|
+
const mongoose = require('mongoose');
|
|
5
|
+
|
|
6
|
+
const RescheduleTaskParams = new Archetype({
|
|
7
|
+
taskId: {
|
|
8
|
+
$type: mongoose.Types.ObjectId,
|
|
9
|
+
$required: true
|
|
10
|
+
},
|
|
11
|
+
scheduledAt: {
|
|
12
|
+
$type: Date,
|
|
13
|
+
$required: true
|
|
14
|
+
}
|
|
15
|
+
}).compile('RescheduleTaskParams');
|
|
16
|
+
|
|
17
|
+
module.exports = ({ db }) => async function rescheduleTask(params) {
|
|
18
|
+
params = new RescheduleTaskParams(params);
|
|
19
|
+
const { taskId, scheduledAt } = params;
|
|
20
|
+
const { Task } = db.models;
|
|
21
|
+
|
|
22
|
+
const task = await Task.findOne({ _id: taskId }).orFail();
|
|
23
|
+
|
|
24
|
+
if (scheduledAt < Date.now()) {
|
|
25
|
+
throw new Error('Cannot reschedule a task for the past');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (task.status != 'pending') {
|
|
29
|
+
throw new Error('Cannot reschedule a task that is not pending');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
task.scheduledAt = scheduledAt;
|
|
33
|
+
|
|
34
|
+
await task.save();
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
task
|
|
38
|
+
};
|
|
39
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Archetype = require('archetype');
|
|
4
|
+
const mongoose = require('mongoose');
|
|
5
|
+
|
|
6
|
+
const RunTaskParams = new Archetype({
|
|
7
|
+
taskId: {
|
|
8
|
+
$type: mongoose.Types.ObjectId,
|
|
9
|
+
$required: true
|
|
10
|
+
}
|
|
11
|
+
}).compile('RunTaskParams');
|
|
12
|
+
|
|
13
|
+
module.exports = ({ db }) => async function runTask(params) {
|
|
14
|
+
params = new RunTaskParams(params);
|
|
15
|
+
const { taskId } = params;
|
|
16
|
+
const { Task } = db.models;
|
|
17
|
+
|
|
18
|
+
const task = await Task.findOne({ _id: taskId }).orFail();
|
|
19
|
+
|
|
20
|
+
const executedTask = await Task.execute(task);
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
task: executedTask
|
|
24
|
+
};
|
|
25
|
+
};
|
package/backend/actions/index.js
CHANGED
package/backend/authorize.js
CHANGED
|
@@ -17,12 +17,14 @@ const actionsToRequiredRoles = {
|
|
|
17
17
|
'Model.deleteDocument': ['owner', 'admin', 'member'],
|
|
18
18
|
'Model.deleteDocuments': ['owner', 'admin', 'member'],
|
|
19
19
|
'Model.dropIndex': ['owner', 'admin'],
|
|
20
|
+
'Model.executeDocumentScript': ['owner', 'admin', 'member'],
|
|
20
21
|
'Model.exportQueryResults': ['owner', 'admin', 'member', 'readonly'],
|
|
21
22
|
'Model.getDocument': ['owner', 'admin', 'member', 'readonly'],
|
|
22
23
|
'Model.getDocuments': ['owner', 'admin', 'member', 'readonly'],
|
|
23
24
|
'Model.getDocumentsStream': ['owner', 'admin', 'member', 'readonly'],
|
|
24
25
|
'Model.getIndexes': ['owner', 'admin', 'member', 'readonly'],
|
|
25
26
|
'Model.listModels': ['owner', 'admin', 'member', 'readonly'],
|
|
27
|
+
'Model.streamDocumentChanges': ['owner', 'admin', 'member', 'readonly'],
|
|
26
28
|
'Model.streamChatMessage': ['owner', 'admin', 'member', 'readonly'],
|
|
27
29
|
'Model.updateDocuments': ['owner', 'admin', 'member']
|
|
28
30
|
};
|
package/backend/index.js
CHANGED
|
@@ -16,6 +16,12 @@ module.exports = function backend(db, studioConnection, options) {
|
|
|
16
16
|
const ChatMessage = studioConnection.model('__Studio_ChatMessage', chatMessageSchema, 'studio__chatMessages');
|
|
17
17
|
const ChatThread = studioConnection.model('__Studio_ChatThread', chatThreadSchema, 'studio__chatThreads');
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
let changeStream = null;
|
|
20
|
+
if (options?.changeStream) {
|
|
21
|
+
changeStream = db.watch();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const actions = applySpec(Actions, { db, studioConnection, options, changeStream });
|
|
25
|
+
actions.services = { changeStream };
|
|
20
26
|
return actions;
|
|
21
27
|
};
|
package/eslint.config.js
CHANGED
|
@@ -37,9 +37,12 @@ module.exports = defineConfig([
|
|
|
37
37
|
fetch: true,
|
|
38
38
|
__dirname: true,
|
|
39
39
|
process: true,
|
|
40
|
+
clearTimeout: true,
|
|
40
41
|
setTimeout: true,
|
|
41
42
|
navigator: true,
|
|
42
|
-
TextDecoder: true
|
|
43
|
+
TextDecoder: true,
|
|
44
|
+
AbortController: true,
|
|
45
|
+
clearTimeout: true
|
|
43
46
|
},
|
|
44
47
|
sourceType: 'commonjs'
|
|
45
48
|
},
|
package/express.js
CHANGED
|
@@ -7,8 +7,9 @@ const { toRoute, objectRouter } = require('extrovert');
|
|
|
7
7
|
|
|
8
8
|
module.exports = async function mongooseStudioExpressApp(apiUrl, conn, options) {
|
|
9
9
|
const router = express.Router();
|
|
10
|
+
options = options ? { changeStream: true, ...options } : { changeStream: true };
|
|
10
11
|
|
|
11
|
-
const mothershipUrl = options
|
|
12
|
+
const mothershipUrl = options._mothershipUrl || 'https://mongoose-js.netlify.app/.netlify/functions';
|
|
12
13
|
let workspace = null;
|
|
13
14
|
if (options?.apiKey) {
|
|
14
15
|
({ workspace } = await fetch(`${mothershipUrl}/getWorkspace`, {
|
|
@@ -31,7 +32,7 @@ module.exports = async function mongooseStudioExpressApp(apiUrl, conn, options)
|
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
apiUrl = apiUrl || 'api';
|
|
34
|
-
const backend = Backend(conn, options
|
|
35
|
+
const backend = Backend(conn, options.studioConnection, options);
|
|
35
36
|
|
|
36
37
|
router.use(
|
|
37
38
|
'/api',
|
|
@@ -80,6 +81,7 @@ module.exports = async function mongooseStudioExpressApp(apiUrl, conn, options)
|
|
|
80
81
|
);
|
|
81
82
|
|
|
82
83
|
const { config } = await frontend(apiUrl, false, options, workspace);
|
|
84
|
+
config.enableTaskVisualizer = options.enableTaskVisualizer;
|
|
83
85
|
router.get('/config.js', function (req, res) {
|
|
84
86
|
res.setHeader('Content-Type', 'application/javascript');
|
|
85
87
|
res.end(`window.MONGOOSE_STUDIO_CONFIG = ${JSON.stringify(config, null, 2)};`);
|