@jarenjs/json 0.46.4 → 0.49.2
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/ARCHITECTURE.md +1 -1
- package/README.md +47 -4
- package/dist/types/query/operators.d.ts +42 -0
- package/dist/types/query/series.d.ts +164 -0
- package/docs/QUERY-FORMAT.md +172 -4
- package/package.json +2 -2
- package/schemas/jaren-jslt.draft-07.schema.json +236 -0
- package/schemas/jaren-jslt.llm-profile.schema.json +229 -0
- package/schemas/jaren-jslt.schema.json +236 -0
- package/schemas/jaren-query.draft-07.schema.json +236 -0
- package/schemas/jaren-query.llm-profile.schema.json +229 -0
- package/schemas/jaren-query.schema.json +122 -2
- package/src/query/normalize.js +44 -1
- package/src/query/operators.js +332 -3
- package/src/query/series.js +415 -0
package/src/query/operators.js
CHANGED
|
@@ -57,6 +57,14 @@ import {
|
|
|
57
57
|
compileDateFormat,
|
|
58
58
|
} from '@jarenjs/core/dates';
|
|
59
59
|
import { isVector, cosineSimilarity } from '@jarenjs/core/vector';
|
|
60
|
+
import {
|
|
61
|
+
toEpoch,
|
|
62
|
+
overlapsInterval,
|
|
63
|
+
compileBuckets,
|
|
64
|
+
resampleSeries,
|
|
65
|
+
rollingSeries,
|
|
66
|
+
asOfJoin,
|
|
67
|
+
} from '@jarenjs/core/series';
|
|
60
68
|
import {
|
|
61
69
|
isPosition,
|
|
62
70
|
bboxOf,
|
|
@@ -83,6 +91,13 @@ import {
|
|
|
83
91
|
CARD_ZERO, CARD_ONE, CARD_OPT, CARD_MANY, joinCard, sumCard,
|
|
84
92
|
} from './normalize.js';
|
|
85
93
|
import { compileExistsTest } from './compile.js';
|
|
94
|
+
import {
|
|
95
|
+
CLOCK_MEMBERS, RESAMPLE_MEMBERS, ROLLING_MEMBERS, ASOF_MEMBERS,
|
|
96
|
+
AGGREGATES, FILLS, DIRECTIONS,
|
|
97
|
+
requireSpec, requireEnum, requireNumber, requireInstant, requireSpan,
|
|
98
|
+
compileSelector, compileClock, buildKernelSpec,
|
|
99
|
+
seriesArg, intervalArg, seriesRefusal,
|
|
100
|
+
} from './series.js';
|
|
86
101
|
|
|
87
102
|
const hasOwn = Object.hasOwn;
|
|
88
103
|
|
|
@@ -99,6 +114,13 @@ const ARGS_0N = Object.freeze({ kinds: Object.freeze(['expr']), min: 0, variadic
|
|
|
99
114
|
const ARGS_1N = Object.freeze({ kinds: Object.freeze(['expr']), min: 1, variadic: true });
|
|
100
115
|
// the schema operators $valid/$assert: [expr, schema] (section 8.11)
|
|
101
116
|
const ARGS_EXPR_SCHEMA = Object.freeze({ kinds: Object.freeze(['expr', 'schema']), min: 2 });
|
|
117
|
+
// the series operators (section 8.16): the operand(s), then a spec
|
|
118
|
+
// captured VERBATIM - a literal, so the width, the aggregate, the fill
|
|
119
|
+
// and the clock are read once at compile time and never per row
|
|
120
|
+
const ARGS_EXPR_SPEC = Object.freeze({ kinds: Object.freeze(['expr', 'raw']), min: 2 });
|
|
121
|
+
const ARGS_ASOF = Object.freeze({ kinds: Object.freeze(['expr', 'expr', 'raw']), min: 2 });
|
|
122
|
+
const ARGS_TIME_BUCKET = Object.freeze({
|
|
123
|
+
kinds: Object.freeze(['expr', 'expr', 'expr', 'raw']), min: 2 });
|
|
102
124
|
|
|
103
125
|
const RESULT_ONE = () => CARD_ONE;
|
|
104
126
|
const RESULT_OPT = () => CARD_OPT;
|
|
@@ -676,6 +698,17 @@ function unitArg(v, docPath) {
|
|
|
676
698
|
return v;
|
|
677
699
|
}
|
|
678
700
|
|
|
701
|
+
// The kernel refuses an operation whose unit reads a half the value has
|
|
702
|
+
// not got, and a fraction of a unit that has no exact length. Those are
|
|
703
|
+
// data-level refusals here - the operand decided them, not the query -
|
|
704
|
+
// so they surface as JQ2001 against the date operand, the same way a
|
|
705
|
+
// dynamic `$date-format` pattern does.
|
|
706
|
+
function dateRefusal(e, docPath) {
|
|
707
|
+
if (!(e instanceof TypeError))
|
|
708
|
+
throw e;
|
|
709
|
+
return runtimeError('JQ2001', e.message, docPath);
|
|
710
|
+
}
|
|
711
|
+
|
|
679
712
|
// The shared shape of $date-add / $date-sub: [date, duration] applies an
|
|
680
713
|
// ISO 8601 duration, [date, amount, unit] applies one unit. Both return
|
|
681
714
|
// the same lexical form they were given, so a full-date stays a
|
|
@@ -707,12 +740,22 @@ function dateShiftEntry(sign) {
|
|
|
707
740
|
+ (typeof second === 'string' ? JSON.stringify(second) : describeItem(second)),
|
|
708
741
|
secondPath);
|
|
709
742
|
}
|
|
710
|
-
|
|
743
|
+
try {
|
|
744
|
+
return formatRFC3339Parts(addDuration(parts, duration, sign));
|
|
745
|
+
}
|
|
746
|
+
catch (e) {
|
|
747
|
+
throw dateRefusal(e, datePath);
|
|
748
|
+
}
|
|
711
749
|
}
|
|
712
750
|
if (typeof second !== 'number')
|
|
713
751
|
throw runtimeError('JQ2001', `expected a number of units, got ${describeItem(second)}`, secondPath);
|
|
714
752
|
const unit = unitArg(unitGet(f), unitPath);
|
|
715
|
-
|
|
753
|
+
try {
|
|
754
|
+
return formatRFC3339Parts(addToParts(parts, sign * second, unit));
|
|
755
|
+
}
|
|
756
|
+
catch (e) {
|
|
757
|
+
throw dateRefusal(e, datePath);
|
|
758
|
+
}
|
|
716
759
|
};
|
|
717
760
|
},
|
|
718
761
|
};
|
|
@@ -735,7 +778,13 @@ function dateTruncEntry(truncate) {
|
|
|
735
778
|
if (value === EMPTY)
|
|
736
779
|
return EMPTY;
|
|
737
780
|
const parts = dateParts(value, datePath);
|
|
738
|
-
|
|
781
|
+
const unit = unitArg(unitGet(f), unitPath);
|
|
782
|
+
try {
|
|
783
|
+
return formatRFC3339Parts(truncate(parts, unit));
|
|
784
|
+
}
|
|
785
|
+
catch (e) {
|
|
786
|
+
throw dateRefusal(e, datePath);
|
|
787
|
+
}
|
|
739
788
|
};
|
|
740
789
|
},
|
|
741
790
|
};
|
|
@@ -876,6 +925,120 @@ function vectorArg(v, docPath) {
|
|
|
876
925
|
|
|
877
926
|
//#endregion
|
|
878
927
|
|
|
928
|
+
//#region series operators (section 8.16)
|
|
929
|
+
// Five operators, and not one loop among them. Interval overlap,
|
|
930
|
+
// bucketing, resampling, a window measured in time and an as-of join all
|
|
931
|
+
// exist once already, in `@jarenjs/core/series`, where the chart engine
|
|
932
|
+
// and the database read them too; these entries carry a document's
|
|
933
|
+
// literal spec across to that kernel and its answer back.
|
|
934
|
+
//
|
|
935
|
+
// The split between the two error families is the same one section 8.14
|
|
936
|
+
// draws, moved one level up. Everything a document AUTHORED - the width,
|
|
937
|
+
// the aggregate, the fill policy, the wall clock, the path a row keeps
|
|
938
|
+
// its instant at - is checked when the query compiles (`JQ0003`), so a
|
|
939
|
+
// misspelled member is a broken document rather than a surprise on the
|
|
940
|
+
// ten-thousandth row. Everything the DATA decides - a row that is not a
|
|
941
|
+
// sample, an instant that names none, a local time that never happened -
|
|
942
|
+
// is `JQ2001` against the operand that carried it. An as-of row with no
|
|
943
|
+
// match is neither: it is `right: null`, which is an answer.
|
|
944
|
+
//
|
|
945
|
+
// The instants in and out are epoch milliseconds, because that is what
|
|
946
|
+
// D3 makes canonical and what an indexed column stores. `$epoch` and
|
|
947
|
+
// `$datetime` are the two conversions, and they already exist.
|
|
948
|
+
|
|
949
|
+
/** The default clock: UTC, which needs no context at all. */
|
|
950
|
+
const EMPTY_CLOCK = Object.freeze({});
|
|
951
|
+
|
|
952
|
+
/** The default spec: every kernel default, none of them restated here. */
|
|
953
|
+
const EMPTY_SPEC = Object.freeze({});
|
|
954
|
+
|
|
955
|
+
/** How each `$resample` spec member is read. */
|
|
956
|
+
const RESAMPLE_RULES = Object.freeze({
|
|
957
|
+
every: requireSpan,
|
|
958
|
+
origin: requireInstant,
|
|
959
|
+
start: requireInstant,
|
|
960
|
+
end: requireInstant,
|
|
961
|
+
aggregate: (v, m, p) => requireEnum(v, AGGREGATES, m, p),
|
|
962
|
+
fill: (v, m, p) => requireEnum(v, FILLS, m, p),
|
|
963
|
+
at: null, value: null, // selectors; buildKernelSpec compiles these
|
|
964
|
+
});
|
|
965
|
+
|
|
966
|
+
/** How each `$rolling` spec member is read. */
|
|
967
|
+
const ROLLING_RULES = Object.freeze({
|
|
968
|
+
width: requireSpan,
|
|
969
|
+
aggregate: (v, m, p) => requireEnum(v, AGGREGATES, m, p),
|
|
970
|
+
minPeriods: requireNumber,
|
|
971
|
+
at: null, value: null,
|
|
972
|
+
});
|
|
973
|
+
|
|
974
|
+
// The shared shape of $resample and $rolling: [series, spec]. The spec
|
|
975
|
+
// compiles once, into the record the kernel takes, and the closure does
|
|
976
|
+
// nothing per call but read its rows and hand them over.
|
|
977
|
+
function seriesEntry(name, members, rules, required, kernel) {
|
|
978
|
+
return {
|
|
979
|
+
params: ARGS_EXPR_SPEC,
|
|
980
|
+
result: RESULT_MANY,
|
|
981
|
+
compile: (gets, args, docPath, node) => {
|
|
982
|
+
const seriesGet = gets[0];
|
|
983
|
+
const seriesPath = args[0].docPath;
|
|
984
|
+
const specNode = args[1];
|
|
985
|
+
const specPath = specNode.docPath;
|
|
986
|
+
const spec = requireSpec(specNode.value, members, name, specPath);
|
|
987
|
+
if (!hasOwn(spec, required)) {
|
|
988
|
+
throw new JsonQueryCompileError('JQ0003',
|
|
989
|
+
`'${name}' needs a spec member '${required}'`, specPath);
|
|
990
|
+
}
|
|
991
|
+
const kernelSpec = Object.freeze(
|
|
992
|
+
buildKernelSpec(spec, node.zoneProvider ?? null, specPath, rules));
|
|
993
|
+
// the kernel owns the remaining rules - a width mixing the
|
|
994
|
+
// calendar and clock families, a minPeriods that is not a whole
|
|
995
|
+
// number, a tolerance that is not a fixed width. Running it over
|
|
996
|
+
// no rows AT COMPILE TIME asks it all of them without copying one
|
|
997
|
+
// of them here, so a literal spec that cannot work is a broken
|
|
998
|
+
// document rather than a surprise on the first row
|
|
999
|
+
try {
|
|
1000
|
+
kernel([], kernelSpec);
|
|
1001
|
+
}
|
|
1002
|
+
catch (e) {
|
|
1003
|
+
if (!(e instanceof TypeError))
|
|
1004
|
+
throw e;
|
|
1005
|
+
throw new JsonQueryCompileError('JQ0003',
|
|
1006
|
+
`'${name}' spec: ${e.message}`, specPath, { cause: e });
|
|
1007
|
+
}
|
|
1008
|
+
return (f) => {
|
|
1009
|
+
const rows = seriesArg(seriesGet(f), seriesPath);
|
|
1010
|
+
try {
|
|
1011
|
+
return seqOf(kernel(rows, kernelSpec));
|
|
1012
|
+
}
|
|
1013
|
+
catch (e) {
|
|
1014
|
+
throw seriesRefusal(e, specPath);
|
|
1015
|
+
}
|
|
1016
|
+
};
|
|
1017
|
+
},
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
// $asof's spec in the kernel's spelling: `by` is one key for both sides,
|
|
1022
|
+
// and `leftAt`/`rightAt` say where each side keeps its instant when the
|
|
1023
|
+
// two documents disagree about the name.
|
|
1024
|
+
function asOfSpec(spec, docPath) {
|
|
1025
|
+
/** @type {any} */
|
|
1026
|
+
const out = {};
|
|
1027
|
+
if (hasOwn(spec, 'direction'))
|
|
1028
|
+
out.direction = requireEnum(spec.direction, DIRECTIONS, 'direction', docPath);
|
|
1029
|
+
if (hasOwn(spec, 'tolerance'))
|
|
1030
|
+
out.tolerance = requireSpan(spec.tolerance, 'tolerance', docPath);
|
|
1031
|
+
if (hasOwn(spec, 'by'))
|
|
1032
|
+
out.key = compileSelector(spec.by, 'by', docPath);
|
|
1033
|
+
if (hasOwn(spec, 'leftAt'))
|
|
1034
|
+
out.left = Object.freeze({ at: compileSelector(spec.leftAt, 'leftAt', docPath) });
|
|
1035
|
+
if (hasOwn(spec, 'rightAt'))
|
|
1036
|
+
out.right = Object.freeze({ at: compileSelector(spec.rightAt, 'rightAt', docPath) });
|
|
1037
|
+
return Object.freeze(out);
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
//#endregion
|
|
1041
|
+
|
|
879
1042
|
// the RFC 3339 type tests, shaped like the section 8.10 $is-* family:
|
|
880
1043
|
// one item of the right lexical form, never an error
|
|
881
1044
|
function dateTestEntry(test) {
|
|
@@ -1845,6 +2008,13 @@ export const OPERATORS = Object.freeze({
|
|
|
1845
2008
|
// months, quarters and years have no fixed width, so they are
|
|
1846
2009
|
// counted on the calendar; everything else divides an exact span
|
|
1847
2010
|
if (unit === 'month' || unit === 'quarter' || unit === 'year') {
|
|
2011
|
+
if (from.year < 0 || to.year < 0) {
|
|
2012
|
+
// the fixed-width branch below refuses this through NaN; the
|
|
2013
|
+
// calendar branch has to say so itself, or a full-time pair
|
|
2014
|
+
// measures zero months apart
|
|
2015
|
+
throw runtimeError('JQ2001', 'cannot measure a span from a value with no date',
|
|
2016
|
+
from.year < 0 ? fromPath : toPath);
|
|
2017
|
+
}
|
|
1848
2018
|
const months = monthsBetween(from, to);
|
|
1849
2019
|
return unit === 'month' ? months
|
|
1850
2020
|
: Math.trunc(months / (unit === 'quarter' ? 3 : 12));
|
|
@@ -1990,6 +2160,165 @@ export const OPERATORS = Object.freeze({
|
|
|
1990
2160
|
},
|
|
1991
2161
|
|
|
1992
2162
|
//#endregion
|
|
2163
|
+
|
|
2164
|
+
//#region section 8.16 - time series
|
|
2165
|
+
|
|
2166
|
+
'$overlaps': { // do two half-open intervals share an instant?
|
|
2167
|
+
params: ARGS_2,
|
|
2168
|
+
result: (cards) => (cards[0] === CARD_ONE && cards[1] === CARD_ONE ? CARD_ONE : CARD_OPT),
|
|
2169
|
+
resultType: RT_BOOLEAN,
|
|
2170
|
+
compile: (gets, args) => {
|
|
2171
|
+
const leftGet = gets[0];
|
|
2172
|
+
const leftPath = args[0].docPath;
|
|
2173
|
+
const rightGet = gets[1];
|
|
2174
|
+
const rightPath = args[1].docPath;
|
|
2175
|
+
return (f) => {
|
|
2176
|
+
// a row with no span is not overlapping and not an error: the
|
|
2177
|
+
// empty sequence propagates, as it does for every date operator
|
|
2178
|
+
const left = leftGet(f);
|
|
2179
|
+
if (left === EMPTY)
|
|
2180
|
+
return EMPTY;
|
|
2181
|
+
const right = rightGet(f);
|
|
2182
|
+
if (right === EMPTY)
|
|
2183
|
+
return EMPTY;
|
|
2184
|
+
try {
|
|
2185
|
+
return overlapsInterval(intervalArg(left, leftPath), intervalArg(right, rightPath));
|
|
2186
|
+
}
|
|
2187
|
+
catch (e) {
|
|
2188
|
+
throw seriesRefusal(e, leftPath);
|
|
2189
|
+
}
|
|
2190
|
+
};
|
|
2191
|
+
},
|
|
2192
|
+
},
|
|
2193
|
+
|
|
2194
|
+
'$time-bucket': { // the instant labelling the bucket an instant falls in
|
|
2195
|
+
params: ARGS_TIME_BUCKET,
|
|
2196
|
+
// every EXPRESSION operand propagates the empty sequence, so the
|
|
2197
|
+
// answer is exactly-one only when none of them can be empty - a
|
|
2198
|
+
// CARD_ONE declaration over an optional width would let the internal
|
|
2199
|
+
// empty marker escape into an array or object constructor
|
|
2200
|
+
result: (cards) => (cards.every((c) => c === CARD_ONE) ? CARD_ONE : CARD_OPT),
|
|
2201
|
+
resultType: RT_INTEGER,
|
|
2202
|
+
compile: (gets, args, docPath, node) => {
|
|
2203
|
+
const atGet = gets[0];
|
|
2204
|
+
const atPath = args[0].docPath;
|
|
2205
|
+
const everyGet = gets[1];
|
|
2206
|
+
const everyPath = args[1].docPath;
|
|
2207
|
+
const originGet = args.length >= 3 ? gets[2] : null;
|
|
2208
|
+
// the calendar context is the one literal position: a clock read
|
|
2209
|
+
// per row could not be resolved once, and a named zone needs a
|
|
2210
|
+
// provider a JSON document has no way to carry
|
|
2211
|
+
const clock = args.length === 4
|
|
2212
|
+
? compileClock(
|
|
2213
|
+
requireSpec(args[3].value, CLOCK_MEMBERS, '$time-bucket', args[3].docPath),
|
|
2214
|
+
node.zoneProvider ?? null, args[3].docPath)
|
|
2215
|
+
: EMPTY_CLOCK;
|
|
2216
|
+
// the ladder is a function of (every, origin) alone, and both are
|
|
2217
|
+
// almost always literal - so the memo makes a computed width
|
|
2218
|
+
// CORRECT, and costs a constant pair of comparisons when it is not
|
|
2219
|
+
let lastEvery;
|
|
2220
|
+
let lastOrigin;
|
|
2221
|
+
let buckets = null;
|
|
2222
|
+
// the common case is a literal width and a literal origin, and
|
|
2223
|
+
// then the ladder belongs to the query rather than to a row: a
|
|
2224
|
+
// duration neither family recognizes is a broken document
|
|
2225
|
+
if (args[1].kind === 'literal' && (originGet === null || args[2].kind === 'literal')) {
|
|
2226
|
+
lastEvery = args[1].value;
|
|
2227
|
+
lastOrigin = originGet === null || args[2].value === null ? EMPTY : args[2].value;
|
|
2228
|
+
try {
|
|
2229
|
+
buckets = compileBuckets(
|
|
2230
|
+
lastOrigin === EMPTY ? { every: lastEvery } : { every: lastEvery, origin: lastOrigin },
|
|
2231
|
+
clock);
|
|
2232
|
+
}
|
|
2233
|
+
catch (e) {
|
|
2234
|
+
if (!(e instanceof TypeError))
|
|
2235
|
+
throw e;
|
|
2236
|
+
throw new JsonQueryCompileError('JQ0003',
|
|
2237
|
+
`'$time-bucket': ${e.message}`, everyPath, { cause: e });
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
return (f) => {
|
|
2241
|
+
const at = atGet(f);
|
|
2242
|
+
if (at === EMPTY)
|
|
2243
|
+
return EMPTY;
|
|
2244
|
+
const every = everyGet(f);
|
|
2245
|
+
if (every === EMPTY)
|
|
2246
|
+
return EMPTY;
|
|
2247
|
+
// an absent origin is the ladder's own default; `null` spells it
|
|
2248
|
+
// for a caller who has a fourth argument to give
|
|
2249
|
+
let origin = originGet === null ? EMPTY : originGet(f);
|
|
2250
|
+
if (origin === null)
|
|
2251
|
+
origin = EMPTY;
|
|
2252
|
+
if (buckets === null || every !== lastEvery || origin !== lastOrigin) {
|
|
2253
|
+
if (typeof every !== 'string' && typeof every !== 'number') {
|
|
2254
|
+
throw runtimeError('JQ2001',
|
|
2255
|
+
`expected a bucket width, got ${describeItem(every)}`, everyPath);
|
|
2256
|
+
}
|
|
2257
|
+
try {
|
|
2258
|
+
buckets = compileBuckets(origin === EMPTY ? { every } : { every, origin }, clock);
|
|
2259
|
+
}
|
|
2260
|
+
catch (e) {
|
|
2261
|
+
buckets = null;
|
|
2262
|
+
throw seriesRefusal(e, everyPath);
|
|
2263
|
+
}
|
|
2264
|
+
lastEvery = every;
|
|
2265
|
+
lastOrigin = origin;
|
|
2266
|
+
}
|
|
2267
|
+
try {
|
|
2268
|
+
return buckets.floor(toEpoch(at));
|
|
2269
|
+
}
|
|
2270
|
+
catch (e) {
|
|
2271
|
+
throw seriesRefusal(e, atPath);
|
|
2272
|
+
}
|
|
2273
|
+
};
|
|
2274
|
+
},
|
|
2275
|
+
},
|
|
2276
|
+
|
|
2277
|
+
'$resample': seriesEntry('$resample', RESAMPLE_MEMBERS, RESAMPLE_RULES, 'every', resampleSeries),
|
|
2278
|
+
|
|
2279
|
+
'$rolling': seriesEntry('$rolling', ROLLING_MEMBERS, ROLLING_RULES, 'width', rollingSeries),
|
|
2280
|
+
|
|
2281
|
+
'$asof': { // the value that was current when this happened
|
|
2282
|
+
params: ARGS_ASOF,
|
|
2283
|
+
result: RESULT_MANY,
|
|
2284
|
+
// no clock: a join is instant arithmetic, so there is no calendar
|
|
2285
|
+
// boundary to fall on and no zone provider to resolve
|
|
2286
|
+
compile: (gets, args, docPath) => {
|
|
2287
|
+
const leftGet = gets[0];
|
|
2288
|
+
const leftPath = args[0].docPath;
|
|
2289
|
+
const rightGet = gets[1];
|
|
2290
|
+
const rightPath = args[1].docPath;
|
|
2291
|
+
const specNode = args.length === 3 ? args[2] : null;
|
|
2292
|
+
const spec = specNode === null
|
|
2293
|
+
? EMPTY_SPEC
|
|
2294
|
+
: asOfSpec(requireSpec(specNode.value, ASOF_MEMBERS, '$asof', specNode.docPath),
|
|
2295
|
+
specNode.docPath);
|
|
2296
|
+
const specPath = specNode === null ? docPath : specNode.docPath;
|
|
2297
|
+
try { // the direction and the tolerance, asked of the kernel once
|
|
2298
|
+
asOfJoin([], [], spec);
|
|
2299
|
+
}
|
|
2300
|
+
catch (e) {
|
|
2301
|
+
if (!(e instanceof TypeError))
|
|
2302
|
+
throw e;
|
|
2303
|
+
throw new JsonQueryCompileError('JQ0003',
|
|
2304
|
+
`'$asof' spec: ${e.message}`, specPath, { cause: e });
|
|
2305
|
+
}
|
|
2306
|
+
return (f) => {
|
|
2307
|
+
const left = seriesArg(leftGet(f), leftPath);
|
|
2308
|
+
const right = seriesArg(rightGet(f), rightPath);
|
|
2309
|
+
try {
|
|
2310
|
+
// a left row with nothing at or before it is `right: null`;
|
|
2311
|
+
// no match is data, and the row stays in the answer
|
|
2312
|
+
return seqOf(asOfJoin(left, right, spec));
|
|
2313
|
+
}
|
|
2314
|
+
catch (e) {
|
|
2315
|
+
throw seriesRefusal(e, specPath);
|
|
2316
|
+
}
|
|
2317
|
+
};
|
|
2318
|
+
},
|
|
2319
|
+
},
|
|
2320
|
+
|
|
2321
|
+
//#endregion
|
|
1993
2322
|
});
|
|
1994
2323
|
|
|
1995
2324
|
// singleton type predicates (section 8.10): true iff the operand is one
|