@spotto/semantic-query 1.0.70-alpha.27 → 1.0.70-alpha.29

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 CHANGED
@@ -1,32 +1,32 @@
1
- import { SemanticDateValue } from '@spotto/contract';
2
- /**
3
- * The semantic-query date engine.
4
- *
5
- * A saved query keeps relative dates SYMBOLIC — "last 30 days" means the last
6
- * 30 days forever, not the 30 days before it was saved — so every execution
7
- * resolves them afresh. This module is the one place that resolution happens,
8
- * for every consumer: the client evaluating against its local cache and the
9
- * server compiling to Mongo resolve the same words to the same instant.
10
- *
11
- * Everything takes an EXPLICIT `now`. Nothing here reads the clock. That is
12
- * what makes a resolved query reproducible: the same query, catalogue, `now`
13
- * and timezone always produce the same boundaries, whoever resolved them.
14
- */
15
- /** A calendar date with no timezone attached. */
16
- export interface SemanticCivilDate {
17
- y: number;
18
- m: number;
19
- d: number;
20
- }
21
- /** Resolve either semantic date form (ISO string or relative object) to a civil date. */
22
- export declare function resolveSemanticDate(v: SemanticDateValue, now: Date, tz: string): SemanticCivilDate;
23
- /** DATE custom fields store day-number integers: 15 Aug 2026 → 20260815. */
24
- export declare function semanticCivilToYyyymmdd(c: SemanticCivilDate): number;
25
- /**
26
- * Epoch-ms boundary for comparing against a millisecond timestamp field.
27
- *
28
- * `gt`/`lte` sit at the END of the named day (so "lte 2026-07-27" includes all
29
- * of the 27th); `gte`/`lt` at its START. Without this, half of every inclusive
30
- * range query silently drops a day.
31
- */
32
- export declare function semanticDateBoundaryMs(v: SemanticDateValue, operator: 'gt' | 'gte' | 'lt' | 'lte', now: Date, tz: string): number;
1
+ import { SemanticDateValue } from '@spotto/contract';
2
+ /**
3
+ * The semantic-query date engine.
4
+ *
5
+ * A saved query keeps relative dates SYMBOLIC — "last 30 days" means the last
6
+ * 30 days forever, not the 30 days before it was saved — so every execution
7
+ * resolves them afresh. This module is the one place that resolution happens,
8
+ * for every consumer: the client evaluating against its local cache and the
9
+ * server compiling to Mongo resolve the same words to the same instant.
10
+ *
11
+ * Everything takes an EXPLICIT `now`. Nothing here reads the clock. That is
12
+ * what makes a resolved query reproducible: the same query, catalogue, `now`
13
+ * and timezone always produce the same boundaries, whoever resolved them.
14
+ */
15
+ /** A calendar date with no timezone attached. */
16
+ export interface SemanticCivilDate {
17
+ y: number;
18
+ m: number;
19
+ d: number;
20
+ }
21
+ /** Resolve either semantic date form (ISO string or relative object) to a civil date. */
22
+ export declare function resolveSemanticDate(v: SemanticDateValue, now: Date, tz: string): SemanticCivilDate;
23
+ /** DATE custom fields store day-number integers: 15 Aug 2026 → 20260815. */
24
+ export declare function semanticCivilToYyyymmdd(c: SemanticCivilDate): number;
25
+ /**
26
+ * Epoch-ms boundary for comparing against a millisecond timestamp field.
27
+ *
28
+ * `gt`/`lte` sit at the END of the named day (so "lte 2026-07-27" includes all
29
+ * of the 27th); `gte`/`lt` at its START. Without this, half of every inclusive
30
+ * range query silently drops a day.
31
+ */
32
+ export declare function semanticDateBoundaryMs(v: SemanticDateValue, operator: 'gt' | 'gte' | 'lt' | 'lte', now: Date, tz: string): number;
package/dist/dates.js CHANGED
@@ -1,105 +1,105 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.semanticDateBoundaryMs = exports.semanticCivilToYyyymmdd = exports.resolveSemanticDate = void 0;
7
- const dayjs_1 = __importDefault(require("dayjs"));
8
- const utc_1 = __importDefault(require("dayjs/plugin/utc"));
9
- const timezone_1 = __importDefault(require("dayjs/plugin/timezone"));
10
- const isoWeek_1 = __importDefault(require("dayjs/plugin/isoWeek"));
11
- const quarterOfYear_1 = __importDefault(require("dayjs/plugin/quarterOfYear"));
12
- dayjs_1.default.extend(utc_1.default);
13
- dayjs_1.default.extend(timezone_1.default);
14
- dayjs_1.default.extend(isoWeek_1.default);
15
- dayjs_1.default.extend(quarterOfYear_1.default);
16
- const SEMANTIC_ISO_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
17
- function civilOf(d) {
18
- return { y: d.year(), m: d.month() + 1, d: d.date() };
19
- }
20
- /** Resolve a structured semantic relative date to a civil date in `tz`. */
21
- function resolveSemanticRelativeDate(v, now, tz) {
22
- const today = (0, dayjs_1.default)(now).tz(tz);
23
- const { base, offset, position } = v;
24
- switch (base) {
25
- case 'day':
26
- return civilOf(today.add(offset, 'day'));
27
- case 'week': {
28
- // 'same' keeps the weekday — "a week from today" is +7 days, not the
29
- // following Monday. `start`/`end` snap to the ISO week's boundaries
30
- // (Monday start, matching the platform's UNIT_MAP choice).
31
- if (position === 'same')
32
- return civilOf(today.add(offset, 'week'));
33
- const monday = today.add(offset, 'week').startOf('isoWeek');
34
- return civilOf(position === 'end' ? monday.add(6, 'day') : monday);
35
- }
36
- case 'month': {
37
- // 'same' keeps the day-of-month; dayjs clamps short months
38
- // (31 Jan + 1 month = 28/29 Feb), which is the specified behaviour.
39
- if (position === 'same')
40
- return civilOf(today.add(offset, 'month'));
41
- const t = today.add(offset, 'month');
42
- return civilOf(position === 'end' ? t.endOf('month') : t.startOf('month'));
43
- }
44
- case 'quarter': {
45
- if (position === 'same')
46
- return civilOf(today.add(offset * 3, 'month'));
47
- const t = today.add(offset, 'quarter');
48
- return civilOf(position === 'end'
49
- ? t.endOf('quarter')
50
- : t.startOf('quarter'));
51
- }
52
- case 'year': {
53
- if (position === 'same')
54
- return civilOf(today.add(offset * 12, 'month'));
55
- const t = today.add(offset, 'year');
56
- return civilOf(position === 'end' ? t.endOf('year') : t.startOf('year'));
57
- }
58
- }
59
- throw new Error(`unknown relative base '${String(base)}'`);
60
- }
61
- /** Resolve either semantic date form (ISO string or relative object) to a civil date. */
62
- function resolveSemanticDate(v, now, tz) {
63
- if (typeof v === 'string') {
64
- const m = SEMANTIC_ISO_RE.exec(v.trim());
65
- if (!m)
66
- throw new Error(`date must be ISO YYYY-MM-DD, got '${v}'`);
67
- const civil = { y: Number(m[1]), m: Number(m[2]), d: Number(m[3]) };
68
- // The pattern alone accepts 2026-02-31 and 2026-13-01, which resolve to an
69
- // Invalid Date and then to a NaN boundary — a query that runs and matches
70
- // nothing. Reject the impossible date instead. (Checked arithmetically:
71
- // dayjs' strict parsing needs the customParseFormat plugin, which this
72
- // module deliberately does not load.)
73
- const daysInMonth = new Date(Date.UTC(civil.y, civil.m, 0)).getUTCDate();
74
- if (civil.m < 1 || civil.m > 12 || civil.d < 1 || civil.d > daysInMonth) {
75
- throw new Error(`'${v}' is not a real calendar date`);
76
- }
77
- return civil;
78
- }
79
- if (v && typeof v === 'object' && v.relative) {
80
- return resolveSemanticRelativeDate(v, now, tz);
81
- }
82
- throw new Error(`unrecognised date value: ${JSON.stringify(v)}`);
83
- }
84
- exports.resolveSemanticDate = resolveSemanticDate;
85
- /** DATE custom fields store day-number integers: 15 Aug 2026 → 20260815. */
86
- function semanticCivilToYyyymmdd(c) {
87
- return c.y * 10000 + c.m * 100 + c.d;
88
- }
89
- exports.semanticCivilToYyyymmdd = semanticCivilToYyyymmdd;
90
- /**
91
- * Epoch-ms boundary for comparing against a millisecond timestamp field.
92
- *
93
- * `gt`/`lte` sit at the END of the named day (so "lte 2026-07-27" includes all
94
- * of the 27th); `gte`/`lt` at its START. Without this, half of every inclusive
95
- * range query silently drops a day.
96
- */
97
- function semanticDateBoundaryMs(v, operator, now, tz) {
98
- const c = resolveSemanticDate(v, now, tz);
99
- const iso = `${String(c.y).padStart(4, '0')}-${String(c.m).padStart(2, '0')}-${String(c.d).padStart(2, '0')}`;
100
- const day = dayjs_1.default.tz(iso, tz);
101
- const endOfDay = operator === 'gt' || operator === 'lte';
102
- return (endOfDay ? day.endOf('day') : day.startOf('day')).valueOf();
103
- }
104
- exports.semanticDateBoundaryMs = semanticDateBoundaryMs;
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.semanticDateBoundaryMs = exports.semanticCivilToYyyymmdd = exports.resolveSemanticDate = void 0;
7
+ const dayjs_1 = __importDefault(require("dayjs"));
8
+ const utc_1 = __importDefault(require("dayjs/plugin/utc"));
9
+ const timezone_1 = __importDefault(require("dayjs/plugin/timezone"));
10
+ const isoWeek_1 = __importDefault(require("dayjs/plugin/isoWeek"));
11
+ const quarterOfYear_1 = __importDefault(require("dayjs/plugin/quarterOfYear"));
12
+ dayjs_1.default.extend(utc_1.default);
13
+ dayjs_1.default.extend(timezone_1.default);
14
+ dayjs_1.default.extend(isoWeek_1.default);
15
+ dayjs_1.default.extend(quarterOfYear_1.default);
16
+ const SEMANTIC_ISO_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
17
+ function civilOf(d) {
18
+ return { y: d.year(), m: d.month() + 1, d: d.date() };
19
+ }
20
+ /** Resolve a structured semantic relative date to a civil date in `tz`. */
21
+ function resolveSemanticRelativeDate(v, now, tz) {
22
+ const today = (0, dayjs_1.default)(now).tz(tz);
23
+ const { base, offset, position } = v;
24
+ switch (base) {
25
+ case 'day':
26
+ return civilOf(today.add(offset, 'day'));
27
+ case 'week': {
28
+ // 'same' keeps the weekday — "a week from today" is +7 days, not the
29
+ // following Monday. `start`/`end` snap to the ISO week's boundaries
30
+ // (Monday start, matching the platform's UNIT_MAP choice).
31
+ if (position === 'same')
32
+ return civilOf(today.add(offset, 'week'));
33
+ const monday = today.add(offset, 'week').startOf('isoWeek');
34
+ return civilOf(position === 'end' ? monday.add(6, 'day') : monday);
35
+ }
36
+ case 'month': {
37
+ // 'same' keeps the day-of-month; dayjs clamps short months
38
+ // (31 Jan + 1 month = 28/29 Feb), which is the specified behaviour.
39
+ if (position === 'same')
40
+ return civilOf(today.add(offset, 'month'));
41
+ const t = today.add(offset, 'month');
42
+ return civilOf(position === 'end' ? t.endOf('month') : t.startOf('month'));
43
+ }
44
+ case 'quarter': {
45
+ if (position === 'same')
46
+ return civilOf(today.add(offset * 3, 'month'));
47
+ const t = today.add(offset, 'quarter');
48
+ return civilOf(position === 'end'
49
+ ? t.endOf('quarter')
50
+ : t.startOf('quarter'));
51
+ }
52
+ case 'year': {
53
+ if (position === 'same')
54
+ return civilOf(today.add(offset * 12, 'month'));
55
+ const t = today.add(offset, 'year');
56
+ return civilOf(position === 'end' ? t.endOf('year') : t.startOf('year'));
57
+ }
58
+ }
59
+ throw new Error(`unknown relative base '${String(base)}'`);
60
+ }
61
+ /** Resolve either semantic date form (ISO string or relative object) to a civil date. */
62
+ function resolveSemanticDate(v, now, tz) {
63
+ if (typeof v === 'string') {
64
+ const m = SEMANTIC_ISO_RE.exec(v.trim());
65
+ if (!m)
66
+ throw new Error(`date must be ISO YYYY-MM-DD, got '${v}'`);
67
+ const civil = { y: Number(m[1]), m: Number(m[2]), d: Number(m[3]) };
68
+ // The pattern alone accepts 2026-02-31 and 2026-13-01, which resolve to an
69
+ // Invalid Date and then to a NaN boundary — a query that runs and matches
70
+ // nothing. Reject the impossible date instead. (Checked arithmetically:
71
+ // dayjs' strict parsing needs the customParseFormat plugin, which this
72
+ // module deliberately does not load.)
73
+ const daysInMonth = new Date(Date.UTC(civil.y, civil.m, 0)).getUTCDate();
74
+ if (civil.m < 1 || civil.m > 12 || civil.d < 1 || civil.d > daysInMonth) {
75
+ throw new Error(`'${v}' is not a real calendar date`);
76
+ }
77
+ return civil;
78
+ }
79
+ if (v && typeof v === 'object' && v.relative) {
80
+ return resolveSemanticRelativeDate(v, now, tz);
81
+ }
82
+ throw new Error(`unrecognised date value: ${JSON.stringify(v)}`);
83
+ }
84
+ exports.resolveSemanticDate = resolveSemanticDate;
85
+ /** DATE custom fields store day-number integers: 15 Aug 2026 → 20260815. */
86
+ function semanticCivilToYyyymmdd(c) {
87
+ return c.y * 10000 + c.m * 100 + c.d;
88
+ }
89
+ exports.semanticCivilToYyyymmdd = semanticCivilToYyyymmdd;
90
+ /**
91
+ * Epoch-ms boundary for comparing against a millisecond timestamp field.
92
+ *
93
+ * `gt`/`lte` sit at the END of the named day (so "lte 2026-07-27" includes all
94
+ * of the 27th); `gte`/`lt` at its START. Without this, half of every inclusive
95
+ * range query silently drops a day.
96
+ */
97
+ function semanticDateBoundaryMs(v, operator, now, tz) {
98
+ const c = resolveSemanticDate(v, now, tz);
99
+ const iso = `${String(c.y).padStart(4, '0')}-${String(c.m).padStart(2, '0')}-${String(c.d).padStart(2, '0')}`;
100
+ const day = dayjs_1.default.tz(iso, tz);
101
+ const endOfDay = operator === 'gt' || operator === 'lte';
102
+ return (endOfDay ? day.endOf('day') : day.startOf('day')).valueOf();
103
+ }
104
+ exports.semanticDateBoundaryMs = semanticDateBoundaryMs;
105
105
  //# sourceMappingURL=dates.js.map
