json-object-editor 0.10.509 → 0.10.521
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/CHANGELOG.md +35 -0
- package/_www/mcp-export.html +11 -4
- package/_www/mcp-nav.js +8 -4
- package/_www/mcp-prompt.html +96 -121
- package/_www/mcp-schemas.html +294 -0
- package/_www/mcp-test.html +86 -0
- package/docs/JOE_Master_Knowledge_Export.md +135 -0
- package/docs/joe_agent_custom_gpt_instructions_v_2.md +54 -0
- package/docs/joe_agent_spec_v_2.2.md +64 -0
- package/docs/schema_summary_guidelines.md +128 -0
- package/package.json +1 -1
- package/readme.md +525 -469
- package/server/modules/MCP.js +606 -405
- package/server/modules/Schemas.js +321 -111
- package/server/modules/Server.js +26 -15
- package/server/modules/Storage.js +9 -0
- package/server/relationships.graph.json +5 -0
- package/server/schemas/block.js +37 -0
- package/server/schemas/board.js +2 -1
- package/server/schemas/budget.js +28 -1
- package/server/schemas/event.js +42 -0
- package/server/schemas/financial_account.js +35 -0
- package/server/schemas/goal.js +30 -0
- package/server/schemas/group.js +31 -0
- package/server/schemas/include.js +28 -0
- package/server/schemas/ingredient.js +28 -0
- package/server/schemas/initiative.js +32 -0
- package/server/schemas/instance.js +31 -1
- package/server/schemas/layout.js +31 -0
- package/server/schemas/ledger.js +30 -0
- package/server/schemas/list.js +33 -0
- package/server/schemas/meal.js +30 -0
- package/server/schemas/note.js +30 -0
- package/server/schemas/notification.js +33 -1
- package/server/schemas/page.js +43 -0
- package/server/schemas/post.js +32 -0
- package/server/schemas/project.js +36 -0
- package/server/schemas/recipe.js +32 -0
- package/server/schemas/report.js +32 -0
- package/server/schemas/setting.js +22 -0
- package/server/schemas/site.js +30 -0
- package/server/schemas/status.js +33 -0
- package/server/schemas/tag.js +28 -1
- package/server/schemas/task.js +778 -737
- package/server/schemas/transaction.js +43 -0
- package/server/schemas/user.js +36 -1
- package/server/schemas/workflow.js +30 -1
|
@@ -1,112 +1,322 @@
|
|
|
1
|
-
var fs = require('fs');
|
|
2
|
-
var serverPath = '../';
|
|
3
|
-
function Schemas(){
|
|
4
|
-
var self = this;
|
|
5
|
-
this.schema = {};
|
|
6
|
-
this.schemaList =[];
|
|
7
|
-
this.raw_schemas = {};
|
|
8
|
-
this.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
1
|
+
var fs = require('fs');
|
|
2
|
+
var serverPath = '../';
|
|
3
|
+
function Schemas(){
|
|
4
|
+
var self = this;
|
|
5
|
+
this.schema = {};
|
|
6
|
+
this.schemaList =[];
|
|
7
|
+
this.raw_schemas = {};
|
|
8
|
+
this.summary = {};
|
|
9
|
+
this.summaryGeneratedAt = null;
|
|
10
|
+
this.updateDelay = 1200;
|
|
11
|
+
//this.core = {};
|
|
12
|
+
var internalSchemasDir = __dirname+'/../schemas';
|
|
13
|
+
//console.log('internal schema dir = '+internalSchemasDir);
|
|
14
|
+
JOE.schemaDir = schemaDir = JOE.appDir+'/'+JOE.webconfig.schemaDir+'/';
|
|
15
|
+
|
|
16
|
+
this.load = function(s){
|
|
17
|
+
|
|
18
|
+
//console.log('[schema] loading '+s);
|
|
19
|
+
var schema,raw_schema;
|
|
20
|
+
var origin = 'instance';
|
|
21
|
+
if(s.indexOf('.js') == -1){s+= '.js';}
|
|
22
|
+
try{
|
|
23
|
+
var schemapath = JOE.schemaDir + s;
|
|
24
|
+
fs.accessSync(schemapath, fs.F_OK);
|
|
25
|
+
|
|
26
|
+
}catch(e){
|
|
27
|
+
var schemapath = serverPath+'schemas/' + s;
|
|
28
|
+
origin = 'core';
|
|
29
|
+
}
|
|
30
|
+
try{
|
|
31
|
+
try{
|
|
32
|
+
delete require.cache[require.resolve(schemapath)];
|
|
33
|
+
}catch(e){
|
|
34
|
+
console.log('[schema][error] '+s+' not deleted');
|
|
35
|
+
}
|
|
36
|
+
schema = require(schemapath);
|
|
37
|
+
try { schema.__origin = origin; } catch(_e) {}
|
|
38
|
+
|
|
39
|
+
}catch(e){
|
|
40
|
+
schema = {error:'schema '+s+' not found'};
|
|
41
|
+
console.log(e,schema);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/*raw_schema = self.raw_schemas[s.replace('.js','')] = $c.merge({},schema);
|
|
45
|
+
if(raw_schema.events){
|
|
46
|
+
raw_schema.events = $c.merge({},raw_schema.events);
|
|
47
|
+
}*/
|
|
48
|
+
raw_schema = self.raw_schemas[s.replace('.js','')] = schema.duplicate(true);
|
|
49
|
+
JOE.Utils.stringFunctions(schema);
|
|
50
|
+
let schemaname = s.replace('.js','')
|
|
51
|
+
self.schema[schemaname] = schema;
|
|
52
|
+
self.schemaList.push(schemaname);
|
|
53
|
+
return schema;
|
|
54
|
+
}
|
|
55
|
+
this.updateTimeout;
|
|
56
|
+
// Build normalized schema summaries for agents and tooling
|
|
57
|
+
this.buildSummary = function(){
|
|
58
|
+
try{
|
|
59
|
+
var summaries = {};
|
|
60
|
+
var MAX_META_LEN = 160;
|
|
61
|
+
function truncMeta(v){
|
|
62
|
+
if (typeof v !== 'string') return v;
|
|
63
|
+
return (v.length > MAX_META_LEN) ? (v.slice(0, MAX_META_LEN-1) + '…') : v;
|
|
64
|
+
}
|
|
65
|
+
// First pass: collect outbound relationships and field basics
|
|
66
|
+
Object.keys(self.schema||{}).forEach(function(name){
|
|
67
|
+
var def = self.schema[name] || {};
|
|
68
|
+
var fieldDefs = [];
|
|
69
|
+
var outbound = [];
|
|
70
|
+
var searchable = Array.isArray(def.searchables) ? def.searchables.slice() : [];
|
|
71
|
+
var allowedSorts = [];
|
|
72
|
+
var defaultSort = null;
|
|
73
|
+
|
|
74
|
+
// Sorters
|
|
75
|
+
if (Array.isArray(def.sorter)){
|
|
76
|
+
def.sorter.forEach(function(s){
|
|
77
|
+
var fieldName = null, dir = 'asc';
|
|
78
|
+
if (typeof s === 'string'){
|
|
79
|
+
fieldName = s.replace(/^!/, function(){ dir='desc'; return ''; });
|
|
80
|
+
} else if (s && typeof s === 'object' && typeof s.field === 'string'){
|
|
81
|
+
fieldName = s.field.replace(/^!/, function(){ dir='desc'; return ''; });
|
|
82
|
+
}
|
|
83
|
+
if (fieldName){
|
|
84
|
+
allowedSorts.push(fieldName);
|
|
85
|
+
if (!defaultSort) { defaultSort = { field: fieldName, dir: dir }; }
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
if (!defaultSort){ defaultSort = { field: 'joeUpdated', dir: 'desc' }; }
|
|
90
|
+
|
|
91
|
+
// Fields (robust extraction)
|
|
92
|
+
var rawFields = [];
|
|
93
|
+
try{
|
|
94
|
+
if (Array.isArray(def.fields)) { rawFields = def.fields; }
|
|
95
|
+
else if (typeof def.fields === 'function') { rawFields = def.fields.call(def) || []; }
|
|
96
|
+
}catch(_e){ rawFields = []; }
|
|
97
|
+
|
|
98
|
+
function inferRef(fieldObj){
|
|
99
|
+
var tSchema = null, isRef = false, isArr = false;
|
|
100
|
+
var t = fieldObj.type || 'unknown';
|
|
101
|
+
if (t === 'objectList' || t === 'array') { isArr = true; }
|
|
102
|
+
if (t === 'select'){
|
|
103
|
+
if (typeof fieldObj.schema === 'string'){ isRef = true; tSchema = fieldObj.schema; }
|
|
104
|
+
else if (typeof fieldObj.values === 'string'){ isRef = true; tSchema = fieldObj.values; }
|
|
105
|
+
else if (typeof fieldObj.goto === 'string'){ isRef = true; tSchema = fieldObj.goto; }
|
|
106
|
+
}
|
|
107
|
+
// Heuristics by name
|
|
108
|
+
var n = fieldObj.name || '';
|
|
109
|
+
if (!isRef && typeof n === 'string'){
|
|
110
|
+
if (n === 'status'){ isRef = true; tSchema = 'status'; }
|
|
111
|
+
else if (n === 'project'){ isRef = true; tSchema = 'project'; }
|
|
112
|
+
else if (n === 'assignee'){ isRef = true; tSchema = 'user'; }
|
|
113
|
+
else if (n === 'members'){ isRef = true; tSchema = 'user'; isArr = true; }
|
|
114
|
+
else if (n === 'tags'){ isRef = true; tSchema = 'tag'; isArr = true; }
|
|
115
|
+
else if (/_id$/.test(n)){ isRef = true; tSchema = n.replace(/_id$/, ''); }
|
|
116
|
+
else if (/Id$/.test(n)){ isRef = true; tSchema = n.replace(/Id$/, ''); }
|
|
117
|
+
}
|
|
118
|
+
return { isReference: !!isRef, targetSchema: tSchema, isArray: !!isArr };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function pushFieldEntry(entry){
|
|
122
|
+
if (!entry) return;
|
|
123
|
+
if (typeof entry === 'string'){
|
|
124
|
+
// string shorthand
|
|
125
|
+
var refGuess = inferRef({ name: entry, type: 'string' });
|
|
126
|
+
fieldDefs.push({ name: entry, type: 'string', isArray: false, isReference: refGuess.isReference, targetSchema: refGuess.targetSchema });
|
|
127
|
+
if (refGuess.isReference && refGuess.targetSchema){ outbound.push({ field: entry, targetSchema: refGuess.targetSchema }); }
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (typeof entry === 'object'){
|
|
131
|
+
// skip layout markers
|
|
132
|
+
if (entry.section_start || entry.section_end || entry.sidebar_start || entry.sidebar_end) { return; }
|
|
133
|
+
// Treat extend as a real field mapped to core definition with specs overlay
|
|
134
|
+
if (entry.extend){
|
|
135
|
+
try{
|
|
136
|
+
var fname = entry.extend;
|
|
137
|
+
var coreDef = (JOE && JOE.Fields && (JOE.Fields.core || JOE.Fields.fields) && (JOE.Fields.core[fname] || JOE.Fields.fields[fname])) || null;
|
|
138
|
+
var inferredType = (entry.specs && entry.specs.type) || (coreDef && coreDef.type) || 'string';
|
|
139
|
+
var ref2 = inferRef({ name: fname, type: inferredType });
|
|
140
|
+
var fobjE = { name: fname, type: inferredType, isArray: !!(coreDef && coreDef.isArray) || (inferredType==='objectList'), isReference: ref2.isReference, targetSchema: ref2.targetSchema };
|
|
141
|
+
if (entry.specs && entry.specs.display) { fobjE.display = entry.specs.display; }
|
|
142
|
+
else if (coreDef && coreDef.display) { fobjE.display = coreDef.display; }
|
|
143
|
+
if (entry.specs && entry.specs.comment) { fobjE.comment = truncMeta(entry.specs.comment); }
|
|
144
|
+
else if (coreDef && coreDef.comment) { fobjE.comment = truncMeta(coreDef.comment); }
|
|
145
|
+
if (entry.specs && entry.specs.tooltip) { fobjE.tooltip = truncMeta(entry.specs.tooltip); }
|
|
146
|
+
else if (coreDef && coreDef.tooltip) { fobjE.tooltip = truncMeta(coreDef.tooltip); }
|
|
147
|
+
fieldDefs.push(fobjE);
|
|
148
|
+
if (ref2.isReference && ref2.targetSchema){ outbound.push({ field: fname, targetSchema: ref2.targetSchema, cardinality: (fobjE.isArray ? 'many' : 'one') }); }
|
|
149
|
+
return;
|
|
150
|
+
}catch(_e){ /* fall through */ }
|
|
151
|
+
}
|
|
152
|
+
if (entry.name){
|
|
153
|
+
var t = entry.type || 'unknown';
|
|
154
|
+
var ref = inferRef(entry);
|
|
155
|
+
var fobj = { name: entry.name, type: t, isArray: !!ref.isArray || t === 'objectList', isReference: ref.isReference, targetSchema: ref.targetSchema };
|
|
156
|
+
if (entry.display) { fobj.display = entry.display; }
|
|
157
|
+
if (entry.comment) { fobj.comment = truncMeta(entry.comment); }
|
|
158
|
+
if (entry.tooltip) { fobj.tooltip = truncMeta(entry.tooltip); }
|
|
159
|
+
fieldDefs.push(fobj);
|
|
160
|
+
if (ref.isReference && ref.targetSchema){ outbound.push({ field: entry.name, targetSchema: ref.targetSchema, cardinality: (ref.isArray || t === 'objectList') ? 'many' : 'one' }); }
|
|
161
|
+
}
|
|
162
|
+
// If objectList has properties, we don't descend (embedded), keep top-level only
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
rawFields.forEach(pushFieldEntry);
|
|
167
|
+
// Ensure core timestamps/ids appear even if not declared explicitly
|
|
168
|
+
['_id','itemtype','joeUpdated','created'].forEach(function(coreName){
|
|
169
|
+
if (!fieldDefs.some(function(fd){ return fd.name === coreName; })){
|
|
170
|
+
fieldDefs.push({ name: coreName, type: coreName === '_id' ? 'string' : (coreName==='joeUpdated'||coreName==='created'?'date-time':'string') });
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Label field guess
|
|
175
|
+
var labelField = null;
|
|
176
|
+
['name','title','label'].some(function(c){
|
|
177
|
+
var has = fieldDefs.some(function(fd){ return fd && fd.name === c; });
|
|
178
|
+
if (has){ labelField = c; return true; }
|
|
179
|
+
return false;
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Ensure common sorts appear
|
|
183
|
+
['joeUpdated','created','name'].forEach(function(sf){ if (allowedSorts.indexOf(sf) === -1) { allowedSorts.push(sf); } });
|
|
184
|
+
|
|
185
|
+
var baseSummary = {
|
|
186
|
+
description: (def.summary && typeof def.summary.description === 'string') ? def.summary.description : '',
|
|
187
|
+
purpose: (def.summary && typeof def.summary.purpose === 'string') ? def.summary.purpose : '',
|
|
188
|
+
wip: !!def.wip,
|
|
189
|
+
source: (def.summary && typeof def.summary.source === 'string') ? def.summary.source : ((def.__origin === 'core') ? 'core' : 'instance'),
|
|
190
|
+
labelField: labelField || 'name',
|
|
191
|
+
defaultSort: defaultSort,
|
|
192
|
+
searchableFields: searchable,
|
|
193
|
+
allowedSorts: allowedSorts,
|
|
194
|
+
relationships: { outbound: outbound, inbound: { graphRef: 'server/relationships.graph.json' } },
|
|
195
|
+
fields: fieldDefs,
|
|
196
|
+
fieldCountBase: fieldDefs.length,
|
|
197
|
+
joeManagedFields: ['created','joeUpdated']
|
|
198
|
+
};
|
|
199
|
+
// Allow curated overrides from schema.summary
|
|
200
|
+
var finalSummary = baseSummary;
|
|
201
|
+
if (def.summary && typeof def.summary === 'object'){
|
|
202
|
+
// Shallow merge top-level props, but merge fields by name (do not replace entire list)
|
|
203
|
+
var curated = def.summary;
|
|
204
|
+
finalSummary = Object.assign({}, baseSummary, curated);
|
|
205
|
+
try{
|
|
206
|
+
var mergedFields = (baseSummary.fields||[]).slice();
|
|
207
|
+
var byName = {};
|
|
208
|
+
mergedFields.forEach(function(f, idx){ byName[f.name] = { idx: idx, field: f }; });
|
|
209
|
+
if (Array.isArray(curated.fields)){
|
|
210
|
+
curated.fields.forEach(function(cf){
|
|
211
|
+
if (!cf || !cf.name) return;
|
|
212
|
+
if (byName[cf.name]){
|
|
213
|
+
// overlay curated props onto base field
|
|
214
|
+
var existing = mergedFields[byName[cf.name].idx];
|
|
215
|
+
mergedFields[byName[cf.name].idx] = Object.assign({}, existing, cf);
|
|
216
|
+
} else {
|
|
217
|
+
mergedFields.push(cf);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
finalSummary.fields = mergedFields;
|
|
222
|
+
}catch(_e){ finalSummary.fields = baseSummary.fields; }
|
|
223
|
+
}
|
|
224
|
+
// Enrich curated fields with display/comment/tooltip from base when missing
|
|
225
|
+
try{
|
|
226
|
+
var baseFieldByName = {};
|
|
227
|
+
(baseSummary.fields||[]).forEach(function(f){ baseFieldByName[f.name] = f; });
|
|
228
|
+
(finalSummary.fields||[]).forEach(function(f){
|
|
229
|
+
var bf = baseFieldByName[f.name];
|
|
230
|
+
if (!bf) return;
|
|
231
|
+
if (f.display == null && bf.display != null) f.display = bf.display;
|
|
232
|
+
if (f.comment == null && bf.comment != null) f.comment = truncMeta(bf.comment);
|
|
233
|
+
if (f.tooltip == null && bf.tooltip != null) f.tooltip = truncMeta(bf.tooltip);
|
|
234
|
+
if (f.comment) f.comment = truncMeta(f.comment);
|
|
235
|
+
if (f.tooltip) f.tooltip = truncMeta(f.tooltip);
|
|
236
|
+
});
|
|
237
|
+
}catch(_e){}
|
|
238
|
+
|
|
239
|
+
// Final counts
|
|
240
|
+
try{
|
|
241
|
+
finalSummary.fieldCount = Array.isArray(finalSummary.fields) ? finalSummary.fields.length : 0;
|
|
242
|
+
if (typeof finalSummary.fieldCountBase !== 'number'){
|
|
243
|
+
finalSummary.fieldCountBase = baseSummary.fieldCountBase || 0;
|
|
244
|
+
}
|
|
245
|
+
}catch(_e){}
|
|
246
|
+
|
|
247
|
+
summaries[name] = finalSummary;
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// Inbound relationships will be maintained in a shared graph file; do not compute here
|
|
251
|
+
|
|
252
|
+
self.summary = summaries;
|
|
253
|
+
self.summaryGeneratedAt = new Date();
|
|
254
|
+
}catch(e){
|
|
255
|
+
console.log('[Schemas] buildSummary error:', e);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
this.update = function(doItNow){
|
|
260
|
+
if(doItNow){
|
|
261
|
+
var lBM = new Benchmarker();
|
|
262
|
+
var schema_to_load = [];
|
|
263
|
+
//go through all the server files, then the schemadir files\
|
|
264
|
+
schema_to_load = schema_to_load.concat(fs.readdirSync(internalSchemasDir));
|
|
265
|
+
//schema_to_load = schema_to_load.concat(fs.readdirSync('server/schemas'));
|
|
266
|
+
schema_to_load = schema_to_load.concat(fs.readdirSync(JOE.schemaDir));
|
|
267
|
+
schema_to_load.map(function(sc){
|
|
268
|
+
self.load(sc);
|
|
269
|
+
})
|
|
270
|
+
// Build normalized summaries after load
|
|
271
|
+
try { self.buildSummary(); } catch(_e){}
|
|
272
|
+
logit(JOE.Utils.color('[schema] ','module')+schema_to_load.length+' updated in '+lBM.stop()+' secs');
|
|
273
|
+
}else{
|
|
274
|
+
clearTimeout(self.updateTimeout);
|
|
275
|
+
self.updateTimeout = setTimeout(self.update,self.updateDelay,true);
|
|
276
|
+
//logit(JOE.Utils.color('[schema] ','module')+'updating in '+ this.updateDelay/1000);
|
|
277
|
+
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
JOE.Utils.setupFileFolder(schemaDir,'schemas',self.update);
|
|
281
|
+
fs.watch(internalSchemasDir,function(){
|
|
282
|
+
self.update();
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
this.events = function(item,events,specs){
|
|
286
|
+
try{
|
|
287
|
+
var specs = specs || {};
|
|
288
|
+
var schemaprop = specs.schemaprop || "itemtype";
|
|
289
|
+
var schemaname = item[schemaprop];
|
|
290
|
+
var schema_def = self.raw_schemas[schemaname];
|
|
291
|
+
|
|
292
|
+
if(schema_def){
|
|
293
|
+
|
|
294
|
+
if(typeof events == "string"){
|
|
295
|
+
events = events.split(',');
|
|
296
|
+
}
|
|
297
|
+
events.map(function(event){
|
|
298
|
+
if(schema_def.events && schema_def.events[event]){
|
|
299
|
+
if(typeof schema_def.events[event] == "function"){
|
|
300
|
+
try{
|
|
301
|
+
|
|
302
|
+
schema_def.events[event](item,specs);
|
|
303
|
+
logit('[event] '+schemaname+' > '+event);
|
|
304
|
+
}catch(e){
|
|
305
|
+
console.log(JOE.Utils.color('[schema]event error: ','error')+event+':'+e);
|
|
306
|
+
}
|
|
307
|
+
}else{
|
|
308
|
+
logit('no event'+event+' for '+schemaname );
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
})
|
|
312
|
+
}else{
|
|
313
|
+
logit('schema "'+schemaname+'" not found');
|
|
314
|
+
}
|
|
315
|
+
}catch(e){
|
|
316
|
+
console.log(JOE.Utils.color('[schema] hook error: ','error')+events+':'+e);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
this.update();
|
|
320
|
+
return self;
|
|
321
|
+
}
|
|
112
322
|
module.exports = new Schemas();
|
package/server/modules/Server.js
CHANGED
|
@@ -156,12 +156,18 @@ server.get('/mcp-test.html',auth,function(req,res){
|
|
|
156
156
|
server.get('/mcp-export.html',auth,function(req,res){
|
|
157
157
|
res.sendFile(path.join(JOE.joedir,'_www','mcp-export.html'));
|
|
158
158
|
});
|
|
159
|
+
server.get('/mcp-schemas.html',auth,function(req,res){
|
|
160
|
+
res.sendFile(path.join(JOE.joedir,'_www','mcp-schemas.html'));
|
|
161
|
+
});
|
|
159
162
|
server.get(JOE.webconfig.joepath+'_www/mcp-test.html',auth,function(req,res){
|
|
160
163
|
res.sendFile(path.join(JOE.joedir,'_www','mcp-test.html'));
|
|
161
164
|
});
|
|
162
165
|
server.get(JOE.webconfig.joepath+'_www/mcp-export.html',auth,function(req,res){
|
|
163
166
|
res.sendFile(path.join(JOE.joedir,'_www','mcp-export.html'));
|
|
164
167
|
});
|
|
168
|
+
server.get(JOE.webconfig.joepath+'_www/mcp-schemas.html',auth,function(req,res){
|
|
169
|
+
res.sendFile(path.join(JOE.joedir,'_www','mcp-schemas.html'));
|
|
170
|
+
});
|
|
165
171
|
|
|
166
172
|
server.use(JOE.webconfig.joepath,express.static(JOE.joedir));
|
|
167
173
|
|
|
@@ -528,23 +534,28 @@ server.get(['/API/schema/:names','/API/schemas/:names'],auth,function(req,res,ne
|
|
|
528
534
|
}else if(!JOE.Data || !JOE.Data.user || !JOE.Data.user.length){
|
|
529
535
|
isNew = true;
|
|
530
536
|
}
|
|
537
|
+
var useSummary = (req.query.summaryOnly === 'true' || req.query.summaryOnly === true || req.query.summaries === 'true' || req.query.view === 'summary');
|
|
531
538
|
schemaname.map(function(s){
|
|
532
539
|
if(isSuper || isNew || permitted.indexOf(s) != -1){
|
|
533
|
-
payload[s] = JOE.Schemas.schema[s];
|
|
534
|
-
}
|
|
535
|
-
});
|
|
536
|
-
var F = (req.query.fields||req.params.fields||'core').split(',');
|
|
537
|
-
F.map(function(f){
|
|
538
|
-
try {
|
|
539
|
-
fieldspath = serverPath+'fields/' + f + '.js';
|
|
540
|
-
delete require.cache[require.resolve(fieldspath)];
|
|
541
|
-
fields = require(fieldspath);
|
|
542
|
-
stringFunctions(fields);
|
|
543
|
-
JOE.Fields[f]=fields;
|
|
544
|
-
}catch(e){
|
|
545
|
-
fields ={error:'field '+f+' not found'};
|
|
540
|
+
payload[s] = useSummary ? (JOE.Schemas.summary && JOE.Schemas.summary[s]) : JOE.Schemas.schema[s];
|
|
546
541
|
}
|
|
547
542
|
});
|
|
543
|
+
var Fraw = (req.query.fields||req.params.fields||'core');
|
|
544
|
+
var noFields = (Fraw === 'none') || (useSummary === true);
|
|
545
|
+
if (!noFields){
|
|
546
|
+
var F = Fraw.split(',');
|
|
547
|
+
F.map(function(f){
|
|
548
|
+
try {
|
|
549
|
+
fieldspath = serverPath+'fields/' + f + '.js';
|
|
550
|
+
delete require.cache[require.resolve(fieldspath)];
|
|
551
|
+
fields = require(fieldspath);
|
|
552
|
+
stringFunctions(fields);
|
|
553
|
+
JOE.Fields[f]=fields;
|
|
554
|
+
}catch(e){
|
|
555
|
+
fields ={error:'field '+f+' not found'};
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
}
|
|
548
559
|
|
|
549
560
|
var bm = sBM.stop();
|
|
550
561
|
switch(format){
|
|
@@ -555,7 +566,7 @@ server.get(['/API/schema/:names','/API/schemas/:names'],auth,function(req,res,ne
|
|
|
555
566
|
res.send('var joe_info = '+js_payload);
|
|
556
567
|
break;
|
|
557
568
|
default:
|
|
558
|
-
res.jsonp({schemas:payload,fields:fields,benchmark:bm});
|
|
569
|
+
res.jsonp({schemas:payload,fields:fields,benchmark:bm, summaryOnly: !!useSummary});
|
|
559
570
|
break;
|
|
560
571
|
}
|
|
561
572
|
|
|
@@ -673,7 +684,7 @@ server.get(['/API/save/','/API/save/:itemid'],auth,function(req,res,next){
|
|
|
673
684
|
object[prop] = $c.parseBoolean(object[prop]);
|
|
674
685
|
}
|
|
675
686
|
}
|
|
676
|
-
object.joeUpdated = new Date();
|
|
687
|
+
object.joeUpdated = new Date().toISOString();
|
|
677
688
|
//TODO: check to make sure schema idprop is present
|
|
678
689
|
JOE.Storage.save(object,object.itemtype,function(err,results){
|
|
679
690
|
//var object = merge({},results);
|
|
@@ -97,6 +97,15 @@ function Storage(specs){
|
|
|
97
97
|
var specs = $c.merge({history:true},(specs||{}));
|
|
98
98
|
var user = specs.user || {name:'anonymous'};
|
|
99
99
|
|
|
100
|
+
// Ensure timestamps: always set joeUpdated; set created only on create when missing
|
|
101
|
+
try{
|
|
102
|
+
if(!data.joeUpdated){ data.joeUpdated = new Date().toISOString(); }
|
|
103
|
+
if(!data.created){
|
|
104
|
+
var cachedBefore = (data && data._id && JOE && JOE.Cache && JOE.Cache.findByID) ? JOE.Cache.findByID(collection,data._id) : null;
|
|
105
|
+
if(!cachedBefore){ data.created = new Date().toISOString(); }
|
|
106
|
+
}
|
|
107
|
+
}catch(_e){}
|
|
108
|
+
|
|
100
109
|
if(JOE.Mongo){
|
|
101
110
|
logit(moduleName+' mongo saving -> '+colorize(collection,'schema'));
|
|
102
111
|
|
package/server/schemas/block.js
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
var block = {
|
|
2
2
|
info:"A block is a small chunk of reusable content, which can be customized when it is placed in a page or layout.",
|
|
3
|
+
// Curated summary for agents
|
|
4
|
+
summary:{
|
|
5
|
+
description:'Reusable content component or template used within layouts and pages.',
|
|
6
|
+
purpose:'Blocks encapsulate content or logic. They can be used directly or defined as templates (is_template) to generate other blocks.',
|
|
7
|
+
labelField:'name',
|
|
8
|
+
defaultSort:{ field:'name', dir:'asc' },
|
|
9
|
+
searchableFields:['name','info','_id'],
|
|
10
|
+
allowedSorts:['name','joeUpdated','created'],
|
|
11
|
+
relationships:{
|
|
12
|
+
outbound:[
|
|
13
|
+
{ field:'site', targetSchema:'site', cardinality:'one' },
|
|
14
|
+
{ field:'includes', targetSchema:'include', cardinality:'many' },
|
|
15
|
+
{ field:'use_template', targetSchema:'block', cardinality:'one' }
|
|
16
|
+
],
|
|
17
|
+
inbound:{ graphRef:'server/relationships.graph.json' }
|
|
18
|
+
},
|
|
19
|
+
joeManagedFields:['created','joeUpdated'],
|
|
20
|
+
fields:[
|
|
21
|
+
{ name:'_id', type:'string', required:true },
|
|
22
|
+
{ name:'itemtype', type:'string', required:true, const:'block' },
|
|
23
|
+
{ name:'name', type:'string', required:true },
|
|
24
|
+
{ name:'info', type:'string' },
|
|
25
|
+
{ name:'description', type:'string' },
|
|
26
|
+
{ name:'use_template', type:'string', isReference:true, targetSchema:'block' },
|
|
27
|
+
{ name:'is_template', type:'boolean' },
|
|
28
|
+
{ name:'template_type', type:'string', enumValues:['code','module'] },
|
|
29
|
+
{ name:'template', type:'string' },
|
|
30
|
+
{ name:'block_fields', type:'objectList' },
|
|
31
|
+
{ name:'content_type', type:'string', enumValues:['wysiwyg','code','module'] },
|
|
32
|
+
{ name:'content', type:'string' },
|
|
33
|
+
{ name:'process_functions', type:'objectList' },
|
|
34
|
+
{ name:'site', type:'string', isReference:true, targetSchema:'site' },
|
|
35
|
+
{ name:'includes', type:'string', isArray:true, isReference:true, targetSchema:'include' },
|
|
36
|
+
{ name:'joeUpdated', type:'string', format:'date-time', required:true },
|
|
37
|
+
{ name:'created', type:'string', format:'date-time', required:true }
|
|
38
|
+
]
|
|
39
|
+
},
|
|
3
40
|
sorter:['name',{value:'!is_template',display:'template'}],
|
|
4
41
|
subsets:function(){
|
|
5
42
|
var subs = [];
|
package/server/schemas/board.js
CHANGED
package/server/schemas/budget.js
CHANGED
|
@@ -2,6 +2,33 @@ var schema = {
|
|
|
2
2
|
title : '${name}',
|
|
3
3
|
info:"Use a budget to track your income and expenses.",
|
|
4
4
|
menuicon:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="-15 -15 80 80"><path d="M18.5 2.6C14.4 2.6 11 5.9 11 10.1 11 11.2 11.3 12.3 11.8 13.3L13.5 12.4C13.2 11.7 13 10.9 13 10.1 13 7 15.4 4.6 18.5 4.6 21.5 4.6 24 7 24 10.1L26 10.1C26 5.9 22.6 2.6 18.5 2.6zM37.2 6.9A1 1 0 0 0 37.1 7 1 1 0 0 0 37 7C34.5 7.6 31.6 9.2 29.7 12.6 28 12.2 26.2 11.9 24.3 11.9 18.6 11.9 14.2 13.7 10.7 16.4 8.9 17.8 7.5 19.7 6.7 22 6.3 21.9 5.9 21.7 5.5 21.4 4.7 20.9 4 20.1 4 18.8 4 17.7 4.5 16.8 5.3 16.4A1 1 0 0 0 4.9 14.5 1 1 0 0 0 4.6 14.6C2.9 15.3 2 17 2 18.8 2 20.9 3.2 22.3 4.3 23.1 5 23.5 5.6 23.8 6.1 23.9 5.7 25.4 5.6 27 5.7 28.8 5.8 33.2 8.9 36.5 11.1 38.3A1 1 0 0 0 11.2 38.3C11.4 38.5 11.4 38.6 11.4 38.6 11.5 38.6 11.5 38.6 11.4 38.8 11.1 39.8 10.5 41.2 10.5 41.2A1 1 0 0 0 10.5 41.2C10 42.6 10.7 44.2 12.1 44.7A1 1 0 0 0 12.1 44.7L15.3 45.9C16.7 46.4 18.3 45.7 18.8 44.3 18.8 44.3 19.2 43.2 19.6 42.3 19.6 42.1 19.7 42.1 19.7 42.1 19.8 42.1 19.8 42.1 19.8 42.1 20.5 42.2 21.9 42.6 24.3 42.6 26.7 42.6 28 42.4 28.7 42.3 28.8 42.3 28.9 42.3 28.9 42.3 28.9 42.4 28.9 42.4 28.9 42.4 28.9 42.5 28.9 42.5 28.9 42.5 29.3 43.4 29.6 44.3 29.7 44.3 29.7 44.4 29.7 44.4 29.7 44.4 30.2 45.7 31.8 46.4 33.2 45.9L36.4 44.7A1 1 0 0 0 36.4 44.7C37.8 44.2 38.5 42.6 37.9 41.2L37.4 39.8A1 1 0 0 0 37.3 39.7C37.3 39.7 37.4 39.7 37.4 39.7 37.4 39.7 37.2 39.7 37.6 39.6 40 38.2 41.9 36.2 43 33.8 43.1 33.5 43.2 33.4 43.3 33.3 43.4 33.2 43.4 33.2 43.8 33.2L45.4 33.2C46.8 33.2 48 32 48 30.5L48 25.7C48 24.2 46.8 23 45.4 23L43.8 23C43.5 23 43.4 23 43.4 23 42.9 20.1 41 16.5 36.7 14.7 36.7 12.6 37.2 11 38.2 8.3A1 1 0 0 0 37.2 6.9zM35.6 9.8C35 11.6 34.6 13.3 34.6 15.3A1 1 0 0 0 35.3 16.2C39.5 17.7 41 20.9 41.4 23.4 41.6 24.4 42.6 25 43.8 25L45.4 25C45.7 25 46 25.3 46 25.7L46 30.5C46 30.9 45.7 31.2 45.4 31.2L43.8 31.2C43 31.2 42.4 31.4 41.9 31.8 41.5 32.2 41.3 32.6 41.2 32.9 40.2 35 38.7 36.7 36.6 37.8 35.8 38.2 35.4 39 35.4 39.6 35.4 40.1 35.5 40.4 35.6 40.5L35.6 40.5 35.6 40.7 36.1 41.9C36.2 42.3 36 42.7 35.7 42.8L32.4 44.1C32.1 44.2 31.6 44 31.5 43.6A1 1 0 0 0 31.5 43.6C31.5 43.6 31.1 42.7 30.8 41.8 30.7 41.4 30.5 40.9 30 40.6 29.5 40.3 29 40.3 28.6 40.3 27.7 40.4 26.5 40.6 24.3 40.6 22.1 40.6 21 40.3 20.2 40.1 19.8 40 19.2 40 18.7 40.3 18.1 40.6 17.9 41.1 17.8 41.6 17.4 42.5 17 43.6 17 43.6 16.8 44 16.4 44.2 16 44.1L12.8 42.8C12.4 42.7 12.3 42.3 12.4 41.9 12.4 41.9 13 40.5 13.3 39.5 13.5 38.8 13.4 38 13.2 37.6 12.9 37.1 12.5 36.8 12.4 36.8 10.4 35.1 7.8 32.3 7.7 28.7 7.4 23.6 9.2 20.1 11.9 18 15.1 15.5 19 13.9 24.3 13.9 26.3 13.9 28.2 14.2 30 14.6A1 1 0 0 0 31.1 14.1C32.2 11.6 33.9 10.5 35.6 9.8zM36.5 23.1C35.4 23.1 34.6 23.9 34.6 25 34.6 26.1 35.4 26.9 36.5 26.9 37.5 26.9 38.4 26.1 38.4 25 38.4 23.9 37.5 23.1 36.5 23.1z"/></svg>',
|
|
5
|
+
// Curated summary for agents
|
|
6
|
+
summary:{
|
|
7
|
+
description:'Spending or income target tracked against ledgers via tags.',
|
|
8
|
+
purpose:'Use budgets to set target amounts and analyze spending history. Budgets link to ledgers sharing the same tags.',
|
|
9
|
+
labelField:'name',
|
|
10
|
+
defaultSort:{ field:'joeUpdated', dir:'desc' },
|
|
11
|
+
searchableFields:['name','info','_id'],
|
|
12
|
+
allowedSorts:['joeUpdated','created','name','amount'],
|
|
13
|
+
relationships:{
|
|
14
|
+
outbound:[
|
|
15
|
+
{ field:'tags', targetSchema:'tag', cardinality:'many' }
|
|
16
|
+
],
|
|
17
|
+
inbound:{ graphRef:'server/relationships.graph.json' }
|
|
18
|
+
},
|
|
19
|
+
joeManagedFields:['created','joeUpdated'],
|
|
20
|
+
fields:[
|
|
21
|
+
{ name:'_id', type:'string', required:true },
|
|
22
|
+
{ name:'itemtype', type:'string', required:true, const:'budget' },
|
|
23
|
+
{ name:'name', type:'string', required:true },
|
|
24
|
+
{ name:'info', type:'string' },
|
|
25
|
+
{ name:'additive', type:'boolean', display:'Sum actuals', comment:'Dynamic budget: amount equals the sum of matching transactions for the period; no remaining shown.' },
|
|
26
|
+
{ name:'amount', type:'number' },
|
|
27
|
+
{ name:'tags', type:'string', isArray:true, isReference:true, targetSchema:'tag' },
|
|
28
|
+
{ name:'joeUpdated', type:'string', format:'date-time', required:true },
|
|
29
|
+
{ name:'created', type:'string', format:'date-time', required:true }
|
|
30
|
+
]
|
|
31
|
+
},
|
|
5
32
|
listView:{
|
|
6
33
|
title:
|
|
7
34
|
'<joe-title class="joe-fright">-$${amount}</joe-title>'+
|
|
@@ -13,7 +40,7 @@ menuicon:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="-15 -15 80 80"><path
|
|
|
13
40
|
fields:[
|
|
14
41
|
'name',
|
|
15
42
|
'info',
|
|
16
|
-
'additive:boolean',
|
|
43
|
+
{name:'additive', type:'boolean', display:'Sum actuals', comment:'If checked, this budget is dynamic: its amount equals the sum of matching transactions for the selected period. No remaining is shown. Use for roll‑up categories (e.g., income or passthrough).'},
|
|
17
44
|
{name:'amount',type:'number',comment:'the default amount'},
|
|
18
45
|
'tags',
|
|
19
46
|
{sidebar_start:'right'},
|