@spotto/semantic-query 1.0.70-alpha.34 → 1.0.70-alpha.35
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/dates.d.ts +32 -32
- package/dist/dates.js +104 -104
- package/dist/evaluate.d.ts +28 -28
- package/dist/evaluate.js +576 -545
- package/dist/evaluate.js.map +1 -1
- package/dist/fixtures.d.ts +277 -277
- package/dist/fixtures.js +175 -175
- package/dist/index.d.ts +32 -32
- package/dist/index.js +48 -48
- package/dist/resolve-name.d.ts +9 -9
- package/dist/resolve-name.js +90 -90
- package/dist/suggest.d.ts +12 -12
- package/dist/suggest.js +69 -69
- package/dist/types.d.ts +111 -111
- package/dist/types.js +23 -23
- package/dist/validate.d.ts +33 -33
- package/dist/validate.js +821 -795
- package/dist/validate.js.map +1 -1
- package/package.json +3 -3
package/dist/resolve-name.js
CHANGED
|
@@ -1,91 +1,91 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.resolveName = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* Resolve a NAME to the ids it means — the one place a human word becomes a
|
|
6
|
-
* record reference.
|
|
7
|
-
*
|
|
8
|
-
* The ladder is exact → case-insensitive → unique partial, and it is
|
|
9
|
-
* deliberately conservative at every rung:
|
|
10
|
-
*
|
|
11
|
-
* - An EXACT match wins outright, even if partials also exist. "Bob" must not
|
|
12
|
-
* become ambiguous just because "Bobby" is also on staff.
|
|
13
|
-
* - Several records sharing one exact name resolve to ALL of them. Two people
|
|
14
|
-
* genuinely called "Sam Lee" is a data fact, not a question to ask; both are
|
|
15
|
-
* matched and the answer covers the name as asked.
|
|
16
|
-
* - A partial match is accepted ONLY when it is unique. "Bri" matching both
|
|
17
|
-
* "Brian" and "Brisbane Bob" is refused, with both named, because picking
|
|
18
|
-
* one would answer a different question and look exactly like an answer.
|
|
19
|
-
* - No match is refused with the nearest candidates offered.
|
|
20
|
-
*
|
|
21
|
-
* The failure modes are what make this safe to expose to a model. A regex
|
|
22
|
-
* fallback — which is what the prototype did for assets — would match whatever
|
|
23
|
-
* it happened to match, silently.
|
|
24
|
-
*/
|
|
25
|
-
/**
|
|
26
|
-
* How many candidates an ambiguity message names. Enough to disambiguate a
|
|
27
|
-
* genuine near-miss, few enough that the error is not a directory listing.
|
|
28
|
-
*/
|
|
29
|
-
const AMBIGUITY_EXAMPLES = 3;
|
|
30
|
-
/** Up to three nearest candidates by simple containment, for a suggestion. */
|
|
31
|
-
function nearest(catalogue, want) {
|
|
32
|
-
const needle = want.trim().toLowerCase();
|
|
33
|
-
if (!needle)
|
|
34
|
-
return undefined;
|
|
35
|
-
const hits = catalogue
|
|
36
|
-
.filter((r) => {
|
|
37
|
-
const n = r.name.toLowerCase();
|
|
38
|
-
return n.includes(needle.slice(0, 3)) || needle.includes(n.slice(0, 3));
|
|
39
|
-
})
|
|
40
|
-
.slice(0, 3)
|
|
41
|
-
.map((r) => { var _a; return (_a = r.label) !== null && _a !== void 0 ? _a : r.name; });
|
|
42
|
-
return hits.length ? `Did you mean: ${hits.join(', ')}?` : undefined;
|
|
43
|
-
}
|
|
44
|
-
function resolveName(catalogue, rawName, opts) {
|
|
45
|
-
const want = typeof rawName === 'string' ? rawName.trim() : '';
|
|
46
|
-
if (!want) {
|
|
47
|
-
return {
|
|
48
|
-
ids: [],
|
|
49
|
-
issue: {
|
|
50
|
-
code: 'BAD_SHAPE',
|
|
51
|
-
path: opts.path,
|
|
52
|
-
message: `${opts.noun} needs a name`,
|
|
53
|
-
},
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
const all = catalogue !== null && catalogue !== void 0 ? catalogue : [];
|
|
57
|
-
const lower = want.toLowerCase();
|
|
58
|
-
const exact = all.filter((r) => r.name.toLowerCase() === lower);
|
|
59
|
-
const partial = all.filter((r) => r.name.toLowerCase().includes(lower));
|
|
60
|
-
// Exact wins outright — a longer name that merely CONTAINS this one must not
|
|
61
|
-
// make an exact match ambiguous.
|
|
62
|
-
const hit = exact.length ? exact : partial;
|
|
63
|
-
if (!hit.length) {
|
|
64
|
-
return {
|
|
65
|
-
ids: [],
|
|
66
|
-
issue: Object.assign({ code: 'UNKNOWN_NAME', path: opts.path, message: `No ${opts.noun} called '${want}' in this organisation` }, (nearest(all, want) ? { suggestion: nearest(all, want) } : {})),
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
if (!exact.length && partial.length > 1) {
|
|
70
|
-
// NAMED, NOT ENUMERATED. Listing every match turns this message into a
|
|
71
|
-
// directory: the catalogue is org-scoped but not user-group scoped, and
|
|
72
|
-
// these endpoints are gated on `assets:view` rather than `users:view` — so
|
|
73
|
-
// an unbounded list let a confined field worker recover the whole staff
|
|
74
|
-
// roster one letter at a time. A handful of examples is what makes the
|
|
75
|
-
// error actionable; the rest is only useful to someone fishing.
|
|
76
|
-
const shown = partial.slice(0, AMBIGUITY_EXAMPLES).map((r) => { var _a; return (_a = r.label) !== null && _a !== void 0 ? _a : r.name; });
|
|
77
|
-
const rest = partial.length - shown.length;
|
|
78
|
-
return {
|
|
79
|
-
ids: [],
|
|
80
|
-
issue: {
|
|
81
|
-
code: 'AMBIGUOUS_NAME',
|
|
82
|
-
path: opts.path,
|
|
83
|
-
message: `'${want}' matches ${partial.length} ${opts.noun}s ` +
|
|
84
|
-
`(${shown.join(', ')}${rest > 0 ? `, and ${rest} more` : ''}) — be more specific`,
|
|
85
|
-
},
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
return { ids: [...new Set(hit.map((r) => r.id))] };
|
|
89
|
-
}
|
|
90
|
-
exports.resolveName = resolveName;
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveName = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Resolve a NAME to the ids it means — the one place a human word becomes a
|
|
6
|
+
* record reference.
|
|
7
|
+
*
|
|
8
|
+
* The ladder is exact → case-insensitive → unique partial, and it is
|
|
9
|
+
* deliberately conservative at every rung:
|
|
10
|
+
*
|
|
11
|
+
* - An EXACT match wins outright, even if partials also exist. "Bob" must not
|
|
12
|
+
* become ambiguous just because "Bobby" is also on staff.
|
|
13
|
+
* - Several records sharing one exact name resolve to ALL of them. Two people
|
|
14
|
+
* genuinely called "Sam Lee" is a data fact, not a question to ask; both are
|
|
15
|
+
* matched and the answer covers the name as asked.
|
|
16
|
+
* - A partial match is accepted ONLY when it is unique. "Bri" matching both
|
|
17
|
+
* "Brian" and "Brisbane Bob" is refused, with both named, because picking
|
|
18
|
+
* one would answer a different question and look exactly like an answer.
|
|
19
|
+
* - No match is refused with the nearest candidates offered.
|
|
20
|
+
*
|
|
21
|
+
* The failure modes are what make this safe to expose to a model. A regex
|
|
22
|
+
* fallback — which is what the prototype did for assets — would match whatever
|
|
23
|
+
* it happened to match, silently.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* How many candidates an ambiguity message names. Enough to disambiguate a
|
|
27
|
+
* genuine near-miss, few enough that the error is not a directory listing.
|
|
28
|
+
*/
|
|
29
|
+
const AMBIGUITY_EXAMPLES = 3;
|
|
30
|
+
/** Up to three nearest candidates by simple containment, for a suggestion. */
|
|
31
|
+
function nearest(catalogue, want) {
|
|
32
|
+
const needle = want.trim().toLowerCase();
|
|
33
|
+
if (!needle)
|
|
34
|
+
return undefined;
|
|
35
|
+
const hits = catalogue
|
|
36
|
+
.filter((r) => {
|
|
37
|
+
const n = r.name.toLowerCase();
|
|
38
|
+
return n.includes(needle.slice(0, 3)) || needle.includes(n.slice(0, 3));
|
|
39
|
+
})
|
|
40
|
+
.slice(0, 3)
|
|
41
|
+
.map((r) => { var _a; return (_a = r.label) !== null && _a !== void 0 ? _a : r.name; });
|
|
42
|
+
return hits.length ? `Did you mean: ${hits.join(', ')}?` : undefined;
|
|
43
|
+
}
|
|
44
|
+
function resolveName(catalogue, rawName, opts) {
|
|
45
|
+
const want = typeof rawName === 'string' ? rawName.trim() : '';
|
|
46
|
+
if (!want) {
|
|
47
|
+
return {
|
|
48
|
+
ids: [],
|
|
49
|
+
issue: {
|
|
50
|
+
code: 'BAD_SHAPE',
|
|
51
|
+
path: opts.path,
|
|
52
|
+
message: `${opts.noun} needs a name`,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const all = catalogue !== null && catalogue !== void 0 ? catalogue : [];
|
|
57
|
+
const lower = want.toLowerCase();
|
|
58
|
+
const exact = all.filter((r) => r.name.toLowerCase() === lower);
|
|
59
|
+
const partial = all.filter((r) => r.name.toLowerCase().includes(lower));
|
|
60
|
+
// Exact wins outright — a longer name that merely CONTAINS this one must not
|
|
61
|
+
// make an exact match ambiguous.
|
|
62
|
+
const hit = exact.length ? exact : partial;
|
|
63
|
+
if (!hit.length) {
|
|
64
|
+
return {
|
|
65
|
+
ids: [],
|
|
66
|
+
issue: Object.assign({ code: 'UNKNOWN_NAME', path: opts.path, message: `No ${opts.noun} called '${want}' in this organisation` }, (nearest(all, want) ? { suggestion: nearest(all, want) } : {})),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if (!exact.length && partial.length > 1) {
|
|
70
|
+
// NAMED, NOT ENUMERATED. Listing every match turns this message into a
|
|
71
|
+
// directory: the catalogue is org-scoped but not user-group scoped, and
|
|
72
|
+
// these endpoints are gated on `assets:view` rather than `users:view` — so
|
|
73
|
+
// an unbounded list let a confined field worker recover the whole staff
|
|
74
|
+
// roster one letter at a time. A handful of examples is what makes the
|
|
75
|
+
// error actionable; the rest is only useful to someone fishing.
|
|
76
|
+
const shown = partial.slice(0, AMBIGUITY_EXAMPLES).map((r) => { var _a; return (_a = r.label) !== null && _a !== void 0 ? _a : r.name; });
|
|
77
|
+
const rest = partial.length - shown.length;
|
|
78
|
+
return {
|
|
79
|
+
ids: [],
|
|
80
|
+
issue: {
|
|
81
|
+
code: 'AMBIGUOUS_NAME',
|
|
82
|
+
path: opts.path,
|
|
83
|
+
message: `'${want}' matches ${partial.length} ${opts.noun}s ` +
|
|
84
|
+
`(${shown.join(', ')}${rest > 0 ? `, and ${rest} more` : ''}) — be more specific`,
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return { ids: [...new Set(hit.map((r) => r.id))] };
|
|
89
|
+
}
|
|
90
|
+
exports.resolveName = resolveName;
|
|
91
91
|
//# sourceMappingURL=resolve-name.js.map
|
package/dist/suggest.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Nearest-candidate matching for "did you mean…" suggestions on validation
|
|
3
|
-
* errors — an unknown field or dropdown value is rejected WITH the closest
|
|
4
|
-
* real name, so an honest error is also an actionable one.
|
|
5
|
-
*
|
|
6
|
-
* Substring matching alone is not enough: the canonical case is
|
|
7
|
-
* `SerialNo` → `SerialNumber`, and "serialno" is NOT a substring of
|
|
8
|
-
* "serialnumber" (…n-o… vs …n-u…) nor within any sane edit-distance
|
|
9
|
-
* tolerance (5 edits). The longest-shared-prefix pass is what makes that
|
|
10
|
-
* documented case actually produce a suggestion.
|
|
11
|
-
*/
|
|
12
|
-
export declare function nearest(candidates: string[], wanted: string): string | undefined;
|
|
1
|
+
/**
|
|
2
|
+
* Nearest-candidate matching for "did you mean…" suggestions on validation
|
|
3
|
+
* errors — an unknown field or dropdown value is rejected WITH the closest
|
|
4
|
+
* real name, so an honest error is also an actionable one.
|
|
5
|
+
*
|
|
6
|
+
* Substring matching alone is not enough: the canonical case is
|
|
7
|
+
* `SerialNo` → `SerialNumber`, and "serialno" is NOT a substring of
|
|
8
|
+
* "serialnumber" (…n-o… vs …n-u…) nor within any sane edit-distance
|
|
9
|
+
* tolerance (5 edits). The longest-shared-prefix pass is what makes that
|
|
10
|
+
* documented case actually produce a suggestion.
|
|
11
|
+
*/
|
|
12
|
+
export declare function nearest(candidates: string[], wanted: string): string | undefined;
|
package/dist/suggest.js
CHANGED
|
@@ -1,70 +1,70 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Nearest-candidate matching for "did you mean…" suggestions on validation
|
|
4
|
-
* errors — an unknown field or dropdown value is rejected WITH the closest
|
|
5
|
-
* real name, so an honest error is also an actionable one.
|
|
6
|
-
*
|
|
7
|
-
* Substring matching alone is not enough: the canonical case is
|
|
8
|
-
* `SerialNo` → `SerialNumber`, and "serialno" is NOT a substring of
|
|
9
|
-
* "serialnumber" (…n-o… vs …n-u…) nor within any sane edit-distance
|
|
10
|
-
* tolerance (5 edits). The longest-shared-prefix pass is what makes that
|
|
11
|
-
* documented case actually produce a suggestion.
|
|
12
|
-
*/
|
|
13
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
-
exports.nearest = void 0;
|
|
15
|
-
function editDistance(a, b) {
|
|
16
|
-
const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
17
|
-
const cur = new Array(b.length + 1);
|
|
18
|
-
for (let i = 1; i <= a.length; i++) {
|
|
19
|
-
cur[0] = i;
|
|
20
|
-
for (let j = 1; j <= b.length; j++) {
|
|
21
|
-
cur[j] = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
22
|
-
}
|
|
23
|
-
prev.splice(0, prev.length, ...cur);
|
|
24
|
-
}
|
|
25
|
-
return prev[b.length];
|
|
26
|
-
}
|
|
27
|
-
function nearest(candidates, wanted) {
|
|
28
|
-
const w = wanted.toLowerCase().trim();
|
|
29
|
-
if (!w)
|
|
30
|
-
return undefined;
|
|
31
|
-
const lower = candidates.map((c) => c.toLowerCase());
|
|
32
|
-
const exact = lower.indexOf(w);
|
|
33
|
-
if (exact >= 0)
|
|
34
|
-
return candidates[exact];
|
|
35
|
-
for (let i = 0; i < lower.length; i++) {
|
|
36
|
-
if (lower[i].startsWith(w) || w.startsWith(lower[i]))
|
|
37
|
-
return candidates[i];
|
|
38
|
-
}
|
|
39
|
-
for (let i = 0; i < lower.length; i++) {
|
|
40
|
-
if (lower[i].includes(w) || w.includes(lower[i]))
|
|
41
|
-
return candidates[i];
|
|
42
|
-
}
|
|
43
|
-
let prefixBest;
|
|
44
|
-
let prefixLen = 0;
|
|
45
|
-
for (let i = 0; i < lower.length; i++) {
|
|
46
|
-
let n = 0;
|
|
47
|
-
while (n < w.length && n < lower[i].length && w[n] === lower[i][n])
|
|
48
|
-
n++;
|
|
49
|
-
if (n > prefixLen) {
|
|
50
|
-
prefixLen = n;
|
|
51
|
-
prefixBest = candidates[i];
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
if (prefixBest && prefixLen >= Math.max(4, Math.ceil(w.length / 2))) {
|
|
55
|
-
return prefixBest;
|
|
56
|
-
}
|
|
57
|
-
let best;
|
|
58
|
-
let bestScore = Infinity;
|
|
59
|
-
const tolerance = Math.max(2, Math.floor(w.length / 3));
|
|
60
|
-
for (let i = 0; i < lower.length; i++) {
|
|
61
|
-
const d = editDistance(w, lower[i]);
|
|
62
|
-
if (d < bestScore) {
|
|
63
|
-
bestScore = d;
|
|
64
|
-
best = candidates[i];
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return bestScore <= tolerance ? best : undefined;
|
|
68
|
-
}
|
|
69
|
-
exports.nearest = nearest;
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Nearest-candidate matching for "did you mean…" suggestions on validation
|
|
4
|
+
* errors — an unknown field or dropdown value is rejected WITH the closest
|
|
5
|
+
* real name, so an honest error is also an actionable one.
|
|
6
|
+
*
|
|
7
|
+
* Substring matching alone is not enough: the canonical case is
|
|
8
|
+
* `SerialNo` → `SerialNumber`, and "serialno" is NOT a substring of
|
|
9
|
+
* "serialnumber" (…n-o… vs …n-u…) nor within any sane edit-distance
|
|
10
|
+
* tolerance (5 edits). The longest-shared-prefix pass is what makes that
|
|
11
|
+
* documented case actually produce a suggestion.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.nearest = void 0;
|
|
15
|
+
function editDistance(a, b) {
|
|
16
|
+
const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
17
|
+
const cur = new Array(b.length + 1);
|
|
18
|
+
for (let i = 1; i <= a.length; i++) {
|
|
19
|
+
cur[0] = i;
|
|
20
|
+
for (let j = 1; j <= b.length; j++) {
|
|
21
|
+
cur[j] = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
22
|
+
}
|
|
23
|
+
prev.splice(0, prev.length, ...cur);
|
|
24
|
+
}
|
|
25
|
+
return prev[b.length];
|
|
26
|
+
}
|
|
27
|
+
function nearest(candidates, wanted) {
|
|
28
|
+
const w = wanted.toLowerCase().trim();
|
|
29
|
+
if (!w)
|
|
30
|
+
return undefined;
|
|
31
|
+
const lower = candidates.map((c) => c.toLowerCase());
|
|
32
|
+
const exact = lower.indexOf(w);
|
|
33
|
+
if (exact >= 0)
|
|
34
|
+
return candidates[exact];
|
|
35
|
+
for (let i = 0; i < lower.length; i++) {
|
|
36
|
+
if (lower[i].startsWith(w) || w.startsWith(lower[i]))
|
|
37
|
+
return candidates[i];
|
|
38
|
+
}
|
|
39
|
+
for (let i = 0; i < lower.length; i++) {
|
|
40
|
+
if (lower[i].includes(w) || w.includes(lower[i]))
|
|
41
|
+
return candidates[i];
|
|
42
|
+
}
|
|
43
|
+
let prefixBest;
|
|
44
|
+
let prefixLen = 0;
|
|
45
|
+
for (let i = 0; i < lower.length; i++) {
|
|
46
|
+
let n = 0;
|
|
47
|
+
while (n < w.length && n < lower[i].length && w[n] === lower[i][n])
|
|
48
|
+
n++;
|
|
49
|
+
if (n > prefixLen) {
|
|
50
|
+
prefixLen = n;
|
|
51
|
+
prefixBest = candidates[i];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (prefixBest && prefixLen >= Math.max(4, Math.ceil(w.length / 2))) {
|
|
55
|
+
return prefixBest;
|
|
56
|
+
}
|
|
57
|
+
let best;
|
|
58
|
+
let bestScore = Infinity;
|
|
59
|
+
const tolerance = Math.max(2, Math.floor(w.length / 3));
|
|
60
|
+
for (let i = 0; i < lower.length; i++) {
|
|
61
|
+
const d = editDistance(w, lower[i]);
|
|
62
|
+
if (d < bestScore) {
|
|
63
|
+
bestScore = d;
|
|
64
|
+
best = candidates[i];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return bestScore <= tolerance ? best : undefined;
|
|
68
|
+
}
|
|
69
|
+
exports.nearest = nearest;
|
|
70
70
|
//# sourceMappingURL=suggest.js.map
|
package/dist/types.d.ts
CHANGED
|
@@ -1,111 +1,111 @@
|
|
|
1
|
-
import { FieldDataType, SemanticQueryEntityType } from '@spotto/contract';
|
|
2
|
-
/**
|
|
3
|
-
* What an organisation's catalogue looks like to this package.
|
|
4
|
-
*
|
|
5
|
-
* These are NOT wire shapes — the wire contract lives in `@spotto/contract`.
|
|
6
|
-
* This is the vocabulary a query is resolved AGAINST: which custom fields
|
|
7
|
-
* exist, what type each one is, and (for the evaluator) the type documents
|
|
8
|
-
* whose values an asset inherits.
|
|
9
|
-
*/
|
|
10
|
-
export interface SemanticFieldDef {
|
|
11
|
-
/** The field's id — custom-field matching is by `_id`, not name. */
|
|
12
|
-
id: string;
|
|
13
|
-
name: string;
|
|
14
|
-
dataType: FieldDataType;
|
|
15
|
-
/** Dropdown options — an eq value must match one exactly or the query silently returns nothing. */
|
|
16
|
-
options?: string[];
|
|
17
|
-
}
|
|
18
|
-
/** A resolved type-level field value, as materialised in `typeFieldValuesAll`. */
|
|
19
|
-
export interface SemanticTypeFieldValue {
|
|
20
|
-
_id?: string;
|
|
21
|
-
name?: string;
|
|
22
|
-
valueString?: string;
|
|
23
|
-
valueInteger?: number;
|
|
24
|
-
valueDecimal?: number;
|
|
25
|
-
valueBoolean?: boolean;
|
|
26
|
-
valueDate?: number;
|
|
27
|
-
}
|
|
28
|
-
export interface SemanticTypeDef {
|
|
29
|
-
id: string;
|
|
30
|
-
/** Pipe-delimited, pipe-terminated. Root type is "". */
|
|
31
|
-
path: string;
|
|
32
|
-
/** Inheritance already resolved by the platform. */
|
|
33
|
-
typeFieldValuesAll: SemanticTypeFieldValue[];
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* A NAMED record a condition can point at by name instead of by id.
|
|
37
|
-
*
|
|
38
|
-
* This is how the event vocabulary escapes the producer gap without repeating
|
|
39
|
-
* its mistake. An id cannot be asked of a model — it would be invented, and an
|
|
40
|
-
* invented id runs and matches nothing. A NAME can be asked for, but only if
|
|
41
|
-
* it is resolved against real records and an unresolvable or ambiguous one
|
|
42
|
-
* FAILS: the moment resolution silently picks a candidate, or falls back to a
|
|
43
|
-
* regex, it becomes the same silent-wrong-rows problem wearing a friendlier
|
|
44
|
-
* face.
|
|
45
|
-
*
|
|
46
|
-
* Safe here and not for assets purely because of cardinality. Users, readers
|
|
47
|
-
* and actions are small named catalogues that fit in a prompt and resolve
|
|
48
|
-
* unambiguously; assets number in the thousands and share name stems
|
|
49
|
-
* ("BC-A1" is a prefix of "BC-A10").
|
|
50
|
-
*/
|
|
51
|
-
export interface SemanticNamedRef {
|
|
52
|
-
id: string;
|
|
53
|
-
name: string;
|
|
54
|
-
/** Shown to a user in a suggestion when the name does not resolve. */
|
|
55
|
-
label?: string;
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* What VALIDATION needs: field definitions, plus the named catalogues the
|
|
59
|
-
* event vocabulary resolves against. Empty catalogues are legitimate — a
|
|
60
|
-
* caller that never validates event conditions supplies none, and one that
|
|
61
|
-
* does gets an honest "no one called that" rather than a match on nothing.
|
|
62
|
-
*/
|
|
63
|
-
export interface SemanticFieldCatalogue {
|
|
64
|
-
fields: SemanticFieldDef[];
|
|
65
|
-
/** Organisation users — resolves `{type:'user', name}`. */
|
|
66
|
-
users?: SemanticNamedRef[];
|
|
67
|
-
/** Readers — resolves `{type:'reader', name}`. */
|
|
68
|
-
readers?: SemanticNamedRef[];
|
|
69
|
-
/** Form actions — resolves `{type:'action', name}`. */
|
|
70
|
-
actions?: SemanticNamedRef[];
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* What EVALUATION needs: the catalogue plus types, because matching a
|
|
74
|
-
* type-level field value in memory means walking the asset's type document.
|
|
75
|
-
* (The Mongo compiler gets the same values through a `$lookup` instead.)
|
|
76
|
-
*/
|
|
77
|
-
export interface SemanticAccountSchema extends SemanticFieldCatalogue {
|
|
78
|
-
types: SemanticTypeDef[];
|
|
79
|
-
}
|
|
80
|
-
export interface SemanticValidationIssue {
|
|
81
|
-
path: string;
|
|
82
|
-
code: string;
|
|
83
|
-
message: string;
|
|
84
|
-
suggestion?: string;
|
|
85
|
-
}
|
|
86
|
-
export declare class SemanticQueryValidationError extends Error {
|
|
87
|
-
issues: SemanticValidationIssue[];
|
|
88
|
-
constructor(issues: SemanticValidationIssue[]);
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Throw one validation issue — fail-fast, this package's contract (the issues
|
|
92
|
-
* array shape exists for the wire; internally the FIRST problem is the
|
|
93
|
-
* answer). One helper beside the error class it throws, so issue construction
|
|
94
|
-
* cannot drift between layers.
|
|
95
|
-
*/
|
|
96
|
-
export declare function failSemanticValidation(path: string, code: string, message: string, suggestion?: string): never;
|
|
97
|
-
export interface SemanticCompileOptions {
|
|
98
|
-
/**
|
|
99
|
-
* Explicit — never sampled deeper than the entry point, so a run is
|
|
100
|
-
* reproducible: the same query, catalogue, `now` and timezone always give
|
|
101
|
-
* the same answer, whoever is asking.
|
|
102
|
-
*/
|
|
103
|
-
now?: Date;
|
|
104
|
-
/** IANA zone for resolving relative dates. */
|
|
105
|
-
timeZone?: string;
|
|
106
|
-
/**
|
|
107
|
-
* Which entity's vocabulary applies. Defaults to `asset` — the only entity
|
|
108
|
-
* that executes today, and what every existing caller means.
|
|
109
|
-
*/
|
|
110
|
-
entityType?: SemanticQueryEntityType;
|
|
111
|
-
}
|
|
1
|
+
import { FieldDataType, SemanticQueryEntityType } from '@spotto/contract';
|
|
2
|
+
/**
|
|
3
|
+
* What an organisation's catalogue looks like to this package.
|
|
4
|
+
*
|
|
5
|
+
* These are NOT wire shapes — the wire contract lives in `@spotto/contract`.
|
|
6
|
+
* This is the vocabulary a query is resolved AGAINST: which custom fields
|
|
7
|
+
* exist, what type each one is, and (for the evaluator) the type documents
|
|
8
|
+
* whose values an asset inherits.
|
|
9
|
+
*/
|
|
10
|
+
export interface SemanticFieldDef {
|
|
11
|
+
/** The field's id — custom-field matching is by `_id`, not name. */
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
dataType: FieldDataType;
|
|
15
|
+
/** Dropdown options — an eq value must match one exactly or the query silently returns nothing. */
|
|
16
|
+
options?: string[];
|
|
17
|
+
}
|
|
18
|
+
/** A resolved type-level field value, as materialised in `typeFieldValuesAll`. */
|
|
19
|
+
export interface SemanticTypeFieldValue {
|
|
20
|
+
_id?: string;
|
|
21
|
+
name?: string;
|
|
22
|
+
valueString?: string;
|
|
23
|
+
valueInteger?: number;
|
|
24
|
+
valueDecimal?: number;
|
|
25
|
+
valueBoolean?: boolean;
|
|
26
|
+
valueDate?: number;
|
|
27
|
+
}
|
|
28
|
+
export interface SemanticTypeDef {
|
|
29
|
+
id: string;
|
|
30
|
+
/** Pipe-delimited, pipe-terminated. Root type is "". */
|
|
31
|
+
path: string;
|
|
32
|
+
/** Inheritance already resolved by the platform. */
|
|
33
|
+
typeFieldValuesAll: SemanticTypeFieldValue[];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* A NAMED record a condition can point at by name instead of by id.
|
|
37
|
+
*
|
|
38
|
+
* This is how the event vocabulary escapes the producer gap without repeating
|
|
39
|
+
* its mistake. An id cannot be asked of a model — it would be invented, and an
|
|
40
|
+
* invented id runs and matches nothing. A NAME can be asked for, but only if
|
|
41
|
+
* it is resolved against real records and an unresolvable or ambiguous one
|
|
42
|
+
* FAILS: the moment resolution silently picks a candidate, or falls back to a
|
|
43
|
+
* regex, it becomes the same silent-wrong-rows problem wearing a friendlier
|
|
44
|
+
* face.
|
|
45
|
+
*
|
|
46
|
+
* Safe here and not for assets purely because of cardinality. Users, readers
|
|
47
|
+
* and actions are small named catalogues that fit in a prompt and resolve
|
|
48
|
+
* unambiguously; assets number in the thousands and share name stems
|
|
49
|
+
* ("BC-A1" is a prefix of "BC-A10").
|
|
50
|
+
*/
|
|
51
|
+
export interface SemanticNamedRef {
|
|
52
|
+
id: string;
|
|
53
|
+
name: string;
|
|
54
|
+
/** Shown to a user in a suggestion when the name does not resolve. */
|
|
55
|
+
label?: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* What VALIDATION needs: field definitions, plus the named catalogues the
|
|
59
|
+
* event vocabulary resolves against. Empty catalogues are legitimate — a
|
|
60
|
+
* caller that never validates event conditions supplies none, and one that
|
|
61
|
+
* does gets an honest "no one called that" rather than a match on nothing.
|
|
62
|
+
*/
|
|
63
|
+
export interface SemanticFieldCatalogue {
|
|
64
|
+
fields: SemanticFieldDef[];
|
|
65
|
+
/** Organisation users — resolves `{type:'user', name}`. */
|
|
66
|
+
users?: SemanticNamedRef[];
|
|
67
|
+
/** Readers — resolves `{type:'reader', name}`. */
|
|
68
|
+
readers?: SemanticNamedRef[];
|
|
69
|
+
/** Form actions — resolves `{type:'action', name}`. */
|
|
70
|
+
actions?: SemanticNamedRef[];
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* What EVALUATION needs: the catalogue plus types, because matching a
|
|
74
|
+
* type-level field value in memory means walking the asset's type document.
|
|
75
|
+
* (The Mongo compiler gets the same values through a `$lookup` instead.)
|
|
76
|
+
*/
|
|
77
|
+
export interface SemanticAccountSchema extends SemanticFieldCatalogue {
|
|
78
|
+
types: SemanticTypeDef[];
|
|
79
|
+
}
|
|
80
|
+
export interface SemanticValidationIssue {
|
|
81
|
+
path: string;
|
|
82
|
+
code: string;
|
|
83
|
+
message: string;
|
|
84
|
+
suggestion?: string;
|
|
85
|
+
}
|
|
86
|
+
export declare class SemanticQueryValidationError extends Error {
|
|
87
|
+
issues: SemanticValidationIssue[];
|
|
88
|
+
constructor(issues: SemanticValidationIssue[]);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Throw one validation issue — fail-fast, this package's contract (the issues
|
|
92
|
+
* array shape exists for the wire; internally the FIRST problem is the
|
|
93
|
+
* answer). One helper beside the error class it throws, so issue construction
|
|
94
|
+
* cannot drift between layers.
|
|
95
|
+
*/
|
|
96
|
+
export declare function failSemanticValidation(path: string, code: string, message: string, suggestion?: string): never;
|
|
97
|
+
export interface SemanticCompileOptions {
|
|
98
|
+
/**
|
|
99
|
+
* Explicit — never sampled deeper than the entry point, so a run is
|
|
100
|
+
* reproducible: the same query, catalogue, `now` and timezone always give
|
|
101
|
+
* the same answer, whoever is asking.
|
|
102
|
+
*/
|
|
103
|
+
now?: Date;
|
|
104
|
+
/** IANA zone for resolving relative dates. */
|
|
105
|
+
timeZone?: string;
|
|
106
|
+
/**
|
|
107
|
+
* Which entity's vocabulary applies. Defaults to `asset` — the only entity
|
|
108
|
+
* that executes today, and what every existing caller means.
|
|
109
|
+
*/
|
|
110
|
+
entityType?: SemanticQueryEntityType;
|
|
111
|
+
}
|
package/dist/types.js
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.failSemanticValidation = exports.SemanticQueryValidationError = void 0;
|
|
4
|
-
class SemanticQueryValidationError extends Error {
|
|
5
|
-
constructor(issues) {
|
|
6
|
-
super(issues.map((i) => i.message).join('; '));
|
|
7
|
-
this.issues = issues;
|
|
8
|
-
this.name = 'SemanticQueryValidationError';
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
exports.SemanticQueryValidationError = SemanticQueryValidationError;
|
|
12
|
-
/**
|
|
13
|
-
* Throw one validation issue — fail-fast, this package's contract (the issues
|
|
14
|
-
* array shape exists for the wire; internally the FIRST problem is the
|
|
15
|
-
* answer). One helper beside the error class it throws, so issue construction
|
|
16
|
-
* cannot drift between layers.
|
|
17
|
-
*/
|
|
18
|
-
function failSemanticValidation(path, code, message, suggestion) {
|
|
19
|
-
throw new SemanticQueryValidationError([
|
|
20
|
-
suggestion ? { path, code, message, suggestion } : { path, code, message },
|
|
21
|
-
]);
|
|
22
|
-
}
|
|
23
|
-
exports.failSemanticValidation = failSemanticValidation;
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.failSemanticValidation = exports.SemanticQueryValidationError = void 0;
|
|
4
|
+
class SemanticQueryValidationError extends Error {
|
|
5
|
+
constructor(issues) {
|
|
6
|
+
super(issues.map((i) => i.message).join('; '));
|
|
7
|
+
this.issues = issues;
|
|
8
|
+
this.name = 'SemanticQueryValidationError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
exports.SemanticQueryValidationError = SemanticQueryValidationError;
|
|
12
|
+
/**
|
|
13
|
+
* Throw one validation issue — fail-fast, this package's contract (the issues
|
|
14
|
+
* array shape exists for the wire; internally the FIRST problem is the
|
|
15
|
+
* answer). One helper beside the error class it throws, so issue construction
|
|
16
|
+
* cannot drift between layers.
|
|
17
|
+
*/
|
|
18
|
+
function failSemanticValidation(path, code, message, suggestion) {
|
|
19
|
+
throw new SemanticQueryValidationError([
|
|
20
|
+
suggestion ? { path, code, message, suggestion } : { path, code, message },
|
|
21
|
+
]);
|
|
22
|
+
}
|
|
23
|
+
exports.failSemanticValidation = failSemanticValidation;
|
|
24
24
|
//# sourceMappingURL=types.js.map
|