@@ -1,28 +1,28 @@
1
- import { SemanticQuery } from '@spotto/contract';
2
- import { SemanticAccountSchema, SemanticCompileOptions } from './types';
3
- /**
4
- * Reference semantics for the semantic-query language, evaluated directly over
5
- * asset documents in memory.
6
- *
7
- * This is deliberately an INDEPENDENT implementation of what each condition
8
- * MEANS — not a second call into the compiler. It exists to be the oracle in
9
- * the acceptance property:
10
- *
11
- * evaluate(query, docs) === find(compile(query)) restricted to docs
12
- *
13
- * If the two disagree, one of them is wrong, and the disagreement is the bug
14
- * report. This is what catches the silent-wrong-rows class — encoding
15
- * mistakes that otherwise surface as a plausibly-sized result set.
16
- *
17
- * Scope note: like the compiler, this evaluates CONDITIONS ONLY. Org and
18
- * user-group scoping belong to the pipeline scaffold; the oracle harness
19
- * restricts its corpus accordingly before comparing.
20
- *
21
- * Two deliberate mirrors of compiler decisions:
22
- * - Custom fields match at EITHER level (asset `fieldValues` or the type's
23
- * `typeFieldValuesAll`, joined by `typeId`) with no override precedence —
24
- * the platform's shipped flat-filter semantic.
25
- */
26
- declare type Doc = Record<string, any>;
27
- export declare function evaluateSemanticQuery(query: SemanticQuery, docs: Doc[], schema: SemanticAccountSchema, opts?: SemanticCompileOptions): Doc[];
28
- export {};
1
+ import { SemanticQuery } from '@spotto/contract';
2
+ import { SemanticAccountSchema, SemanticCompileOptions } from './types';
3
+ /**
4
+ * Reference semantics for the semantic-query language, evaluated directly over
5
+ * asset documents in memory.
6
+ *
7
+ * This is deliberately an INDEPENDENT implementation of what each condition
8
+ * MEANS — not a second call into the compiler. It exists to be the oracle in
9
+ * the acceptance property:
10
+ *
11
+ * evaluate(query, docs) === find(compile(query)) restricted to docs
12
+ *
13
+ * If the two disagree, one of them is wrong, and the disagreement is the bug
14
+ * report. This is what catches the silent-wrong-rows class — encoding
15
+ * mistakes that otherwise surface as a plausibly-sized result set.
16
+ *
17
+ * Scope note: like the compiler, this evaluates CONDITIONS ONLY. Org and
18
+ * user-group scoping belong to the pipeline scaffold; the oracle harness
19
+ * restricts its corpus accordingly before comparing.
20
+ *
21
+ * Two deliberate mirrors of compiler decisions:
22
+ * - Custom fields match at EITHER level (asset `fieldValues` or the type's
23
+ * `typeFieldValuesAll`, joined by `typeId`) with no override precedence —
24
+ * the platform's shipped flat-filter semantic.
25
+ */
26
+ declare type Doc = Record<string, any>;
27
+ export declare function evaluateSemanticQuery(query: SemanticQuery, docs: Doc[], schema: SemanticAccountSchema, opts?: SemanticCompileOptions): Doc[];
28
+ export {};