@bitcobblers/wod-wiki-wql 0.8.39 → 0.9.40
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/index.cjs +271 -89
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +68 -33
- package/dist/index.d.ts +68 -33
- package/dist/index.mjs +270 -89
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -55,7 +55,7 @@ var WQL_CALC_TARGETS = [
|
|
|
55
55
|
var WQL_INTENSITY_TIERS = ["low", "moderate", "high"];
|
|
56
56
|
var WQL_GRAINS = ["summary", "event"];
|
|
57
57
|
var WQL_FIND_TARGETS = ["note", "block", "effort"];
|
|
58
|
-
var
|
|
58
|
+
var WQL_SOURCE_VALUES = ["journal", "collections", "feeds", "all"];
|
|
59
59
|
var WQL_RESULT_PLANES = wodWikiCore.KNOWN_OUTPUT_TYPES;
|
|
60
60
|
var WQL_ROWS_TARGETS = [...WQL_FIND_TARGETS, ...WQL_RESULT_PLANES, "all"];
|
|
61
61
|
var WQL_ROWS_SCOPE_KEYS = ["result", "block", "note"];
|
|
@@ -154,6 +154,7 @@ var DISPLAY_UNIT_RE = /\s+in\s+([a-zA-Z0-9_-]+)\s*$/;
|
|
|
154
154
|
var ROLLUP_RE = /\.rollup\((\d+)?([a-zA-Z]*)\)\s*$/;
|
|
155
155
|
var BY_RE = /\s+by\s+\{([^}]*)\}\s*$/;
|
|
156
156
|
var LAST_RE = /\s+last\s+(\d+)([dw])\s*$/i;
|
|
157
|
+
var FROM_TO_RE = /\s+from\s+(\d{4}-\d{2}-\d{2})(?:\s+to\s+(\d{4}-\d{2}-\d{2}))?\s*$/i;
|
|
157
158
|
var IN_SCOPE_RE = /\s+in\s+(\w+)\s*$/;
|
|
158
159
|
function parseWqlSuffixes(raw) {
|
|
159
160
|
const { primary, where } = splitAtWhere(raw);
|
|
@@ -161,8 +162,8 @@ function parseWqlSuffixes(raw) {
|
|
|
161
162
|
let displayUnit;
|
|
162
163
|
let rollup;
|
|
163
164
|
let groupBy;
|
|
164
|
-
let
|
|
165
|
-
let
|
|
165
|
+
let window;
|
|
166
|
+
let legacyScope;
|
|
166
167
|
const conflicts = [];
|
|
167
168
|
const isFind = text.startsWith("find:");
|
|
168
169
|
const isRows = /^rows(?=[:{]|\s|$)/.test(text);
|
|
@@ -181,18 +182,40 @@ function parseWqlSuffixes(raw) {
|
|
|
181
182
|
);
|
|
182
183
|
}
|
|
183
184
|
};
|
|
185
|
+
const froms = stripRepeated(FROM_TO_RE);
|
|
186
|
+
const lasts = stripRepeated(LAST_RE);
|
|
187
|
+
{
|
|
188
|
+
const windows = [
|
|
189
|
+
...froms.map((m) => ({
|
|
190
|
+
kind: "range",
|
|
191
|
+
start: m[1],
|
|
192
|
+
end: m[2],
|
|
193
|
+
raw: m[0].trim(),
|
|
194
|
+
at: m.index
|
|
195
|
+
})),
|
|
196
|
+
...lasts.map((m) => ({
|
|
197
|
+
kind: "relative",
|
|
198
|
+
size: parseInt(m[1], 10),
|
|
199
|
+
unit: m[2].toLowerCase(),
|
|
200
|
+
raw: m[0].trim(),
|
|
201
|
+
at: m.index
|
|
202
|
+
}))
|
|
203
|
+
].sort((a, b) => a.at - b.at);
|
|
204
|
+
if (windows.length > 1) {
|
|
205
|
+
conflicts.push(
|
|
206
|
+
`Duplicate 'window' clause: '${windows[0].raw}' conflicts with '${windows[windows.length - 1].raw}'`
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
if (windows.length === 1) {
|
|
210
|
+
const w = windows[0];
|
|
211
|
+
window = w.kind === "range" ? { kind: "range", start: w.start, end: w.end, raw: w.raw } : { kind: "relative", size: w.size, unit: w.unit, raw: w.raw };
|
|
212
|
+
}
|
|
213
|
+
}
|
|
184
214
|
if (isFind || isRows) {
|
|
185
|
-
const
|
|
186
|
-
conflictFrom("
|
|
187
|
-
if (
|
|
188
|
-
|
|
189
|
-
last = { size: parseInt(m[1], 10), unit: m[2].toLowerCase(), raw: m[0].trim() };
|
|
190
|
-
}
|
|
191
|
-
if (isFind) {
|
|
192
|
-
const scopes = stripRepeated(IN_SCOPE_RE);
|
|
193
|
-
conflictFrom("scope", scopes);
|
|
194
|
-
if (scopes.length) scope = scopes[scopes.length - 1][1];
|
|
195
|
-
} else {
|
|
215
|
+
const scopes = stripRepeated(IN_SCOPE_RE);
|
|
216
|
+
conflictFrom("scope", scopes);
|
|
217
|
+
if (scopes.length) legacyScope = scopes[scopes.length - 1][1];
|
|
218
|
+
if (isRows) {
|
|
196
219
|
const rollups = stripRepeated(ROLLUP_RE);
|
|
197
220
|
conflictFrom(".rollup", rollups);
|
|
198
221
|
if (rollups.length) {
|
|
@@ -226,8 +249,8 @@ function parseWqlSuffixes(raw) {
|
|
|
226
249
|
displayUnit,
|
|
227
250
|
rollup,
|
|
228
251
|
groupBy,
|
|
229
|
-
|
|
230
|
-
|
|
252
|
+
window,
|
|
253
|
+
legacyScope,
|
|
231
254
|
conflicts: conflicts.length ? conflicts : void 0,
|
|
232
255
|
primaryText: text
|
|
233
256
|
};
|
|
@@ -255,7 +278,10 @@ function parseJoinClause(where) {
|
|
|
255
278
|
if (where.trimStart().startsWith("find:")) {
|
|
256
279
|
const fp = parseFindQuery(where);
|
|
257
280
|
if (fp.error) return { error: fp.error };
|
|
258
|
-
|
|
281
|
+
if (fp.window?.kind === "range") {
|
|
282
|
+
return { error: "Range windows are not supported on join halves \u2014 use last <n>d|w" };
|
|
283
|
+
}
|
|
284
|
+
return { find: { target: fp.target, filters: fp.filters, last: fp.window?.kind === "relative" ? { size: fp.window.size, unit: fp.window.unit } : void 0 } };
|
|
259
285
|
}
|
|
260
286
|
const m = CMP_RE.exec(where.trim());
|
|
261
287
|
if (!m) return { error: cannotParseJoin(where) };
|
|
@@ -272,22 +298,69 @@ function parseJoinClause(where) {
|
|
|
272
298
|
};
|
|
273
299
|
}
|
|
274
300
|
function parseQuery(raw) {
|
|
275
|
-
const
|
|
301
|
+
const norm = normalizeWql(raw);
|
|
302
|
+
const trimmed = norm.query.trimStart();
|
|
303
|
+
let result;
|
|
276
304
|
if (trimmed.startsWith("find:")) {
|
|
277
|
-
|
|
305
|
+
result = parseFindQuery(norm.query);
|
|
306
|
+
} else if (/^rows(?=[:{]|\s|$)/.test(trimmed)) {
|
|
307
|
+
result = parseRowsQuery(norm.query);
|
|
308
|
+
} else {
|
|
309
|
+
result = parseAnalyticsQuery(norm.query);
|
|
310
|
+
}
|
|
311
|
+
if (norm.advisories.length) {
|
|
312
|
+
result.advisories = [...result.advisories ?? [], ...norm.advisories];
|
|
278
313
|
}
|
|
279
|
-
|
|
280
|
-
|
|
314
|
+
result.raw = raw;
|
|
315
|
+
return result;
|
|
316
|
+
}
|
|
317
|
+
function normalizeWql(raw) {
|
|
318
|
+
const advisories = [];
|
|
319
|
+
let text = raw.trim();
|
|
320
|
+
if (/^rows:?\s*[{]/.test(text)) {
|
|
321
|
+
text = text.replace(/^rows:?\s*[{]/, "rows:all{");
|
|
322
|
+
advisories.push("Bare 'rows:{...}' syntax is deprecated; use 'rows:all{...}' instead.");
|
|
323
|
+
}
|
|
324
|
+
const { primary, where } = splitAtWhere(text);
|
|
325
|
+
const isFind = primary.startsWith("find:");
|
|
326
|
+
const isRows = /^rows(?=[:{]|\s|$)/.test(primary);
|
|
327
|
+
if (isFind || isRows) {
|
|
328
|
+
const suffixes = parseWqlSuffixes(primary);
|
|
329
|
+
if (suffixes.legacyScope && !suffixes.conflicts?.length) {
|
|
330
|
+
advisories.push("Legacy 'in <scope>' syntax is deprecated; use 'source:<scope>' filter instead.");
|
|
331
|
+
const scope = suffixes.legacyScope;
|
|
332
|
+
let head = suffixes.primaryText.trim();
|
|
333
|
+
const braceOpen = head.indexOf("{");
|
|
334
|
+
const braceClose = head.lastIndexOf("}");
|
|
335
|
+
if (braceOpen !== -1 && braceClose !== -1 && braceClose > braceOpen) {
|
|
336
|
+
const beforeBrace = head.slice(0, braceOpen + 1);
|
|
337
|
+
const inside = head.slice(braceOpen + 1, braceClose).trim();
|
|
338
|
+
const afterBrace = head.slice(braceClose);
|
|
339
|
+
const newInside = inside ? `${inside},source:${scope}` : `source:${scope}`;
|
|
340
|
+
head = `${beforeBrace}${newInside}${afterBrace}`;
|
|
341
|
+
} else {
|
|
342
|
+
head = `${head}{source:${scope}}`;
|
|
343
|
+
}
|
|
344
|
+
const parts = [head];
|
|
345
|
+
if (suffixes.window) {
|
|
346
|
+
parts.push(suffixes.window.raw);
|
|
347
|
+
}
|
|
348
|
+
if (where) {
|
|
349
|
+
parts.push(`where ${where}`);
|
|
350
|
+
}
|
|
351
|
+
text = parts.join(" ");
|
|
352
|
+
}
|
|
281
353
|
}
|
|
282
|
-
return
|
|
354
|
+
return { query: text, advisories };
|
|
283
355
|
}
|
|
284
356
|
function cannotParseRows(text) {
|
|
285
357
|
return `Cannot parse "${text}". Expected rows:all{result:\u2026|block:\u2026|note:\u2026}, rows:<plane>{\u2026}, or rows:segment{\u2026} last 8w`;
|
|
286
358
|
}
|
|
287
359
|
function validateRowsFilters(filters) {
|
|
288
360
|
const scopeKeys = new Set(WQL_ROWS_SCOPE_KEYS);
|
|
361
|
+
const allowedKeys = /* @__PURE__ */ new Set([...WQL_ROWS_SCOPE_KEYS, "source"]);
|
|
289
362
|
const unsupported = filters.filter(
|
|
290
|
-
(f) => !
|
|
363
|
+
(f) => !allowedKeys.has(f.key) || f.negate || f.values.some((v) => v.wildcard)
|
|
291
364
|
);
|
|
292
365
|
if (unsupported.length > 0) {
|
|
293
366
|
return `Unsupported rows filter(s): ${unsupported.map((f) => (f.negate ? "!" : "") + f.key).join(", ")}. Rows queries support exact ${WQL_ROWS_SCOPE_KEYS.map((k) => `${k}:`).join(", ")} values.`;
|
|
@@ -297,16 +370,40 @@ function validateRowsFilters(filters) {
|
|
|
297
370
|
}
|
|
298
371
|
return void 0;
|
|
299
372
|
}
|
|
373
|
+
function validateSourceFilter(filters) {
|
|
374
|
+
const validSources = new Set(WQL_SOURCE_VALUES);
|
|
375
|
+
for (const f of filters) {
|
|
376
|
+
if (f.key !== "source") continue;
|
|
377
|
+
for (const v of f.values) {
|
|
378
|
+
const val = v.value;
|
|
379
|
+
if (validSources.has(val) || val === "collection" || val === "feed" || val.startsWith("collection:") || val.startsWith("feed:")) {
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
return `Unknown source "${val}". Try: ${WQL_SOURCE_VALUES.join(", ")}`;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return void 0;
|
|
386
|
+
}
|
|
300
387
|
var BARE_ROWS_RETIRED = 'Bare "rows:" is retired \u2014 name a target: rows:all{\u2026} for every output type, or a plane like rows:segment{\u2026}.';
|
|
301
388
|
function parseRowsQuery(raw) {
|
|
302
389
|
const suffixes = parseWqlSuffixes(raw);
|
|
303
|
-
const { where: whereText,
|
|
390
|
+
const { where: whereText, window: windowSuffix, legacyScope, groupBy, rollup, primaryText } = suffixes;
|
|
391
|
+
const win = toQueryWindow(windowSuffix);
|
|
392
|
+
const advisories = [];
|
|
393
|
+
if (legacyScope) {
|
|
394
|
+
advisories.push("Legacy 'in <scope>' syntax is deprecated; use 'source:<scope>' filter instead.");
|
|
395
|
+
}
|
|
304
396
|
const result = {
|
|
305
397
|
family: "rows",
|
|
306
398
|
raw,
|
|
307
399
|
filters: [],
|
|
308
|
-
|
|
400
|
+
window: win.window,
|
|
401
|
+
...advisories.length ? { advisories } : {}
|
|
309
402
|
};
|
|
403
|
+
if (win.error) {
|
|
404
|
+
result.error = win.error;
|
|
405
|
+
return result;
|
|
406
|
+
}
|
|
310
407
|
if (suffixes.conflicts?.length) {
|
|
311
408
|
result.error = suffixes.conflicts.join("; ");
|
|
312
409
|
return result;
|
|
@@ -349,6 +446,18 @@ function parseRowsQuery(raw) {
|
|
|
349
446
|
}
|
|
350
447
|
if (target !== "all") result.outputType = target;
|
|
351
448
|
result.filters = extractFilters(query, text);
|
|
449
|
+
if (legacyScope) {
|
|
450
|
+
result.filters.push({
|
|
451
|
+
key: "source",
|
|
452
|
+
negate: false,
|
|
453
|
+
values: [{ value: legacyScope, wildcard: false }]
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
const sourceError = validateSourceFilter(result.filters);
|
|
457
|
+
if (sourceError) {
|
|
458
|
+
result.error = sourceError;
|
|
459
|
+
return result;
|
|
460
|
+
}
|
|
352
461
|
const grainError = retiredGrainRollup(result.filters);
|
|
353
462
|
if (grainError) {
|
|
354
463
|
result.error = grainError;
|
|
@@ -361,6 +470,27 @@ function parseRowsQuery(raw) {
|
|
|
361
470
|
}
|
|
362
471
|
return result;
|
|
363
472
|
}
|
|
473
|
+
function isCivilDate(s) {
|
|
474
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s);
|
|
475
|
+
if (!m) return false;
|
|
476
|
+
const y = Number(m[1]);
|
|
477
|
+
const mo = Number(m[2]);
|
|
478
|
+
const d = Number(m[3]);
|
|
479
|
+
const dt = new Date(y, mo - 1, d);
|
|
480
|
+
return dt.getFullYear() === y && dt.getMonth() === mo - 1 && dt.getDate() === d;
|
|
481
|
+
}
|
|
482
|
+
function toQueryWindow(w) {
|
|
483
|
+
if (!w) return {};
|
|
484
|
+
if (w.kind === "relative") {
|
|
485
|
+
return { window: { kind: "relative", size: w.size, unit: w.unit } };
|
|
486
|
+
}
|
|
487
|
+
for (const d of [w.start, w.end]) {
|
|
488
|
+
if (d !== void 0 && !isCivilDate(d)) {
|
|
489
|
+
return { error: `Invalid window date "${d}" \u2014 expected a real calendar date YYYY-MM-DD` };
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
return { window: { kind: "range", start: w.start, end: w.end } };
|
|
493
|
+
}
|
|
364
494
|
function retiredGrainRollup(filters) {
|
|
365
495
|
if (filters.some((f) => f.key === "grain" && f.values.some((v) => v.value === "rollup"))) {
|
|
366
496
|
return "grain:rollup is retired \u2014 rollup grains are never stored; compute them with the .rollup suffix";
|
|
@@ -396,7 +526,8 @@ function extractFilters(query, text) {
|
|
|
396
526
|
}
|
|
397
527
|
function parseAnalyticsQuery(raw) {
|
|
398
528
|
const suffixes = parseWqlSuffixes(raw);
|
|
399
|
-
const { where: whereText, displayUnit, groupBy, rollup, primaryText: text } = suffixes;
|
|
529
|
+
const { where: whereText, displayUnit, groupBy, rollup, window: windowSuffix, primaryText: text } = suffixes;
|
|
530
|
+
const win = toQueryWindow(windowSuffix);
|
|
400
531
|
const base = {
|
|
401
532
|
family: "aggregate",
|
|
402
533
|
raw,
|
|
@@ -405,8 +536,13 @@ function parseAnalyticsQuery(raw) {
|
|
|
405
536
|
filters: [],
|
|
406
537
|
groupBy: groupBy ?? [],
|
|
407
538
|
displayUnit,
|
|
408
|
-
rollup: rollup ? { size: rollup.size, unit: rollup.unit } : void 0
|
|
539
|
+
rollup: rollup ? { size: rollup.size, unit: rollup.unit } : void 0,
|
|
540
|
+
window: win.window
|
|
409
541
|
};
|
|
542
|
+
if (win.error) {
|
|
543
|
+
base.error = win.error;
|
|
544
|
+
return base;
|
|
545
|
+
}
|
|
410
546
|
if (suffixes.conflicts?.length) {
|
|
411
547
|
base.error = suffixes.conflicts.join("; ");
|
|
412
548
|
return base;
|
|
@@ -460,19 +596,28 @@ function parseAnalyticsQuery(raw) {
|
|
|
460
596
|
return base;
|
|
461
597
|
}
|
|
462
598
|
function cannotParseFind(text) {
|
|
463
|
-
return `Cannot parse "${text}". Expected find:target{filters}
|
|
599
|
+
return `Cannot parse "${text}". Expected find:target{filters} last 8w`;
|
|
464
600
|
}
|
|
465
601
|
function parseFindQuery(raw) {
|
|
466
602
|
const suffixes = parseWqlSuffixes(raw);
|
|
467
|
-
const { where: whereText,
|
|
603
|
+
const { where: whereText, window: windowSuffix, legacyScope, primaryText: text } = suffixes;
|
|
604
|
+
const win = toQueryWindow(windowSuffix);
|
|
605
|
+
const advisories = [];
|
|
606
|
+
if (legacyScope) {
|
|
607
|
+
advisories.push("Legacy 'in <scope>' syntax is deprecated; use 'source:<scope>' filter instead.");
|
|
608
|
+
}
|
|
468
609
|
const result = {
|
|
469
610
|
family: "find",
|
|
470
611
|
raw,
|
|
471
612
|
target: "",
|
|
472
613
|
filters: [],
|
|
473
|
-
|
|
474
|
-
|
|
614
|
+
window: win.window,
|
|
615
|
+
...advisories.length ? { advisories } : {}
|
|
475
616
|
};
|
|
617
|
+
if (win.error) {
|
|
618
|
+
result.error = win.error;
|
|
619
|
+
return result;
|
|
620
|
+
}
|
|
476
621
|
if (suffixes.conflicts?.length) {
|
|
477
622
|
result.error = suffixes.conflicts.join("; ");
|
|
478
623
|
return result;
|
|
@@ -505,6 +650,18 @@ function parseFindQuery(raw) {
|
|
|
505
650
|
return result;
|
|
506
651
|
}
|
|
507
652
|
result.filters = extractFilters(query, text);
|
|
653
|
+
if (legacyScope) {
|
|
654
|
+
result.filters.push({
|
|
655
|
+
key: "source",
|
|
656
|
+
negate: false,
|
|
657
|
+
values: [{ value: legacyScope, wildcard: false }]
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
const sourceError = validateSourceFilter(result.filters);
|
|
661
|
+
if (sourceError) {
|
|
662
|
+
result.error = sourceError;
|
|
663
|
+
return result;
|
|
664
|
+
}
|
|
508
665
|
const findGrainError = retiredGrainRollup(result.filters);
|
|
509
666
|
if (findGrainError) {
|
|
510
667
|
result.error = findGrainError;
|
|
@@ -629,6 +786,8 @@ function wqlCompletionSource(options_ = {}) {
|
|
|
629
786
|
return options(WQL_GRAINS);
|
|
630
787
|
case "metric":
|
|
631
788
|
return metricOptions();
|
|
789
|
+
case "source":
|
|
790
|
+
return options(WQL_SOURCE_VALUES);
|
|
632
791
|
default:
|
|
633
792
|
return null;
|
|
634
793
|
}
|
|
@@ -1123,7 +1282,10 @@ function staticTagIndexFromBlocks(blocks) {
|
|
|
1123
1282
|
var DAY = 864e5;
|
|
1124
1283
|
var ROWS_CONTENT_PLANES = new Set(WQL_FIND_TARGETS);
|
|
1125
1284
|
function localDateString(ts) {
|
|
1126
|
-
|
|
1285
|
+
const d = new Date(ts);
|
|
1286
|
+
const mm = String(d.getMonth() + 1).padStart(2, "0");
|
|
1287
|
+
const dd = String(d.getDate()).padStart(2, "0");
|
|
1288
|
+
return `${d.getFullYear()}-${mm}-${dd}`;
|
|
1127
1289
|
}
|
|
1128
1290
|
function catalogOfItem(item) {
|
|
1129
1291
|
if (item.catalog) return item.catalog;
|
|
@@ -1133,9 +1295,13 @@ function catalogOfItem(item) {
|
|
|
1133
1295
|
return clean.split("/")[0];
|
|
1134
1296
|
}
|
|
1135
1297
|
function sourceMatches(sourceId, kind) {
|
|
1298
|
+
if (kind === "all") return true;
|
|
1136
1299
|
if (kind === "journal") return !sourceId || sourceId === "journal";
|
|
1137
|
-
if (kind === "collection" || kind === "
|
|
1138
|
-
return !!sourceId && sourceId.startsWith(
|
|
1300
|
+
if (kind === "collection" || kind === "collections") {
|
|
1301
|
+
return !!sourceId && sourceId.startsWith("collection:");
|
|
1302
|
+
}
|
|
1303
|
+
if (kind === "feed" || kind === "feeds") {
|
|
1304
|
+
return !!sourceId && sourceId.startsWith("feed:");
|
|
1139
1305
|
}
|
|
1140
1306
|
return sourceId === kind;
|
|
1141
1307
|
}
|
|
@@ -1150,12 +1316,26 @@ function applySourceFilter(items, filters) {
|
|
|
1150
1316
|
}
|
|
1151
1317
|
return items;
|
|
1152
1318
|
}
|
|
1153
|
-
function
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1319
|
+
function civilMidnight(iso) {
|
|
1320
|
+
const [y, m, d] = iso.split("-").map(Number);
|
|
1321
|
+
return new Date(y, m - 1, d).getTime();
|
|
1322
|
+
}
|
|
1323
|
+
function nextCivilMidnight(iso) {
|
|
1324
|
+
const [y, m, d] = iso.split("-").map(Number);
|
|
1325
|
+
return new Date(y, m - 1, d + 1).getTime();
|
|
1326
|
+
}
|
|
1327
|
+
function windowRange(w, anchorNow) {
|
|
1328
|
+
if (!w) return void 0;
|
|
1329
|
+
if (w.kind === "relative") {
|
|
1330
|
+
return { start: (anchorNow ?? Date.now()) - w.size * (w.unit === "w" ? 7 : 1) * DAY, end: Number.MAX_SAFE_INTEGER };
|
|
1158
1331
|
}
|
|
1332
|
+
const start = civilMidnight(w.start);
|
|
1333
|
+
const end = w.end !== void 0 ? nextCivilMidnight(w.end) - 1 : Number.MAX_SAFE_INTEGER;
|
|
1334
|
+
return { start, end };
|
|
1335
|
+
}
|
|
1336
|
+
function effectiveTimeWindow(createdAt, window, range, anchorNow) {
|
|
1337
|
+
const resolved = range ?? windowRange(window, anchorNow);
|
|
1338
|
+
if (resolved) return createdAt >= resolved.start && createdAt <= resolved.end;
|
|
1159
1339
|
return true;
|
|
1160
1340
|
}
|
|
1161
1341
|
function latestActivity(rows) {
|
|
@@ -1165,7 +1345,7 @@ function latestActivity(rows) {
|
|
|
1165
1345
|
}
|
|
1166
1346
|
function windowAnchor(selected, parsed, options2) {
|
|
1167
1347
|
if (options2.anchorNow !== void 0) return options2.anchorNow;
|
|
1168
|
-
if (options2.anchor === "latest-activity" && parsed.
|
|
1348
|
+
if (options2.anchor === "latest-activity" && parsed.window?.kind === "relative") return latestActivity(selected);
|
|
1169
1349
|
return void 0;
|
|
1170
1350
|
}
|
|
1171
1351
|
var defaultEventStore = {
|
|
@@ -1235,9 +1415,11 @@ function matchesFilters(row, filters, noteTags) {
|
|
|
1235
1415
|
const negate = group[0].negate;
|
|
1236
1416
|
const raw = factTagValue(row, key, noteTags);
|
|
1237
1417
|
const rowValues = Array.isArray(raw) ? raw : raw !== void 0 ? [raw] : [];
|
|
1238
|
-
const hit =
|
|
1239
|
-
(
|
|
1240
|
-
(
|
|
1418
|
+
const hit = rowValues.some(
|
|
1419
|
+
(value) => group.some(
|
|
1420
|
+
(f) => f.values.some(
|
|
1421
|
+
(a) => a.wildcard ? value.startsWith(a.value) : value === a.value
|
|
1422
|
+
)
|
|
1241
1423
|
)
|
|
1242
1424
|
);
|
|
1243
1425
|
return negate ? !hit : hit;
|
|
@@ -1247,8 +1429,8 @@ function dimValue(row, dim, noteTags) {
|
|
|
1247
1429
|
if (dim === "day") return localDateString(row.timestamp);
|
|
1248
1430
|
if (dim === "week") {
|
|
1249
1431
|
const d = new Date(row.timestamp);
|
|
1250
|
-
const monday = new Date(
|
|
1251
|
-
return
|
|
1432
|
+
const monday = new Date(d.getFullYear(), d.getMonth(), d.getDate() - (d.getDay() + 6) % 7);
|
|
1433
|
+
return localDateString(monday.getTime());
|
|
1252
1434
|
}
|
|
1253
1435
|
if (dim === "session") return row.resultId;
|
|
1254
1436
|
const raw = factTagValue(row, dim, noteTags);
|
|
@@ -1374,9 +1556,9 @@ var QueryService = class {
|
|
|
1374
1556
|
for (const blockContentId of blockIds) collect(await this.store.getEventsByContent(blockContentId));
|
|
1375
1557
|
for (const noteId of noteIds) collect(await this.store.getEventsForNote(noteId));
|
|
1376
1558
|
let groups = [...byResult.entries()].filter(([, rows]) => rows.length > 0);
|
|
1377
|
-
if (parsed.
|
|
1559
|
+
if (parsed.window) {
|
|
1378
1560
|
groups = groups.filter(
|
|
1379
|
-
([, rows]) => effectiveTimeWindow(rows[0].timestamp, parsed.
|
|
1561
|
+
([, rows]) => effectiveTimeWindow(rows[0].timestamp, parsed.window, void 0, options2.anchorNow)
|
|
1380
1562
|
);
|
|
1381
1563
|
}
|
|
1382
1564
|
groups.sort((a, b) => b[1][0].timestamp - a[1][0].timestamp);
|
|
@@ -1404,15 +1586,9 @@ var QueryService = class {
|
|
|
1404
1586
|
return this.runFindEffort(parsed);
|
|
1405
1587
|
}
|
|
1406
1588
|
let notes = [];
|
|
1407
|
-
|
|
1408
|
-
if (
|
|
1409
|
-
notes = notes.concat(await this.
|
|
1410
|
-
}
|
|
1411
|
-
if (scope === "collections" || scope === "feeds" || scope === "all") {
|
|
1412
|
-
let sNotes = this.staticNoteStore ? await this.staticNoteStore.getAllNotes() : [];
|
|
1413
|
-
if (scope === "collections") sNotes = sNotes.filter((n) => n.sourceId?.startsWith("collection:"));
|
|
1414
|
-
if (scope === "feeds") sNotes = sNotes.filter((n) => n.sourceId?.startsWith("feed:"));
|
|
1415
|
-
notes = notes.concat(sNotes);
|
|
1589
|
+
notes = notes.concat(await this.noteStore.getAllNotes());
|
|
1590
|
+
if (this.staticNoteStore) {
|
|
1591
|
+
notes = notes.concat(await this.staticNoteStore.getAllNotes());
|
|
1416
1592
|
}
|
|
1417
1593
|
const selectedCount = notes.length;
|
|
1418
1594
|
const anchorNow = windowAnchor(notes, parsed, options2);
|
|
@@ -1458,8 +1634,8 @@ var QueryService = class {
|
|
|
1458
1634
|
});
|
|
1459
1635
|
}
|
|
1460
1636
|
}
|
|
1461
|
-
if (parsed.
|
|
1462
|
-
notes = notes.filter((n) => effectiveTimeWindow(n.createdAt, parsed.
|
|
1637
|
+
if (parsed.window || options2.range) {
|
|
1638
|
+
notes = notes.filter((n) => effectiveTimeWindow(n.createdAt, parsed.window, options2.range, anchorNow));
|
|
1463
1639
|
}
|
|
1464
1640
|
if (parsed.join) {
|
|
1465
1641
|
const joined = await this.applyMetricJoin(parsed, notes, []);
|
|
@@ -1472,15 +1648,9 @@ var QueryService = class {
|
|
|
1472
1648
|
*/
|
|
1473
1649
|
async runFindBlock(parsed, options2 = {}) {
|
|
1474
1650
|
let blocks = [];
|
|
1475
|
-
|
|
1476
|
-
if (
|
|
1477
|
-
blocks = blocks.concat(await this.
|
|
1478
|
-
}
|
|
1479
|
-
if (scope === "collections" || scope === "feeds" || scope === "all") {
|
|
1480
|
-
let sBlocks = this.staticBlockStore ? await this.staticBlockStore.getAllBlocks() : [];
|
|
1481
|
-
if (scope === "collections") sBlocks = sBlocks.filter((b) => b.sourceId?.startsWith("collection:"));
|
|
1482
|
-
if (scope === "feeds") sBlocks = sBlocks.filter((b) => b.sourceId?.startsWith("feed:"));
|
|
1483
|
-
blocks = blocks.concat(sBlocks);
|
|
1651
|
+
blocks = blocks.concat(await this.blockStore.getAllBlocks());
|
|
1652
|
+
if (this.staticBlockStore) {
|
|
1653
|
+
blocks = blocks.concat(await this.staticBlockStore.getAllBlocks());
|
|
1484
1654
|
}
|
|
1485
1655
|
const selectedCount = blocks.length;
|
|
1486
1656
|
const anchorNow = windowAnchor(blocks, parsed, options2);
|
|
@@ -1520,8 +1690,8 @@ var QueryService = class {
|
|
|
1520
1690
|
blocks = blocks.filter((b) => matchingNoteIds.has(b.noteId));
|
|
1521
1691
|
}
|
|
1522
1692
|
}
|
|
1523
|
-
if (parsed.
|
|
1524
|
-
blocks = blocks.filter((b) => effectiveTimeWindow(b.createdAt, parsed.
|
|
1693
|
+
if (parsed.window || options2.range) {
|
|
1694
|
+
blocks = blocks.filter((b) => effectiveTimeWindow(b.createdAt, parsed.window, options2.range, anchorNow));
|
|
1525
1695
|
}
|
|
1526
1696
|
if (parsed.join) {
|
|
1527
1697
|
const joined = await this.applyMetricJoin(parsed, [], blocks);
|
|
@@ -1574,10 +1744,8 @@ var QueryService = class {
|
|
|
1574
1744
|
};
|
|
1575
1745
|
if (parsed.error) return empty;
|
|
1576
1746
|
if (parsed.join) return this.runJoined(parsed, options2);
|
|
1577
|
-
const
|
|
1578
|
-
const
|
|
1579
|
-
const end = options2.rangeEnd ?? Number.MAX_SAFE_INTEGER;
|
|
1580
|
-
const eventRows = windowed ? await this.store.getEventsByTimeRange(start, end) : await this.store.scanAll();
|
|
1747
|
+
const range = options2.rangeStart !== void 0 || options2.rangeEnd !== void 0 ? { start: options2.rangeStart ?? 0, end: options2.rangeEnd ?? Number.MAX_SAFE_INTEGER } : windowRange(parsed.window);
|
|
1748
|
+
const eventRows = range ? await this.store.getEventsByTimeRange(range.start, range.end) : await this.store.scanAll();
|
|
1581
1749
|
const candidates = eventRows.flatMap(projectEventToFacts).filter((row) => row.metricKey === parsed.metric);
|
|
1582
1750
|
const touchesTags = parsed.filters.some((f) => f.key === "tags") || parsed.groupBy.includes("tags");
|
|
1583
1751
|
const noteTags = await this.loadNoteTags(candidates, touchesTags);
|
|
@@ -1595,8 +1763,24 @@ var QueryService = class {
|
|
|
1595
1763
|
const timeDim = parsed.groupBy.find((d) => d === "day" || d === "week");
|
|
1596
1764
|
const tagDims = parsed.groupBy.filter((d) => d !== "day" && d !== "week");
|
|
1597
1765
|
const bucketMs = timeDim ? (timeDim === "week" ? 7 : 1) * DAY : parsed.rollup ? parsed.rollup.size * (parsed.rollup.unit === "w" ? 7 : 1) * DAY : null;
|
|
1598
|
-
const
|
|
1766
|
+
const civilDay = (ts) => {
|
|
1767
|
+
const d = new Date(ts);
|
|
1768
|
+
return Math.floor(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()) / DAY);
|
|
1769
|
+
};
|
|
1770
|
+
const bucketKey = (ts) => {
|
|
1771
|
+
if (timeDim === "day") return civilDay(ts);
|
|
1772
|
+
if (timeDim === "week") {
|
|
1773
|
+
const d = new Date(ts);
|
|
1774
|
+
return civilDay(ts) - (d.getDay() + 6) % 7;
|
|
1775
|
+
}
|
|
1776
|
+
return bucketMs ? Math.floor(ts / bucketMs) : 0;
|
|
1777
|
+
};
|
|
1599
1778
|
const bucketCount = bucketMs ? new Set(matched.map((p) => bucketKey(p.timestamp))).size : matched.length ? 1 : 0;
|
|
1779
|
+
const civilBucketInstant = (anchor, dim) => {
|
|
1780
|
+
const d = new Date(anchor);
|
|
1781
|
+
const back = dim === "week" ? (d.getDay() + 6) % 7 : 0;
|
|
1782
|
+
return new Date(d.getFullYear(), d.getMonth(), d.getDate() - back, 12).getTime();
|
|
1783
|
+
};
|
|
1600
1784
|
const { unit: targetUnit, convert: shouldConvert } = resolveDisplayUnit(matched, {
|
|
1601
1785
|
directive: parsed.displayUnit,
|
|
1602
1786
|
preferred: options2.preferredUnit
|
|
@@ -1621,7 +1805,7 @@ var QueryService = class {
|
|
|
1621
1805
|
(m) => toDisplayValue(m.value, m.unit ?? m.metricUnit, shouldConvert ? targetUnit : void 0)
|
|
1622
1806
|
);
|
|
1623
1807
|
return {
|
|
1624
|
-
ts: bucketMs ? b * bucketMs + bucketMs / 2 : Math.min(...members.map((m) => m.timestamp)),
|
|
1808
|
+
ts: timeDim ? civilBucketInstant(members[0].timestamp, timeDim) : bucketMs ? b * bucketMs + bucketMs / 2 : Math.min(...members.map((m) => m.timestamp)),
|
|
1625
1809
|
value: Math.round(aggregate(values, parsed.agg, members, shouldConvert ? targetUnit : void 0) * 100) / 100
|
|
1626
1810
|
};
|
|
1627
1811
|
});
|
|
@@ -1655,17 +1839,14 @@ var QueryService = class {
|
|
|
1655
1839
|
raw: "",
|
|
1656
1840
|
target: join.target,
|
|
1657
1841
|
filters: join.filters,
|
|
1658
|
-
|
|
1659
|
-
last: join.last
|
|
1842
|
+
window: join.last ? { kind: "relative", size: join.last.size, unit: join.last.unit } : void 0
|
|
1660
1843
|
});
|
|
1661
1844
|
const contentIds = await this.contentIdsFromFindResult(findResult);
|
|
1662
1845
|
if (contentIds.size === 0) return empty;
|
|
1663
1846
|
let facts = await this.deriveMetricFacts(contentIds, parsed.metric);
|
|
1664
|
-
|
|
1665
|
-
if (
|
|
1666
|
-
|
|
1667
|
-
const end = options2.rangeEnd ?? Number.MAX_SAFE_INTEGER;
|
|
1668
|
-
facts = facts.filter((f) => f.timestamp >= start && f.timestamp <= end);
|
|
1847
|
+
const joinRange = options2.rangeStart !== void 0 || options2.rangeEnd !== void 0 ? { start: options2.rangeStart ?? 0, end: options2.rangeEnd ?? Number.MAX_SAFE_INTEGER } : windowRange(parsed.window);
|
|
1848
|
+
if (joinRange) {
|
|
1849
|
+
facts = facts.filter((f) => f.timestamp >= joinRange.start && f.timestamp <= joinRange.end);
|
|
1669
1850
|
}
|
|
1670
1851
|
const touchesTags = parsed.filters.some((f) => f.key === "tags") || parsed.groupBy.includes("tags");
|
|
1671
1852
|
const noteTags = await this.loadNoteTags(facts, touchesTags);
|
|
@@ -2070,13 +2251,13 @@ sum:$metric{} rollup:1w
|
|
|
2070
2251
|
}
|
|
2071
2252
|
|
|
2072
2253
|
// src/version.ts
|
|
2073
|
-
var VERSION = "0.
|
|
2074
|
-
var GIT_SHA = "
|
|
2075
|
-
var BUILD_TIME = "2026-08-
|
|
2254
|
+
var VERSION = "0.9.40";
|
|
2255
|
+
var GIT_SHA = "2462ec9";
|
|
2256
|
+
var BUILD_TIME = "2026-08-26T01:04:47.412Z";
|
|
2076
2257
|
var SEMVER = {
|
|
2077
|
-
version: "0.
|
|
2078
|
-
gitSha: "
|
|
2079
|
-
buildTime: "2026-08-
|
|
2258
|
+
version: "0.9.40",
|
|
2259
|
+
gitSha: "2462ec9",
|
|
2260
|
+
buildTime: "2026-08-26T01:04:47.412Z"
|
|
2080
2261
|
};
|
|
2081
2262
|
|
|
2082
2263
|
exports.BUILD_TIME = BUILD_TIME;
|
|
@@ -2107,8 +2288,8 @@ exports.WQL_RESULT_PLANES = WQL_RESULT_PLANES;
|
|
|
2107
2288
|
exports.WQL_ROLLUP_PERIODS = WQL_ROLLUP_PERIODS;
|
|
2108
2289
|
exports.WQL_ROWS_SCOPE_KEYS = WQL_ROWS_SCOPE_KEYS;
|
|
2109
2290
|
exports.WQL_ROWS_TARGETS = WQL_ROWS_TARGETS;
|
|
2110
|
-
exports.WQL_SCOPES = WQL_SCOPES;
|
|
2111
2291
|
exports.WQL_SOURCES = WQL_SOURCES;
|
|
2292
|
+
exports.WQL_SOURCE_VALUES = WQL_SOURCE_VALUES;
|
|
2112
2293
|
exports.WQL_TAG_KEYS = WQL_TAG_KEYS;
|
|
2113
2294
|
exports.WQL_VIRTUAL_DIMS = WQL_VIRTUAL_DIMS;
|
|
2114
2295
|
exports.buildDashboardDocument = buildDashboardDocument;
|
|
@@ -2130,6 +2311,7 @@ exports.isFindQuery = isFindQuery;
|
|
|
2130
2311
|
exports.isProposedMetric = isProposedMetric;
|
|
2131
2312
|
exports.isRowsQuery = isRowsQuery;
|
|
2132
2313
|
exports.normalizeSummaryFacts = normalizeSummaryFacts;
|
|
2314
|
+
exports.normalizeWql = normalizeWql;
|
|
2133
2315
|
exports.parseDashboardNote = parseDashboardNote;
|
|
2134
2316
|
exports.parseFlatProperties = parseFlatProperties;
|
|
2135
2317
|
exports.parseFrontmatter = parseFrontmatter;
|