@spotto/semantic-query 1.0.70-alpha.18

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.
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FIXTURE_OPTS = exports.FIXTURE_TZ = exports.FIXTURE_NOW = exports.FIXTURE_CORPUS = exports.FIXTURE_NULLS = exports.FIXTURE_AMBULANCE = exports.FIXTURE_FRIDGE = exports.FIXTURE_EPOC = exports.FIXTURE_SCHEMA = exports.FIXTURE_KIT_PARENT = exports.FIXTURE_MANIFEST = exports.FIXTURE_TYPES = exports.FIXTURE_FIELDS = void 0;
4
+ /**
5
+ * The conformance corpus.
6
+ *
7
+ * Shipped as part of the package, on its own entry point
8
+ * (`@spotto/semantic-query/dist/fixtures`), because it is not test scaffolding
9
+ * — it is the shared statement of the awkward cases every back-end must
10
+ * classify identically. The server's Mongo compiler runs against it, and so
11
+ * does anything else that ever claims to execute this language.
12
+ *
13
+ * Deliberately small and hand-readable. Each document exists for a reason
14
+ * stated beside it; a corpus nobody can hold in their head stops being
15
+ * evidence and starts being decoration.
16
+ */
17
+ exports.FIXTURE_FIELDS = {
18
+ SERIAL: 'aaaaaaaaaaaaaaaaaaaaaa01',
19
+ MANUFACTURER: 'aaaaaaaaaaaaaaaaaaaaaa02',
20
+ OWNERSHIP: 'aaaaaaaaaaaaaaaaaaaaaa03',
21
+ PURCHASE_DATE: 'aaaaaaaaaaaaaaaaaaaaaa04',
22
+ DECOMMISSIONED: 'aaaaaaaaaaaaaaaaaaaaaa05',
23
+ CALIBRATED_AT: 'aaaaaaaaaaaaaaaaaaaaaa06',
24
+ };
25
+ exports.FIXTURE_TYPES = {
26
+ EPOC: 'bbbbbbbbbbbbbbbbbbbbbb01',
27
+ FRIDGE: 'bbbbbbbbbbbbbbbbbbbbbb02',
28
+ AMBULANCE: 'bbbbbbbbbbbbbbbbbbbbbb03',
29
+ };
30
+ exports.FIXTURE_MANIFEST = 'cccccccccccccccccccccc01';
31
+ exports.FIXTURE_KIT_PARENT = 'dddddddddddddddddddddd01';
32
+ /**
33
+ * One organisation's catalogue: every supported data type, a dropdown with
34
+ * options, a type-level value (Manufacturer, which no asset carries directly),
35
+ * and a DATETIME field that must be refused rather than matched.
36
+ */
37
+ exports.FIXTURE_SCHEMA = {
38
+ fields: [
39
+ { id: exports.FIXTURE_FIELDS.SERIAL, name: 'SerialNumber', dataType: 'STRING' },
40
+ { id: exports.FIXTURE_FIELDS.MANUFACTURER, name: 'Manufacturer', dataType: 'STRING' },
41
+ {
42
+ id: exports.FIXTURE_FIELDS.OWNERSHIP,
43
+ name: 'Ownership',
44
+ dataType: 'STRING',
45
+ options: ['QH', 'LFA'],
46
+ },
47
+ { id: exports.FIXTURE_FIELDS.PURCHASE_DATE, name: 'PurchaseDate', dataType: 'DATE' },
48
+ { id: exports.FIXTURE_FIELDS.DECOMMISSIONED, name: 'Decommissioned', dataType: 'BOOLEAN' },
49
+ { id: exports.FIXTURE_FIELDS.CALIBRATED_AT, name: 'CalibratedAt', dataType: 'DATETIME' },
50
+ ],
51
+ types: [
52
+ {
53
+ id: exports.FIXTURE_TYPES.EPOC,
54
+ path: 'Blood Analysers|EPOC|',
55
+ typeFieldValuesAll: [
56
+ {
57
+ _id: exports.FIXTURE_FIELDS.MANUFACTURER,
58
+ name: 'Manufacturer',
59
+ valueString: 'ABBOTT DIAGNOSTICS',
60
+ },
61
+ ],
62
+ },
63
+ {
64
+ id: exports.FIXTURE_TYPES.FRIDGE,
65
+ path: 'Blood Refrigerators|',
66
+ typeFieldValuesAll: [
67
+ {
68
+ _id: exports.FIXTURE_FIELDS.MANUFACTURER,
69
+ name: 'Manufacturer',
70
+ valueString: 'Advanced Health Technology',
71
+ },
72
+ ],
73
+ },
74
+ { id: exports.FIXTURE_TYPES.AMBULANCE, path: 'Vehicles|Ambulance|', typeFieldValuesAll: [] },
75
+ ],
76
+ };
77
+ /** Carries a value for everything; its Manufacturer comes from its TYPE, not itself. */
78
+ exports.FIXTURE_EPOC = {
79
+ _id: '650000000000000000000001',
80
+ name: 'EPOC Analyser 7',
81
+ typeId: exports.FIXTURE_TYPES.EPOC,
82
+ typePath: 'Blood Analysers|EPOC|',
83
+ fieldValues: [
84
+ { _id: exports.FIXTURE_FIELDS.SERIAL, name: 'SerialNumber', valueString: 'ABC123' },
85
+ { _id: exports.FIXTURE_FIELDS.OWNERSHIP, name: 'Ownership', valueString: 'QH' },
86
+ { _id: exports.FIXTURE_FIELDS.PURCHASE_DATE, name: 'PurchaseDate', valueDate: 20260815 },
87
+ ],
88
+ state: {
89
+ locationName: 'Brisbane|Lab|',
90
+ locationStatus: 'HOME',
91
+ lastSeen: 1780000000000,
92
+ supportsWith: true,
93
+ locationWithId: exports.FIXTURE_KIT_PARENT,
94
+ },
95
+ readiness: { ready: 2, readyIssues: [{ reason: 'Overdue' }] },
96
+ tagIds: ['E28011700000021A2C5B0001'],
97
+ labels: [{ name: 'Loan pool' }],
98
+ manifestIds: [exports.FIXTURE_MANIFEST],
99
+ lastUpdated: 1700000000000,
100
+ lastChanged: 1780000000000,
101
+ groupMember: true,
102
+ };
103
+ /** The sparse asset: no state, no readiness, no tags, never updated. */
104
+ exports.FIXTURE_FRIDGE = {
105
+ _id: '650000000000000000000002',
106
+ name: 'Blood Fridge 2',
107
+ typeId: exports.FIXTURE_TYPES.FRIDGE,
108
+ typePath: 'Blood Refrigerators|',
109
+ fieldValues: [],
110
+ tagIds: [],
111
+ };
112
+ /** A kit, dispatched, whose members fall short of its type's template. */
113
+ exports.FIXTURE_AMBULANCE = {
114
+ _id: '650000000000000000000003',
115
+ name: 'Ambulance BC-A1',
116
+ typeId: exports.FIXTURE_TYPES.AMBULANCE,
117
+ typePath: 'Vehicles|Ambulance|',
118
+ fieldValues: [
119
+ { _id: exports.FIXTURE_FIELDS.OWNERSHIP, name: 'Ownership', valueString: 'LFA' },
120
+ { _id: exports.FIXTURE_FIELDS.DECOMMISSIONED, name: 'Decommissioned', valueBoolean: true },
121
+ ],
122
+ state: { locationName: 'Brisbane|Depot|', locationStatus: 'DISPATCH' },
123
+ readiness: { ready: 1, readyIssues: [] },
124
+ tagIds: [],
125
+ lastUpdated: 1780000000000,
126
+ typeKit: true,
127
+ groupSatisfiesKit: false,
128
+ };
129
+ /**
130
+ * The null-slot document. Every field it carries is present-but-null, which is
131
+ * a different shape from absent — and the axis where two back-ends most easily
132
+ * disagree (a database's "does this key exist" versus a language's "is this
133
+ * empty"). Every condition family must classify it identically on both sides.
134
+ */
135
+ exports.FIXTURE_NULLS = {
136
+ _id: '650000000000000000000004',
137
+ name: 'Null Slots',
138
+ typeId: exports.FIXTURE_TYPES.AMBULANCE,
139
+ typePath: 'Vehicles|Ambulance|',
140
+ fieldValues: [{ _id: exports.FIXTURE_FIELDS.OWNERSHIP, name: 'Ownership', valueString: null }],
141
+ state: { locationWithId: null, locationName: 'Brisbane|Depot|' },
142
+ tagIds: null,
143
+ lastUpdated: null,
144
+ telemetry: { battery: null },
145
+ };
146
+ exports.FIXTURE_CORPUS = [
147
+ exports.FIXTURE_EPOC,
148
+ exports.FIXTURE_FRIDGE,
149
+ exports.FIXTURE_AMBULANCE,
150
+ exports.FIXTURE_NULLS,
151
+ ];
152
+ /** Frozen clock for the corpus — 2026-07-27 09:00 in Brisbane. */
153
+ exports.FIXTURE_NOW = new Date('2026-07-26T23:00:00Z');
154
+ exports.FIXTURE_TZ = 'Australia/Brisbane';
155
+ exports.FIXTURE_OPTS = { now: exports.FIXTURE_NOW, timeZone: exports.FIXTURE_TZ };
156
+ //# sourceMappingURL=fixtures.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixtures.js","sourceRoot":"","sources":["../src/fixtures.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * `@spotto/semantic-query` — the Spotto semantic query language.
3
+ *
4
+ * ONE statement of what a query means, shared by everything that reads or
5
+ * writes one: the client's filter UI and offline evaluation, the API's read
6
+ * path, and the AI layer that generates queries.
7
+ *
8
+ * The split this package exists to enforce:
9
+ *
10
+ * validate() every rule of the language — the gate both back-ends stand behind
11
+ * dates one resolution of "last 30 days", for everyone
12
+ * evaluate() a query -> yes/no for a document, in memory
13
+ * ---------------------------------------------------------------
14
+ * compile() a query -> a MongoDB $match [server-side only]
15
+ *
16
+ * Only the last line is unshareable, because only it is database-specific.
17
+ *
18
+ * Be clear about what that does and does not buy. The rules, the date engine
19
+ * and the field lookup exist once, so no consumer can hold a different opinion
20
+ * about them — before this, they lived inside the Mongo compiler, and a client
21
+ * could not so much as check a query without it. What remains genuinely
22
+ * duplicated is the last step: turning a checked query into a `$match` versus
23
+ * answering it in memory. Those are two interpretations of the same
24
+ * instruction and nothing here forces them to agree — only running both over
25
+ * the same documents can do that, which is what the shipped fixtures are for.
26
+ */
27
+ export * from './types';
28
+ export * from './dates';
29
+ export * from './suggest';
30
+ export * from './validate';
31
+ export * from './evaluate';
package/dist/index.js ADDED
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ /**
18
+ * `@spotto/semantic-query` — the Spotto semantic query language.
19
+ *
20
+ * ONE statement of what a query means, shared by everything that reads or
21
+ * writes one: the client's filter UI and offline evaluation, the API's read
22
+ * path, and the AI layer that generates queries.
23
+ *
24
+ * The split this package exists to enforce:
25
+ *
26
+ * validate() every rule of the language — the gate both back-ends stand behind
27
+ * dates one resolution of "last 30 days", for everyone
28
+ * evaluate() a query -> yes/no for a document, in memory
29
+ * ---------------------------------------------------------------
30
+ * compile() a query -> a MongoDB $match [server-side only]
31
+ *
32
+ * Only the last line is unshareable, because only it is database-specific.
33
+ *
34
+ * Be clear about what that does and does not buy. The rules, the date engine
35
+ * and the field lookup exist once, so no consumer can hold a different opinion
36
+ * about them — before this, they lived inside the Mongo compiler, and a client
37
+ * could not so much as check a query without it. What remains genuinely
38
+ * duplicated is the last step: turning a checked query into a `$match` versus
39
+ * answering it in memory. Those are two interpretations of the same
40
+ * instruction and nothing here forces them to agree — only running both over
41
+ * the same documents can do that, which is what the shipped fixtures are for.
42
+ */
43
+ __exportStar(require("./types"), exports);
44
+ __exportStar(require("./dates"), exports);
45
+ __exportStar(require("./suggest"), exports);
46
+ __exportStar(require("./validate"), exports);
47
+ __exportStar(require("./evaluate"), exports);
48
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,0CAAuB;AACvB,0CAAuB;AACvB,4CAAyB;AACzB,6CAA0B;AAC1B,6CAA0B"}
@@ -0,0 +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;
@@ -0,0 +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;
70
+ //# sourceMappingURL=suggest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suggest.js","sourceRoot":"","sources":["../src/suggest.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;;;AAEH,SAAS,YAAY,CAAC,CAAS,EAAE,CAAS;IACxC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAA;IAC9D,MAAM,GAAG,GAAG,IAAI,KAAK,CAAS,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IAC3C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QAClC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;QACV,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;YAClC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CACf,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,EACX,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,EACd,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAC9C,CAAA;SACF;QACD,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAA;KACpC;IACD,OAAO,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAA;AACvB,CAAC;AAED,SAAgB,OAAO,CAAC,UAAoB,EAAE,MAAc;IAC1D,MAAM,CAAC,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAA;IACrC,IAAI,CAAC,CAAC;QAAE,OAAO,SAAS,CAAA;IACxB,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAA;IAEpD,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;IAC9B,IAAI,KAAK,IAAI,CAAC;QAAE,OAAO,UAAU,CAAC,KAAK,CAAC,CAAA;IAExC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QACrC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAAE,OAAO,UAAU,CAAC,CAAC,CAAC,CAAA;KAC3E;IACD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QACrC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAAE,OAAO,UAAU,CAAC,CAAC,CAAC,CAAA;KACvE;IAED,IAAI,UAA8B,CAAA;IAClC,IAAI,SAAS,GAAG,CAAC,CAAA;IACjB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QACrC,IAAI,CAAC,GAAG,CAAC,CAAA;QACT,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAAE,CAAC,EAAE,CAAA;QACvE,IAAI,CAAC,GAAG,SAAS,EAAE;YACjB,SAAS,GAAG,CAAC,CAAA;YACb,UAAU,GAAG,UAAU,CAAC,CAAC,CAAC,CAAA;SAC3B;KACF;IACD,IAAI,UAAU,IAAI,SAAS,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,EAAE;QACnE,OAAO,UAAU,CAAA;KAClB;IAED,IAAI,IAAwB,CAAA;IAC5B,IAAI,SAAS,GAAG,QAAQ,CAAA;IACxB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAA;IACvD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QACrC,MAAM,CAAC,GAAG,YAAY,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;QACnC,IAAI,CAAC,GAAG,SAAS,EAAE;YACjB,SAAS,GAAG,CAAC,CAAA;YACb,IAAI,GAAG,UAAU,CAAC,CAAC,CAAC,CAAA;SACrB;KACF;IACD,OAAO,SAAS,IAAI,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAA;AAClD,CAAC;AAxCD,0BAwCC"}
@@ -0,0 +1,76 @@
1
+ import { FieldDataType } 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
+ * What VALIDATION needs: field definitions only. The Mongo compiler needs no
37
+ * more than this either — type-level values reach it through a `$lookup`.
38
+ */
39
+ export interface SemanticFieldCatalogue {
40
+ fields: SemanticFieldDef[];
41
+ }
42
+ /**
43
+ * What EVALUATION needs: the catalogue plus types, because matching a
44
+ * type-level field value in memory means walking the asset's type document.
45
+ * (The Mongo compiler gets the same values through a `$lookup` instead.)
46
+ */
47
+ export interface SemanticAccountSchema extends SemanticFieldCatalogue {
48
+ types: SemanticTypeDef[];
49
+ }
50
+ export interface SemanticValidationIssue {
51
+ path: string;
52
+ code: string;
53
+ message: string;
54
+ suggestion?: string;
55
+ }
56
+ export declare class SemanticQueryValidationError extends Error {
57
+ issues: SemanticValidationIssue[];
58
+ constructor(issues: SemanticValidationIssue[]);
59
+ }
60
+ /**
61
+ * Throw one validation issue — fail-fast, this package's contract (the issues
62
+ * array shape exists for the wire; internally the FIRST problem is the
63
+ * answer). One helper beside the error class it throws, so issue construction
64
+ * cannot drift between layers.
65
+ */
66
+ export declare function failSemanticValidation(path: string, code: string, message: string, suggestion?: string): never;
67
+ export interface SemanticCompileOptions {
68
+ /**
69
+ * Explicit — never sampled deeper than the entry point, so a run is
70
+ * reproducible: the same query, catalogue, `now` and timezone always give
71
+ * the same answer, whoever is asking.
72
+ */
73
+ now?: Date;
74
+ /** IANA zone for resolving relative dates. */
75
+ timeZone?: string;
76
+ }
package/dist/types.js ADDED
@@ -0,0 +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;
24
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";;;AAmEA,MAAa,4BAA6B,SAAQ,KAAK;IACrD,YAAmB,MAAiC;QAClD,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;QAD7B,WAAM,GAAN,MAAM,CAA2B;QAElD,IAAI,CAAC,IAAI,GAAG,8BAA8B,CAAA;IAC5C,CAAC;CACF;AALD,oEAKC;AAED;;;;;GAKG;AACH,SAAgB,sBAAsB,CACpC,IAAY,EACZ,IAAY,EACZ,OAAe,EACf,UAAmB;IAEnB,MAAM,IAAI,4BAA4B,CAAC;QACrC,UAAU,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE;KAC3E,CAAC,CAAA;AACJ,CAAC;AATD,wDASC"}
@@ -0,0 +1,33 @@
1
+ import { FieldDataType, SemanticQuery } from '@spotto/contract';
2
+ import { SemanticCompileOptions, SemanticFieldCatalogue, SemanticFieldDef, SemanticValidationIssue } from './types';
3
+ /** The live storage slots. `valueBool` does not exist; the live key is `valueBoolean`. */
4
+ export declare const SEMANTIC_VALUE_SLOT: Record<string, string>;
5
+ /** Millisecond-timestamp fields, by condition type. `createdDate` uses the ObjectId. */
6
+ export declare const SEMANTIC_TIMESTAMP_FIELD: Record<string, string>;
7
+ /**
8
+ * Find a field by name, case-insensitively — the one lookup both back-ends
9
+ * use, so neither can resolve a name to a different field than the other.
10
+ */
11
+ export declare function findSemanticField(catalogue: SemanticFieldCatalogue, fieldName: unknown): SemanticFieldDef | undefined;
12
+ /**
13
+ * Turn a written value into the form its storage slot actually holds.
14
+ *
15
+ * Shared deliberately: the Mongo compiler and the in-memory evaluator must
16
+ * compare against the SAME value, and this is where a numeric literal for a
17
+ * text field, or a relative date for a DATE field, becomes one concrete thing.
18
+ * Two copies of this would classify the same document differently.
19
+ *
20
+ * Throws a plain Error — the caller decides whether that is a validation issue
21
+ * (it is, at the gate) or an impossible state (it is, after the gate).
22
+ */
23
+ export declare function coerceSemanticValue(value: unknown, dataType: FieldDataType, now: Date, tz: string): string | number | boolean;
24
+ /**
25
+ * Assert a query is valid, throwing `SemanticQueryValidationError` on the
26
+ * first problem. This is the gate: a back-end may assume anything that passed
27
+ * through it is safe to execute.
28
+ */
29
+ export declare function assertSemanticQuery(semanticQuery: SemanticQuery, catalogue: SemanticFieldCatalogue, opts?: SemanticCompileOptions): void;
30
+ /** Validate without throwing. Empty array = valid. */
31
+ export declare function validateSemanticQuery(semanticQuery: SemanticQuery, catalogue: SemanticFieldCatalogue, opts?: SemanticCompileOptions): SemanticValidationIssue[];
32
+ /** True when any condition touches a custom field — both back-ends need to know. */
33
+ export declare function queryUsesCustomFields(semanticQuery: SemanticQuery): boolean;