@dforge-core/dforge-mcp 0.2.3 → 0.2.4
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/dist/server.js
CHANGED
|
@@ -3641,7 +3641,12 @@ var require_fast_uri = __commonJS({
|
|
|
3641
3641
|
}
|
|
3642
3642
|
function resolve8(baseURI, relativeURI, options) {
|
|
3643
3643
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
3644
|
-
const
|
|
3644
|
+
const { parsed: baseParsed, malformedAuthorityOrPort: baseMalformed } = parseWithStatus(baseURI, schemelessOptions);
|
|
3645
|
+
const { parsed: relativeParsed, malformedAuthorityOrPort: relativeMalformed } = parseWithStatus(relativeURI, schemelessOptions);
|
|
3646
|
+
if (baseMalformed || relativeMalformed) {
|
|
3647
|
+
throw new Error(baseParsed.error || relativeParsed.error || "URI is malformed.");
|
|
3648
|
+
}
|
|
3649
|
+
const resolved = resolveComponent(baseParsed, relativeParsed, schemelessOptions, true);
|
|
3645
3650
|
schemelessOptions.skipEscape = true;
|
|
3646
3651
|
return serialize2(resolved, schemelessOptions);
|
|
3647
3652
|
}
|
|
@@ -3766,6 +3771,8 @@ var require_fast_uri = __commonJS({
|
|
|
3766
3771
|
return uriTokens.join("");
|
|
3767
3772
|
}
|
|
3768
3773
|
var URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
|
|
3774
|
+
var AUTHORITY_PREFIX = /^(?:[^#/:?]+:)?\/\/([^/?#]*)/;
|
|
3775
|
+
var AUTHORITY_INTRODUCER_REGION = /^(?:[^#/:?]+:)?([/\\\t\n\r]*)/;
|
|
3769
3776
|
function getParseError(parsed, matches) {
|
|
3770
3777
|
if (matches[2] !== void 0 && parsed.path && parsed.path[0] !== "/") {
|
|
3771
3778
|
return 'URI path must start with "/" when authority is present.';
|
|
@@ -3795,6 +3802,25 @@ var require_fast_uri = __commonJS({
|
|
|
3795
3802
|
uri = "//" + uri;
|
|
3796
3803
|
}
|
|
3797
3804
|
}
|
|
3805
|
+
const authorityMatch = uri.match(AUTHORITY_PREFIX);
|
|
3806
|
+
if (authorityMatch !== null && authorityMatch[1].indexOf("\\") !== -1) {
|
|
3807
|
+
parsed.error = "URI authority must not contain a literal backslash.";
|
|
3808
|
+
malformedAuthorityOrPort = true;
|
|
3809
|
+
}
|
|
3810
|
+
const introducerMatch = uri.match(AUTHORITY_INTRODUCER_REGION);
|
|
3811
|
+
if (introducerMatch !== null) {
|
|
3812
|
+
const region = introducerMatch[1];
|
|
3813
|
+
const normalizedRegion = region.replace(/[\t\n\r]/g, "");
|
|
3814
|
+
if (normalizedRegion.length >= 2) {
|
|
3815
|
+
if (normalizedRegion.slice(0, 2) !== "//") {
|
|
3816
|
+
parsed.error = parsed.error || "URI authority must not contain a literal backslash.";
|
|
3817
|
+
malformedAuthorityOrPort = true;
|
|
3818
|
+
} else if (region.length !== normalizedRegion.length) {
|
|
3819
|
+
parsed.error = parsed.error || "URI authority introducer must not contain whitespace.";
|
|
3820
|
+
malformedAuthorityOrPort = true;
|
|
3821
|
+
}
|
|
3822
|
+
}
|
|
3823
|
+
}
|
|
3798
3824
|
const matches = uri.match(URI_PARSE);
|
|
3799
3825
|
if (matches) {
|
|
3800
3826
|
parsed.scheme = matches[1];
|
|
@@ -23039,17 +23065,33 @@ function normalizeObjectSchema(schema2) {
|
|
|
23039
23065
|
}
|
|
23040
23066
|
return void 0;
|
|
23041
23067
|
}
|
|
23068
|
+
function getDotPath(path17) {
|
|
23069
|
+
if (path17.length === 0) {
|
|
23070
|
+
return "object root";
|
|
23071
|
+
}
|
|
23072
|
+
return path17.reduce((acc, seg, index) => {
|
|
23073
|
+
if (index === 0) {
|
|
23074
|
+
return String(seg);
|
|
23075
|
+
}
|
|
23076
|
+
if (typeof seg === "number") {
|
|
23077
|
+
return `${acc}[${seg}]`;
|
|
23078
|
+
}
|
|
23079
|
+
return `${acc}.${seg}`;
|
|
23080
|
+
}, "");
|
|
23081
|
+
}
|
|
23042
23082
|
function getParseErrorMessage(error51) {
|
|
23043
23083
|
if (error51 && typeof error51 === "object") {
|
|
23084
|
+
if ("issues" in error51 && Array.isArray(error51.issues) && error51.issues.length > 0) {
|
|
23085
|
+
return error51.issues.map((i) => {
|
|
23086
|
+
if (!i.path?.length) {
|
|
23087
|
+
return i.message;
|
|
23088
|
+
}
|
|
23089
|
+
return `${i.message} at ${getDotPath(i.path)}`;
|
|
23090
|
+
}).join("\n");
|
|
23091
|
+
}
|
|
23044
23092
|
if ("message" in error51 && typeof error51.message === "string") {
|
|
23045
23093
|
return error51.message;
|
|
23046
23094
|
}
|
|
23047
|
-
if ("issues" in error51 && Array.isArray(error51.issues) && error51.issues.length > 0) {
|
|
23048
|
-
const firstIssue = error51.issues[0];
|
|
23049
|
-
if (firstIssue && typeof firstIssue === "object" && "message" in firstIssue) {
|
|
23050
|
-
return String(firstIssue.message);
|
|
23051
|
-
}
|
|
23052
|
-
}
|
|
23053
23095
|
try {
|
|
23054
23096
|
return JSON.stringify(error51);
|
|
23055
23097
|
} catch {
|
|
@@ -29664,16 +29706,7 @@ var Server = class extends Protocol {
|
|
|
29664
29706
|
if (!methodSchema) {
|
|
29665
29707
|
throw new Error("Schema is missing a method literal");
|
|
29666
29708
|
}
|
|
29667
|
-
|
|
29668
|
-
if (isZ4Schema(methodSchema)) {
|
|
29669
|
-
const v4Schema = methodSchema;
|
|
29670
|
-
const v4Def = v4Schema._zod?.def;
|
|
29671
|
-
methodValue = v4Def?.value ?? v4Schema.value;
|
|
29672
|
-
} else {
|
|
29673
|
-
const v3Schema = methodSchema;
|
|
29674
|
-
const legacyDef = v3Schema._def;
|
|
29675
|
-
methodValue = legacyDef?.value ?? v3Schema.value;
|
|
29676
|
-
}
|
|
29709
|
+
const methodValue = getLiteralValue(methodSchema);
|
|
29677
29710
|
if (typeof methodValue !== "string") {
|
|
29678
29711
|
throw new Error("Schema method literal must be a string");
|
|
29679
29712
|
}
|
|
@@ -30861,8 +30894,17 @@ var EMPTY_COMPLETION_RESULT = {
|
|
|
30861
30894
|
var import_node_process = __toESM(require("process"), 1);
|
|
30862
30895
|
|
|
30863
30896
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js
|
|
30897
|
+
var STDIO_DEFAULT_MAX_BUFFER_SIZE = 10 * 1024 * 1024;
|
|
30864
30898
|
var ReadBuffer = class {
|
|
30899
|
+
constructor(options) {
|
|
30900
|
+
this._maxBufferSize = options?.maxBufferSize ?? STDIO_DEFAULT_MAX_BUFFER_SIZE;
|
|
30901
|
+
}
|
|
30865
30902
|
append(chunk) {
|
|
30903
|
+
const newSize = (this._buffer?.length ?? 0) + chunk.length;
|
|
30904
|
+
if (newSize > this._maxBufferSize) {
|
|
30905
|
+
this.clear();
|
|
30906
|
+
throw new Error(`ReadBuffer exceeded maximum size of ${this._maxBufferSize} bytes`);
|
|
30907
|
+
}
|
|
30866
30908
|
this._buffer = this._buffer ? Buffer.concat([this._buffer, chunk]) : chunk;
|
|
30867
30909
|
}
|
|
30868
30910
|
readMessage() {
|
|
@@ -30890,18 +30932,24 @@ function serializeMessage(message) {
|
|
|
30890
30932
|
|
|
30891
30933
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
|
|
30892
30934
|
var StdioServerTransport = class {
|
|
30893
|
-
constructor(_stdin = import_node_process.default.stdin, _stdout = import_node_process.default.stdout) {
|
|
30935
|
+
constructor(_stdin = import_node_process.default.stdin, _stdout = import_node_process.default.stdout, options) {
|
|
30894
30936
|
this._stdin = _stdin;
|
|
30895
30937
|
this._stdout = _stdout;
|
|
30896
|
-
this._readBuffer = new ReadBuffer();
|
|
30897
30938
|
this._started = false;
|
|
30898
30939
|
this._ondata = (chunk) => {
|
|
30899
|
-
|
|
30900
|
-
|
|
30940
|
+
try {
|
|
30941
|
+
this._readBuffer.append(chunk);
|
|
30942
|
+
this.processReadBuffer();
|
|
30943
|
+
} catch (error51) {
|
|
30944
|
+
this.onerror?.(error51);
|
|
30945
|
+
this.close().catch(() => {
|
|
30946
|
+
});
|
|
30947
|
+
}
|
|
30901
30948
|
};
|
|
30902
30949
|
this._onerror = (error51) => {
|
|
30903
30950
|
this.onerror?.(error51);
|
|
30904
30951
|
};
|
|
30952
|
+
this._readBuffer = new ReadBuffer({ maxBufferSize: options?.maxBufferSize });
|
|
30905
30953
|
}
|
|
30906
30954
|
/**
|
|
30907
30955
|
* Starts listening for messages on stdin.
|
|
@@ -31166,7 +31214,7 @@ function buildZedSettings() {
|
|
|
31166
31214
|
var fs = __toESM(require("fs"));
|
|
31167
31215
|
var path = __toESM(require("path"));
|
|
31168
31216
|
|
|
31169
|
-
// node_modules/@dforge-core/metadata/dist/
|
|
31217
|
+
// node_modules/@dforge-core/metadata/dist/index.js
|
|
31170
31218
|
var AggType = {
|
|
31171
31219
|
Sum: "sum",
|
|
31172
31220
|
Count: "count",
|
|
@@ -31176,8 +31224,6 @@ var AggType = {
|
|
|
31176
31224
|
Group: "group"
|
|
31177
31225
|
};
|
|
31178
31226
|
var AGG_TYPE_LIST = Object.values(AggType);
|
|
31179
|
-
|
|
31180
|
-
// node_modules/@dforge-core/metadata/dist/field-types.js
|
|
31181
31227
|
var fieldTypes = [
|
|
31182
31228
|
{ cd: "text", name: "Text", columnType: "D", baseDatatypeCd: "string", supportedAgg: null, maxLen: null, precision: null, useStringInFilter: true },
|
|
31183
31229
|
{ cd: "avatar", name: "Avatar Name", columnType: "D", baseDatatypeCd: "string", supportedAgg: null, maxLen: null, precision: null, useStringInFilter: true },
|
|
@@ -31217,8 +31263,6 @@ function isFieldTypeCd(cd) {
|
|
|
31217
31263
|
function getFieldType(cd) {
|
|
31218
31264
|
return byCd.get(cd);
|
|
31219
31265
|
}
|
|
31220
|
-
|
|
31221
|
-
// node_modules/@dforge-core/metadata/dist/base-datatypes.js
|
|
31222
31266
|
var BASE_TO_DB = {
|
|
31223
31267
|
binary: "bytea",
|
|
31224
31268
|
bool: "bool",
|
|
@@ -31240,8 +31284,6 @@ var BASE_TO_DB = {
|
|
|
31240
31284
|
function baseToDbDatatype(base) {
|
|
31241
31285
|
return BASE_TO_DB[base] ?? null;
|
|
31242
31286
|
}
|
|
31243
|
-
|
|
31244
|
-
// node_modules/@dforge-core/metadata/dist/column-types.js
|
|
31245
31287
|
var columnTypes = [
|
|
31246
31288
|
{ cd: "D", name: "Value", description: "A plain data column stored on this entity." },
|
|
31247
31289
|
{ cd: "R", name: "Reference", description: "An N:1 lookup to another entity (foreign key)." },
|
|
@@ -31255,8 +31297,6 @@ var byCd2 = new Map(columnTypes.map((ct) => [ct.cd, ct]));
|
|
|
31255
31297
|
function getColumnType(cd) {
|
|
31256
31298
|
return byCd2.get(cd);
|
|
31257
31299
|
}
|
|
31258
|
-
|
|
31259
|
-
// node_modules/@dforge-core/metadata/dist/traits.js
|
|
31260
31300
|
var traits = [
|
|
31261
31301
|
{
|
|
31262
31302
|
cd: "identity",
|
|
@@ -31331,16 +31371,14 @@ function templateFields(fields, entity) {
|
|
|
31331
31371
|
for (const [name, def] of Object.entries(fields)) {
|
|
31332
31372
|
const key = applyTemplate(name, entity);
|
|
31333
31373
|
const next = { ...def };
|
|
31334
|
-
if (typeof next.description === "string")
|
|
31335
|
-
next.description = applyTemplate(next.description, entity);
|
|
31374
|
+
if (typeof next.description === "string") next.description = applyTemplate(next.description, entity);
|
|
31336
31375
|
out[key] = next;
|
|
31337
31376
|
}
|
|
31338
31377
|
return out;
|
|
31339
31378
|
}
|
|
31340
31379
|
function expandTrait(traitCd, entityName) {
|
|
31341
31380
|
const trait = byCd3.get(traitCd);
|
|
31342
|
-
if (!trait)
|
|
31343
|
-
return {};
|
|
31381
|
+
if (!trait) return {};
|
|
31344
31382
|
const result = {};
|
|
31345
31383
|
for (const included of trait.includes ?? []) {
|
|
31346
31384
|
Object.assign(result, expandTrait(included, entityName));
|
|
@@ -31350,12 +31388,9 @@ function expandTrait(traitCd, entityName) {
|
|
|
31350
31388
|
}
|
|
31351
31389
|
function expandTraits(traitCds, entityName) {
|
|
31352
31390
|
const result = {};
|
|
31353
|
-
for (const cd of traitCds)
|
|
31354
|
-
Object.assign(result, expandTrait(cd, entityName));
|
|
31391
|
+
for (const cd of traitCds) Object.assign(result, expandTrait(cd, entityName));
|
|
31355
31392
|
return result;
|
|
31356
31393
|
}
|
|
31357
|
-
|
|
31358
|
-
// node_modules/@dforge-core/metadata/dist/derive.js
|
|
31359
31394
|
var NUMERIC_TOTAL = 18;
|
|
31360
31395
|
var TEXT_BACKED = /* @__PURE__ */ new Set(["textarea", "markdown", "richtext", "code"]);
|
|
31361
31396
|
var DB_DATATYPE_OVERRIDE = {
|
|
@@ -31364,23 +31399,18 @@ var DB_DATATYPE_OVERRIDE = {
|
|
|
31364
31399
|
};
|
|
31365
31400
|
function deriveDbDatatype(fieldTypeCd, options = {}) {
|
|
31366
31401
|
const ft = getFieldType(fieldTypeCd);
|
|
31367
|
-
if (!ft)
|
|
31368
|
-
|
|
31369
|
-
if (ft.columnType === "R" || ft.columnType === "S")
|
|
31370
|
-
return null;
|
|
31402
|
+
if (!ft) return "varchar";
|
|
31403
|
+
if (ft.columnType === "R" || ft.columnType === "S") return null;
|
|
31371
31404
|
const override = DB_DATATYPE_OVERRIDE[ft.cd];
|
|
31372
|
-
if (override)
|
|
31373
|
-
return override;
|
|
31405
|
+
if (override) return override;
|
|
31374
31406
|
const base = baseToDbDatatype(ft.baseDatatypeCd);
|
|
31375
|
-
if (base === null)
|
|
31376
|
-
return null;
|
|
31407
|
+
if (base === null) return null;
|
|
31377
31408
|
if (ft.baseDatatypeCd === "number") {
|
|
31378
31409
|
const precision = options.precision ?? ft.precision;
|
|
31379
31410
|
return precision != null ? `numeric(${NUMERIC_TOTAL},${precision})` : "numeric";
|
|
31380
31411
|
}
|
|
31381
31412
|
if (ft.baseDatatypeCd === "string") {
|
|
31382
|
-
if (TEXT_BACKED.has(ft.cd))
|
|
31383
|
-
return "text";
|
|
31413
|
+
if (TEXT_BACKED.has(ft.cd)) return "text";
|
|
31384
31414
|
const maxLen = options.maxLen ?? ft.maxLen;
|
|
31385
31415
|
return maxLen != null ? `varchar(${maxLen})` : "varchar";
|
|
31386
31416
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dforge-core/dforge-mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "MCP server for dForge module authoring. Exposes 34 scaffold/patch/validate/pack/install tools and schema resources so AI agents (Claude Code, Cursor, Zed) can create and ship dForge modules.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/iash44/dForge-core",
|
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
"install-skills": "node scripts/install-skills.mjs"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@dforge-core/dforge-cli": "^0.2.
|
|
38
|
-
"@dforge-core/metadata": "^0.0.
|
|
39
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
37
|
+
"@dforge-core/dforge-cli": "^0.2.12",
|
|
38
|
+
"@dforge-core/metadata": "^0.0.13",
|
|
39
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
40
40
|
"zod": "^4.4.3"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
@@ -492,7 +492,20 @@
|
|
|
492
492
|
},
|
|
493
493
|
"o": {
|
|
494
494
|
"type": "string",
|
|
495
|
-
"
|
|
495
|
+
"enum": [
|
|
496
|
+
"=", "eq", "!=", "notEq",
|
|
497
|
+
">", "gr", ">=", "grEq", "<", "less", "<=", "lessEq",
|
|
498
|
+
"eqRef",
|
|
499
|
+
"contains", "!contains", "nContain",
|
|
500
|
+
"start", "!start", "nStart",
|
|
501
|
+
"end", "!end", "nEnd",
|
|
502
|
+
"mask", "!mask", "nMask",
|
|
503
|
+
"null", "empty", "!null", "nEmpty",
|
|
504
|
+
"in", "!in", "nIn",
|
|
505
|
+
"between", "btw", "!between", "nBetween",
|
|
506
|
+
"f", "func"
|
|
507
|
+
],
|
|
508
|
+
"description": "Operator. Closed set — this is exactly what the server's FilterBuilder translates, and an operator outside it is rejected at install. Symbolic forms and their word aliases are equivalent ('=' / 'eq', '!=' / 'notEq', '>' / 'gr', '>=' / 'grEq', '<' / 'less', '<=' / 'lessEq'); a leading '!' negates, with 'n'-prefixed spellings as the alias ('!in' / 'nIn'). Null checks are 'null' / 'empty' and '!null' / 'nEmpty' (NOT 'isNull' / 'isNotNull'); substring matching is 'contains' (NOT 'like'). 'eqRef' compares against another column (v = column code), 'f' / 'func' takes v = {fn: 'isToday' | 'isYesterday' | 'isThisWeek' | 'isThisMonth' | 'isThisYear' | 'isPast' | 'isFuture' | 'isCurrentUser'}."
|
|
496
509
|
},
|
|
497
510
|
"v": {
|
|
498
511
|
"description": "Comparison value. Scalars, arrays (for in/nIn), or parameter references like '@param_code'."
|
|
@@ -506,8 +519,8 @@
|
|
|
506
519
|
"properties": {
|
|
507
520
|
"g": {
|
|
508
521
|
"type": "string",
|
|
509
|
-
"enum": ["and", "or"],
|
|
510
|
-
"description": "Group combinator"
|
|
522
|
+
"enum": ["and", "or", "!and", "nAnd", "!or", "nOr"],
|
|
523
|
+
"description": "Group combinator. '!and' / '!or' negate the whole group ('nAnd' / 'nOr' are the alias spellings). Closed set — an unknown combinator is rejected at install (at query time it would silently fall back to AND)."
|
|
511
524
|
},
|
|
512
525
|
"i": {
|
|
513
526
|
"type": "array",
|
|
@@ -87,7 +87,23 @@
|
|
|
87
87
|
"required": ["c", "o"],
|
|
88
88
|
"properties": {
|
|
89
89
|
"c": { "type": "string" },
|
|
90
|
-
"o": {
|
|
90
|
+
"o": {
|
|
91
|
+
"type": "string",
|
|
92
|
+
"enum": [
|
|
93
|
+
"=", "eq", "!=", "notEq",
|
|
94
|
+
">", "gr", ">=", "grEq", "<", "less", "<=", "lessEq",
|
|
95
|
+
"eqRef",
|
|
96
|
+
"contains", "!contains", "nContain",
|
|
97
|
+
"start", "!start", "nStart",
|
|
98
|
+
"end", "!end", "nEnd",
|
|
99
|
+
"mask", "!mask", "nMask",
|
|
100
|
+
"null", "empty", "!null", "nEmpty",
|
|
101
|
+
"in", "!in", "nIn",
|
|
102
|
+
"between", "btw", "!between", "nBetween",
|
|
103
|
+
"f", "func"
|
|
104
|
+
],
|
|
105
|
+
"description": "Operator — same closed set as data_views.json. Null checks are 'null' / '!null' (NOT 'isNull' / 'isNotNull'); substring matching is 'contains' (NOT 'like'). An operator outside this set is rejected at install; at query time it would be dropped, widening the row filter it was meant to enforce."
|
|
106
|
+
},
|
|
91
107
|
"v": {}
|
|
92
108
|
},
|
|
93
109
|
"additionalProperties": false
|
|
@@ -96,7 +112,7 @@
|
|
|
96
112
|
"type": "object",
|
|
97
113
|
"required": ["g", "i"],
|
|
98
114
|
"properties": {
|
|
99
|
-
"g": { "type": "string", "enum": ["and", "or"] },
|
|
115
|
+
"g": { "type": "string", "enum": ["and", "or", "!and", "nAnd", "!or", "nOr"] },
|
|
100
116
|
"i": {
|
|
101
117
|
"type": "array",
|
|
102
118
|
"items": { "$ref": "#/$defs/filter" }
|
|
@@ -366,7 +366,23 @@
|
|
|
366
366
|
"required": ["c", "o"],
|
|
367
367
|
"properties": {
|
|
368
368
|
"c": { "type": "string" },
|
|
369
|
-
"o": {
|
|
369
|
+
"o": {
|
|
370
|
+
"type": "string",
|
|
371
|
+
"enum": [
|
|
372
|
+
"=", "eq", "!=", "notEq",
|
|
373
|
+
">", "gr", ">=", "grEq", "<", "less", "<=", "lessEq",
|
|
374
|
+
"eqRef",
|
|
375
|
+
"contains", "!contains", "nContain",
|
|
376
|
+
"start", "!start", "nStart",
|
|
377
|
+
"end", "!end", "nEnd",
|
|
378
|
+
"mask", "!mask", "nMask",
|
|
379
|
+
"null", "empty", "!null", "nEmpty",
|
|
380
|
+
"in", "!in", "nIn",
|
|
381
|
+
"between", "btw", "!between", "nBetween",
|
|
382
|
+
"f", "func"
|
|
383
|
+
],
|
|
384
|
+
"description": "Operator — same closed set as data_views.json. Null checks are 'null' / '!null' (NOT 'isNull' / 'isNotNull'); substring matching is 'contains' (NOT 'like'). An operator outside this set is rejected at install; at query time it would be dropped, silently widening the dataset."
|
|
385
|
+
},
|
|
370
386
|
"v": {}
|
|
371
387
|
},
|
|
372
388
|
"additionalProperties": false
|
|
@@ -375,7 +391,7 @@
|
|
|
375
391
|
"type": "object",
|
|
376
392
|
"required": ["g", "i"],
|
|
377
393
|
"properties": {
|
|
378
|
-
"g": { "type": "string", "enum": ["and", "or"] },
|
|
394
|
+
"g": { "type": "string", "enum": ["and", "or", "!and", "nAnd", "!or", "nOr"] },
|
|
379
395
|
"i": {
|
|
380
396
|
"type": "array",
|
|
381
397
|
"items": { "$ref": "#/$defs/filter" }
|
|
@@ -61,7 +61,7 @@ A report has `description`, a `datasets` map, a `layout` object (`{ panels: [...
|
|
|
61
61
|
"query": {
|
|
62
62
|
"entityCd": "opportunity",
|
|
63
63
|
"columns": ["stage", "amount", "close_date", "customer.name"],
|
|
64
|
-
"filter": { "c": "stage", "o": "
|
|
64
|
+
"filter": { "c": "stage", "o": "!=", "v": "Closed Lost" },
|
|
65
65
|
"sort": [ { "c": "close_date", "d": "desc" } ]
|
|
66
66
|
},
|
|
67
67
|
"columnsDef": { "amount": { "label": "Deal Value" }, "customer.name": { "label": "Account" } }
|