@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.mjs
CHANGED
|
@@ -53,7 +53,7 @@ var WQL_CALC_TARGETS = [
|
|
|
53
53
|
var WQL_INTENSITY_TIERS = ["low", "moderate", "high"];
|
|
54
54
|
var WQL_GRAINS = ["summary", "event"];
|
|
55
55
|
var WQL_FIND_TARGETS = ["note", "block", "effort"];
|
|
56
|
-
var
|
|
56
|
+
var WQL_SOURCE_VALUES = ["journal", "collections", "feeds", "all"];
|
|
57
57
|
var WQL_RESULT_PLANES = KNOWN_OUTPUT_TYPES;
|
|
58
58
|
var WQL_ROWS_TARGETS = [...WQL_FIND_TARGETS, ...WQL_RESULT_PLANES, "all"];
|
|
59
59
|
var WQL_ROWS_SCOPE_KEYS = ["result", "block", "note"];
|
|
@@ -152,6 +152,7 @@ var DISPLAY_UNIT_RE = /\s+in\s+([a-zA-Z0-9_-]+)\s*$/;
|
|
|
152
152
|
var ROLLUP_RE = /\.rollup\((\d+)?([a-zA-Z]*)\)\s*$/;
|
|
153
153
|
var BY_RE = /\s+by\s+\{([^}]*)\}\s*$/;
|
|
154
154
|
var LAST_RE = /\s+last\s+(\d+)([dw])\s*$/i;
|
|
155
|
+
var FROM_TO_RE = /\s+from\s+(\d{4}-\d{2}-\d{2})(?:\s+to\s+(\d{4}-\d{2}-\d{2}))?\s*$/i;
|
|
155
156
|
var IN_SCOPE_RE = /\s+in\s+(\w+)\s*$/;
|
|
156
157
|
function parseWqlSuffixes(raw) {
|
|
157
158
|
const { primary, where } = splitAtWhere(raw);
|
|
@@ -159,8 +160,8 @@ function parseWqlSuffixes(raw) {
|
|
|
159
160
|
let displayUnit;
|
|
160
161
|
let rollup;
|
|
161
162
|
let groupBy;
|
|
162
|
-
let
|
|
163
|
-
let
|
|
163
|
+
let window;
|
|
164
|
+
let legacyScope;
|
|
164
165
|
const conflicts = [];
|
|
165
166
|
const isFind = text.startsWith("find:");
|
|
166
167
|
const isRows = /^rows(?=[:{]|\s|$)/.test(text);
|
|
@@ -179,18 +180,40 @@ function parseWqlSuffixes(raw) {
|
|
|
179
180
|
);
|
|
180
181
|
}
|
|
181
182
|
};
|
|
183
|
+
const froms = stripRepeated(FROM_TO_RE);
|
|
184
|
+
const lasts = stripRepeated(LAST_RE);
|
|
185
|
+
{
|
|
186
|
+
const windows = [
|
|
187
|
+
...froms.map((m) => ({
|
|
188
|
+
kind: "range",
|
|
189
|
+
start: m[1],
|
|
190
|
+
end: m[2],
|
|
191
|
+
raw: m[0].trim(),
|
|
192
|
+
at: m.index
|
|
193
|
+
})),
|
|
194
|
+
...lasts.map((m) => ({
|
|
195
|
+
kind: "relative",
|
|
196
|
+
size: parseInt(m[1], 10),
|
|
197
|
+
unit: m[2].toLowerCase(),
|
|
198
|
+
raw: m[0].trim(),
|
|
199
|
+
at: m.index
|
|
200
|
+
}))
|
|
201
|
+
].sort((a, b) => a.at - b.at);
|
|
202
|
+
if (windows.length > 1) {
|
|
203
|
+
conflicts.push(
|
|
204
|
+
`Duplicate 'window' clause: '${windows[0].raw}' conflicts with '${windows[windows.length - 1].raw}'`
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
if (windows.length === 1) {
|
|
208
|
+
const w = windows[0];
|
|
209
|
+
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 };
|
|
210
|
+
}
|
|
211
|
+
}
|
|
182
212
|
if (isFind || isRows) {
|
|
183
|
-
const
|
|
184
|
-
conflictFrom("
|
|
185
|
-
if (
|
|
186
|
-
|
|
187
|
-
last = { size: parseInt(m[1], 10), unit: m[2].toLowerCase(), raw: m[0].trim() };
|
|
188
|
-
}
|
|
189
|
-
if (isFind) {
|
|
190
|
-
const scopes = stripRepeated(IN_SCOPE_RE);
|
|
191
|
-
conflictFrom("scope", scopes);
|
|
192
|
-
if (scopes.length) scope = scopes[scopes.length - 1][1];
|
|
193
|
-
} else {
|
|
213
|
+
const scopes = stripRepeated(IN_SCOPE_RE);
|
|
214
|
+
conflictFrom("scope", scopes);
|
|
215
|
+
if (scopes.length) legacyScope = scopes[scopes.length - 1][1];
|
|
216
|
+
if (isRows) {
|
|
194
217
|
const rollups = stripRepeated(ROLLUP_RE);
|
|
195
218
|
conflictFrom(".rollup", rollups);
|
|
196
219
|
if (rollups.length) {
|
|
@@ -224,8 +247,8 @@ function parseWqlSuffixes(raw) {
|
|
|
224
247
|
displayUnit,
|
|
225
248
|
rollup,
|
|
226
249
|
groupBy,
|
|
227
|
-
|
|
228
|
-
|
|
250
|
+
window,
|
|
251
|
+
legacyScope,
|
|
229
252
|
conflicts: conflicts.length ? conflicts : void 0,
|
|
230
253
|
primaryText: text
|
|
231
254
|
};
|
|
@@ -253,7 +276,10 @@ function parseJoinClause(where) {
|
|
|
253
276
|
if (where.trimStart().startsWith("find:")) {
|
|
254
277
|
const fp = parseFindQuery(where);
|
|
255
278
|
if (fp.error) return { error: fp.error };
|
|
256
|
-
|
|
279
|
+
if (fp.window?.kind === "range") {
|
|
280
|
+
return { error: "Range windows are not supported on join halves \u2014 use last <n>d|w" };
|
|
281
|
+
}
|
|
282
|
+
return { find: { target: fp.target, filters: fp.filters, last: fp.window?.kind === "relative" ? { size: fp.window.size, unit: fp.window.unit } : void 0 } };
|
|
257
283
|
}
|
|
258
284
|
const m = CMP_RE.exec(where.trim());
|
|
259
285
|
if (!m) return { error: cannotParseJoin(where) };
|
|
@@ -270,22 +296,69 @@ function parseJoinClause(where) {
|
|
|
270
296
|
};
|
|
271
297
|
}
|
|
272
298
|
function parseQuery(raw) {
|
|
273
|
-
const
|
|
299
|
+
const norm = normalizeWql(raw);
|
|
300
|
+
const trimmed = norm.query.trimStart();
|
|
301
|
+
let result;
|
|
274
302
|
if (trimmed.startsWith("find:")) {
|
|
275
|
-
|
|
303
|
+
result = parseFindQuery(norm.query);
|
|
304
|
+
} else if (/^rows(?=[:{]|\s|$)/.test(trimmed)) {
|
|
305
|
+
result = parseRowsQuery(norm.query);
|
|
306
|
+
} else {
|
|
307
|
+
result = parseAnalyticsQuery(norm.query);
|
|
308
|
+
}
|
|
309
|
+
if (norm.advisories.length) {
|
|
310
|
+
result.advisories = [...result.advisories ?? [], ...norm.advisories];
|
|
276
311
|
}
|
|
277
|
-
|
|
278
|
-
|
|
312
|
+
result.raw = raw;
|
|
313
|
+
return result;
|
|
314
|
+
}
|
|
315
|
+
function normalizeWql(raw) {
|
|
316
|
+
const advisories = [];
|
|
317
|
+
let text = raw.trim();
|
|
318
|
+
if (/^rows:?\s*[{]/.test(text)) {
|
|
319
|
+
text = text.replace(/^rows:?\s*[{]/, "rows:all{");
|
|
320
|
+
advisories.push("Bare 'rows:{...}' syntax is deprecated; use 'rows:all{...}' instead.");
|
|
321
|
+
}
|
|
322
|
+
const { primary, where } = splitAtWhere(text);
|
|
323
|
+
const isFind = primary.startsWith("find:");
|
|
324
|
+
const isRows = /^rows(?=[:{]|\s|$)/.test(primary);
|
|
325
|
+
if (isFind || isRows) {
|
|
326
|
+
const suffixes = parseWqlSuffixes(primary);
|
|
327
|
+
if (suffixes.legacyScope && !suffixes.conflicts?.length) {
|
|
328
|
+
advisories.push("Legacy 'in <scope>' syntax is deprecated; use 'source:<scope>' filter instead.");
|
|
329
|
+
const scope = suffixes.legacyScope;
|
|
330
|
+
let head = suffixes.primaryText.trim();
|
|
331
|
+
const braceOpen = head.indexOf("{");
|
|
332
|
+
const braceClose = head.lastIndexOf("}");
|
|
333
|
+
if (braceOpen !== -1 && braceClose !== -1 && braceClose > braceOpen) {
|
|
334
|
+
const beforeBrace = head.slice(0, braceOpen + 1);
|
|
335
|
+
const inside = head.slice(braceOpen + 1, braceClose).trim();
|
|
336
|
+
const afterBrace = head.slice(braceClose);
|
|
337
|
+
const newInside = inside ? `${inside},source:${scope}` : `source:${scope}`;
|
|
338
|
+
head = `${beforeBrace}${newInside}${afterBrace}`;
|
|
339
|
+
} else {
|
|
340
|
+
head = `${head}{source:${scope}}`;
|
|
341
|
+
}
|
|
342
|
+
const parts = [head];
|
|
343
|
+
if (suffixes.window) {
|
|
344
|
+
parts.push(suffixes.window.raw);
|
|
345
|
+
}
|
|
346
|
+
if (where) {
|
|
347
|
+
parts.push(`where ${where}`);
|
|
348
|
+
}
|
|
349
|
+
text = parts.join(" ");
|
|
350
|
+
}
|
|
279
351
|
}
|
|
280
|
-
return
|
|
352
|
+
return { query: text, advisories };
|
|
281
353
|
}
|
|
282
354
|
function cannotParseRows(text) {
|
|
283
355
|
return `Cannot parse "${text}". Expected rows:all{result:\u2026|block:\u2026|note:\u2026}, rows:<plane>{\u2026}, or rows:segment{\u2026} last 8w`;
|
|
284
356
|
}
|
|
285
357
|
function validateRowsFilters(filters) {
|
|
286
358
|
const scopeKeys = new Set(WQL_ROWS_SCOPE_KEYS);
|
|
359
|
+
const allowedKeys = /* @__PURE__ */ new Set([...WQL_ROWS_SCOPE_KEYS, "source"]);
|
|
287
360
|
const unsupported = filters.filter(
|
|
288
|
-
(f) => !
|
|
361
|
+
(f) => !allowedKeys.has(f.key) || f.negate || f.values.some((v) => v.wildcard)
|
|
289
362
|
);
|
|
290
363
|
if (unsupported.length > 0) {
|
|
291
364
|
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.`;
|
|
@@ -295,16 +368,40 @@ function validateRowsFilters(filters) {
|
|
|
295
368
|
}
|
|
296
369
|
return void 0;
|
|
297
370
|
}
|
|
371
|
+
function validateSourceFilter(filters) {
|
|
372
|
+
const validSources = new Set(WQL_SOURCE_VALUES);
|
|
373
|
+
for (const f of filters) {
|
|
374
|
+
if (f.key !== "source") continue;
|
|
375
|
+
for (const v of f.values) {
|
|
376
|
+
const val = v.value;
|
|
377
|
+
if (validSources.has(val) || val === "collection" || val === "feed" || val.startsWith("collection:") || val.startsWith("feed:")) {
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
return `Unknown source "${val}". Try: ${WQL_SOURCE_VALUES.join(", ")}`;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return void 0;
|
|
384
|
+
}
|
|
298
385
|
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}.';
|
|
299
386
|
function parseRowsQuery(raw) {
|
|
300
387
|
const suffixes = parseWqlSuffixes(raw);
|
|
301
|
-
const { where: whereText,
|
|
388
|
+
const { where: whereText, window: windowSuffix, legacyScope, groupBy, rollup, primaryText } = suffixes;
|
|
389
|
+
const win = toQueryWindow(windowSuffix);
|
|
390
|
+
const advisories = [];
|
|
391
|
+
if (legacyScope) {
|
|
392
|
+
advisories.push("Legacy 'in <scope>' syntax is deprecated; use 'source:<scope>' filter instead.");
|
|
393
|
+
}
|
|
302
394
|
const result = {
|
|
303
395
|
family: "rows",
|
|
304
396
|
raw,
|
|
305
397
|
filters: [],
|
|
306
|
-
|
|
398
|
+
window: win.window,
|
|
399
|
+
...advisories.length ? { advisories } : {}
|
|
307
400
|
};
|
|
401
|
+
if (win.error) {
|
|
402
|
+
result.error = win.error;
|
|
403
|
+
return result;
|
|
404
|
+
}
|
|
308
405
|
if (suffixes.conflicts?.length) {
|
|
309
406
|
result.error = suffixes.conflicts.join("; ");
|
|
310
407
|
return result;
|
|
@@ -347,6 +444,18 @@ function parseRowsQuery(raw) {
|
|
|
347
444
|
}
|
|
348
445
|
if (target !== "all") result.outputType = target;
|
|
349
446
|
result.filters = extractFilters(query, text);
|
|
447
|
+
if (legacyScope) {
|
|
448
|
+
result.filters.push({
|
|
449
|
+
key: "source",
|
|
450
|
+
negate: false,
|
|
451
|
+
values: [{ value: legacyScope, wildcard: false }]
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
const sourceError = validateSourceFilter(result.filters);
|
|
455
|
+
if (sourceError) {
|
|
456
|
+
result.error = sourceError;
|
|
457
|
+
return result;
|
|
458
|
+
}
|
|
350
459
|
const grainError = retiredGrainRollup(result.filters);
|
|
351
460
|
if (grainError) {
|
|
352
461
|
result.error = grainError;
|
|
@@ -359,6 +468,27 @@ function parseRowsQuery(raw) {
|
|
|
359
468
|
}
|
|
360
469
|
return result;
|
|
361
470
|
}
|
|
471
|
+
function isCivilDate(s) {
|
|
472
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s);
|
|
473
|
+
if (!m) return false;
|
|
474
|
+
const y = Number(m[1]);
|
|
475
|
+
const mo = Number(m[2]);
|
|
476
|
+
const d = Number(m[3]);
|
|
477
|
+
const dt = new Date(y, mo - 1, d);
|
|
478
|
+
return dt.getFullYear() === y && dt.getMonth() === mo - 1 && dt.getDate() === d;
|
|
479
|
+
}
|
|
480
|
+
function toQueryWindow(w) {
|
|
481
|
+
if (!w) return {};
|
|
482
|
+
if (w.kind === "relative") {
|
|
483
|
+
return { window: { kind: "relative", size: w.size, unit: w.unit } };
|
|
484
|
+
}
|
|
485
|
+
for (const d of [w.start, w.end]) {
|
|
486
|
+
if (d !== void 0 && !isCivilDate(d)) {
|
|
487
|
+
return { error: `Invalid window date "${d}" \u2014 expected a real calendar date YYYY-MM-DD` };
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return { window: { kind: "range", start: w.start, end: w.end } };
|
|
491
|
+
}
|
|
362
492
|
function retiredGrainRollup(filters) {
|
|
363
493
|
if (filters.some((f) => f.key === "grain" && f.values.some((v) => v.value === "rollup"))) {
|
|
364
494
|
return "grain:rollup is retired \u2014 rollup grains are never stored; compute them with the .rollup suffix";
|
|
@@ -394,7 +524,8 @@ function extractFilters(query, text) {
|
|
|
394
524
|
}
|
|
395
525
|
function parseAnalyticsQuery(raw) {
|
|
396
526
|
const suffixes = parseWqlSuffixes(raw);
|
|
397
|
-
const { where: whereText, displayUnit, groupBy, rollup, primaryText: text } = suffixes;
|
|
527
|
+
const { where: whereText, displayUnit, groupBy, rollup, window: windowSuffix, primaryText: text } = suffixes;
|
|
528
|
+
const win = toQueryWindow(windowSuffix);
|
|
398
529
|
const base = {
|
|
399
530
|
family: "aggregate",
|
|
400
531
|
raw,
|
|
@@ -403,8 +534,13 @@ function parseAnalyticsQuery(raw) {
|
|
|
403
534
|
filters: [],
|
|
404
535
|
groupBy: groupBy ?? [],
|
|
405
536
|
displayUnit,
|
|
406
|
-
rollup: rollup ? { size: rollup.size, unit: rollup.unit } : void 0
|
|
537
|
+
rollup: rollup ? { size: rollup.size, unit: rollup.unit } : void 0,
|
|
538
|
+
window: win.window
|
|
407
539
|
};
|
|
540
|
+
if (win.error) {
|
|
541
|
+
base.error = win.error;
|
|
542
|
+
return base;
|
|
543
|
+
}
|
|
408
544
|
if (suffixes.conflicts?.length) {
|
|
409
545
|
base.error = suffixes.conflicts.join("; ");
|
|
410
546
|
return base;
|
|
@@ -458,19 +594,28 @@ function parseAnalyticsQuery(raw) {
|
|
|
458
594
|
return base;
|
|
459
595
|
}
|
|
460
596
|
function cannotParseFind(text) {
|
|
461
|
-
return `Cannot parse "${text}". Expected find:target{filters}
|
|
597
|
+
return `Cannot parse "${text}". Expected find:target{filters} last 8w`;
|
|
462
598
|
}
|
|
463
599
|
function parseFindQuery(raw) {
|
|
464
600
|
const suffixes = parseWqlSuffixes(raw);
|
|
465
|
-
const { where: whereText,
|
|
601
|
+
const { where: whereText, window: windowSuffix, legacyScope, primaryText: text } = suffixes;
|
|
602
|
+
const win = toQueryWindow(windowSuffix);
|
|
603
|
+
const advisories = [];
|
|
604
|
+
if (legacyScope) {
|
|
605
|
+
advisories.push("Legacy 'in <scope>' syntax is deprecated; use 'source:<scope>' filter instead.");
|
|
606
|
+
}
|
|
466
607
|
const result = {
|
|
467
608
|
family: "find",
|
|
468
609
|
raw,
|
|
469
610
|
target: "",
|
|
470
611
|
filters: [],
|
|
471
|
-
|
|
472
|
-
|
|
612
|
+
window: win.window,
|
|
613
|
+
...advisories.length ? { advisories } : {}
|
|
473
614
|
};
|
|
615
|
+
if (win.error) {
|
|
616
|
+
result.error = win.error;
|
|
617
|
+
return result;
|
|
618
|
+
}
|
|
474
619
|
if (suffixes.conflicts?.length) {
|
|
475
620
|
result.error = suffixes.conflicts.join("; ");
|
|
476
621
|
return result;
|
|
@@ -503,6 +648,18 @@ function parseFindQuery(raw) {
|
|
|
503
648
|
return result;
|
|
504
649
|
}
|
|
505
650
|
result.filters = extractFilters(query, text);
|
|
651
|
+
if (legacyScope) {
|
|
652
|
+
result.filters.push({
|
|
653
|
+
key: "source",
|
|
654
|
+
negate: false,
|
|
655
|
+
values: [{ value: legacyScope, wildcard: false }]
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
const sourceError = validateSourceFilter(result.filters);
|
|
659
|
+
if (sourceError) {
|
|
660
|
+
result.error = sourceError;
|
|
661
|
+
return result;
|
|
662
|
+
}
|
|
506
663
|
const findGrainError = retiredGrainRollup(result.filters);
|
|
507
664
|
if (findGrainError) {
|
|
508
665
|
result.error = findGrainError;
|
|
@@ -627,6 +784,8 @@ function wqlCompletionSource(options_ = {}) {
|
|
|
627
784
|
return options(WQL_GRAINS);
|
|
628
785
|
case "metric":
|
|
629
786
|
return metricOptions();
|
|
787
|
+
case "source":
|
|
788
|
+
return options(WQL_SOURCE_VALUES);
|
|
630
789
|
default:
|
|
631
790
|
return null;
|
|
632
791
|
}
|
|
@@ -1121,7 +1280,10 @@ function staticTagIndexFromBlocks(blocks) {
|
|
|
1121
1280
|
var DAY = 864e5;
|
|
1122
1281
|
var ROWS_CONTENT_PLANES = new Set(WQL_FIND_TARGETS);
|
|
1123
1282
|
function localDateString(ts) {
|
|
1124
|
-
|
|
1283
|
+
const d = new Date(ts);
|
|
1284
|
+
const mm = String(d.getMonth() + 1).padStart(2, "0");
|
|
1285
|
+
const dd = String(d.getDate()).padStart(2, "0");
|
|
1286
|
+
return `${d.getFullYear()}-${mm}-${dd}`;
|
|
1125
1287
|
}
|
|
1126
1288
|
function catalogOfItem(item) {
|
|
1127
1289
|
if (item.catalog) return item.catalog;
|
|
@@ -1131,9 +1293,13 @@ function catalogOfItem(item) {
|
|
|
1131
1293
|
return clean.split("/")[0];
|
|
1132
1294
|
}
|
|
1133
1295
|
function sourceMatches(sourceId, kind) {
|
|
1296
|
+
if (kind === "all") return true;
|
|
1134
1297
|
if (kind === "journal") return !sourceId || sourceId === "journal";
|
|
1135
|
-
if (kind === "collection" || kind === "
|
|
1136
|
-
return !!sourceId && sourceId.startsWith(
|
|
1298
|
+
if (kind === "collection" || kind === "collections") {
|
|
1299
|
+
return !!sourceId && sourceId.startsWith("collection:");
|
|
1300
|
+
}
|
|
1301
|
+
if (kind === "feed" || kind === "feeds") {
|
|
1302
|
+
return !!sourceId && sourceId.startsWith("feed:");
|
|
1137
1303
|
}
|
|
1138
1304
|
return sourceId === kind;
|
|
1139
1305
|
}
|
|
@@ -1148,12 +1314,26 @@ function applySourceFilter(items, filters) {
|
|
|
1148
1314
|
}
|
|
1149
1315
|
return items;
|
|
1150
1316
|
}
|
|
1151
|
-
function
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1317
|
+
function civilMidnight(iso) {
|
|
1318
|
+
const [y, m, d] = iso.split("-").map(Number);
|
|
1319
|
+
return new Date(y, m - 1, d).getTime();
|
|
1320
|
+
}
|
|
1321
|
+
function nextCivilMidnight(iso) {
|
|
1322
|
+
const [y, m, d] = iso.split("-").map(Number);
|
|
1323
|
+
return new Date(y, m - 1, d + 1).getTime();
|
|
1324
|
+
}
|
|
1325
|
+
function windowRange(w, anchorNow) {
|
|
1326
|
+
if (!w) return void 0;
|
|
1327
|
+
if (w.kind === "relative") {
|
|
1328
|
+
return { start: (anchorNow ?? Date.now()) - w.size * (w.unit === "w" ? 7 : 1) * DAY, end: Number.MAX_SAFE_INTEGER };
|
|
1156
1329
|
}
|
|
1330
|
+
const start = civilMidnight(w.start);
|
|
1331
|
+
const end = w.end !== void 0 ? nextCivilMidnight(w.end) - 1 : Number.MAX_SAFE_INTEGER;
|
|
1332
|
+
return { start, end };
|
|
1333
|
+
}
|
|
1334
|
+
function effectiveTimeWindow(createdAt, window, range, anchorNow) {
|
|
1335
|
+
const resolved = range ?? windowRange(window, anchorNow);
|
|
1336
|
+
if (resolved) return createdAt >= resolved.start && createdAt <= resolved.end;
|
|
1157
1337
|
return true;
|
|
1158
1338
|
}
|
|
1159
1339
|
function latestActivity(rows) {
|
|
@@ -1163,7 +1343,7 @@ function latestActivity(rows) {
|
|
|
1163
1343
|
}
|
|
1164
1344
|
function windowAnchor(selected, parsed, options2) {
|
|
1165
1345
|
if (options2.anchorNow !== void 0) return options2.anchorNow;
|
|
1166
|
-
if (options2.anchor === "latest-activity" && parsed.
|
|
1346
|
+
if (options2.anchor === "latest-activity" && parsed.window?.kind === "relative") return latestActivity(selected);
|
|
1167
1347
|
return void 0;
|
|
1168
1348
|
}
|
|
1169
1349
|
var defaultEventStore = {
|
|
@@ -1233,9 +1413,11 @@ function matchesFilters(row, filters, noteTags) {
|
|
|
1233
1413
|
const negate = group[0].negate;
|
|
1234
1414
|
const raw = factTagValue(row, key, noteTags);
|
|
1235
1415
|
const rowValues = Array.isArray(raw) ? raw : raw !== void 0 ? [raw] : [];
|
|
1236
|
-
const hit =
|
|
1237
|
-
(
|
|
1238
|
-
(
|
|
1416
|
+
const hit = rowValues.some(
|
|
1417
|
+
(value) => group.some(
|
|
1418
|
+
(f) => f.values.some(
|
|
1419
|
+
(a) => a.wildcard ? value.startsWith(a.value) : value === a.value
|
|
1420
|
+
)
|
|
1239
1421
|
)
|
|
1240
1422
|
);
|
|
1241
1423
|
return negate ? !hit : hit;
|
|
@@ -1245,8 +1427,8 @@ function dimValue(row, dim, noteTags) {
|
|
|
1245
1427
|
if (dim === "day") return localDateString(row.timestamp);
|
|
1246
1428
|
if (dim === "week") {
|
|
1247
1429
|
const d = new Date(row.timestamp);
|
|
1248
|
-
const monday = new Date(
|
|
1249
|
-
return
|
|
1430
|
+
const monday = new Date(d.getFullYear(), d.getMonth(), d.getDate() - (d.getDay() + 6) % 7);
|
|
1431
|
+
return localDateString(monday.getTime());
|
|
1250
1432
|
}
|
|
1251
1433
|
if (dim === "session") return row.resultId;
|
|
1252
1434
|
const raw = factTagValue(row, dim, noteTags);
|
|
@@ -1372,9 +1554,9 @@ var QueryService = class {
|
|
|
1372
1554
|
for (const blockContentId of blockIds) collect(await this.store.getEventsByContent(blockContentId));
|
|
1373
1555
|
for (const noteId of noteIds) collect(await this.store.getEventsForNote(noteId));
|
|
1374
1556
|
let groups = [...byResult.entries()].filter(([, rows]) => rows.length > 0);
|
|
1375
|
-
if (parsed.
|
|
1557
|
+
if (parsed.window) {
|
|
1376
1558
|
groups = groups.filter(
|
|
1377
|
-
([, rows]) => effectiveTimeWindow(rows[0].timestamp, parsed.
|
|
1559
|
+
([, rows]) => effectiveTimeWindow(rows[0].timestamp, parsed.window, void 0, options2.anchorNow)
|
|
1378
1560
|
);
|
|
1379
1561
|
}
|
|
1380
1562
|
groups.sort((a, b) => b[1][0].timestamp - a[1][0].timestamp);
|
|
@@ -1402,15 +1584,9 @@ var QueryService = class {
|
|
|
1402
1584
|
return this.runFindEffort(parsed);
|
|
1403
1585
|
}
|
|
1404
1586
|
let notes = [];
|
|
1405
|
-
|
|
1406
|
-
if (
|
|
1407
|
-
notes = notes.concat(await this.
|
|
1408
|
-
}
|
|
1409
|
-
if (scope === "collections" || scope === "feeds" || scope === "all") {
|
|
1410
|
-
let sNotes = this.staticNoteStore ? await this.staticNoteStore.getAllNotes() : [];
|
|
1411
|
-
if (scope === "collections") sNotes = sNotes.filter((n) => n.sourceId?.startsWith("collection:"));
|
|
1412
|
-
if (scope === "feeds") sNotes = sNotes.filter((n) => n.sourceId?.startsWith("feed:"));
|
|
1413
|
-
notes = notes.concat(sNotes);
|
|
1587
|
+
notes = notes.concat(await this.noteStore.getAllNotes());
|
|
1588
|
+
if (this.staticNoteStore) {
|
|
1589
|
+
notes = notes.concat(await this.staticNoteStore.getAllNotes());
|
|
1414
1590
|
}
|
|
1415
1591
|
const selectedCount = notes.length;
|
|
1416
1592
|
const anchorNow = windowAnchor(notes, parsed, options2);
|
|
@@ -1456,8 +1632,8 @@ var QueryService = class {
|
|
|
1456
1632
|
});
|
|
1457
1633
|
}
|
|
1458
1634
|
}
|
|
1459
|
-
if (parsed.
|
|
1460
|
-
notes = notes.filter((n) => effectiveTimeWindow(n.createdAt, parsed.
|
|
1635
|
+
if (parsed.window || options2.range) {
|
|
1636
|
+
notes = notes.filter((n) => effectiveTimeWindow(n.createdAt, parsed.window, options2.range, anchorNow));
|
|
1461
1637
|
}
|
|
1462
1638
|
if (parsed.join) {
|
|
1463
1639
|
const joined = await this.applyMetricJoin(parsed, notes, []);
|
|
@@ -1470,15 +1646,9 @@ var QueryService = class {
|
|
|
1470
1646
|
*/
|
|
1471
1647
|
async runFindBlock(parsed, options2 = {}) {
|
|
1472
1648
|
let blocks = [];
|
|
1473
|
-
|
|
1474
|
-
if (
|
|
1475
|
-
blocks = blocks.concat(await this.
|
|
1476
|
-
}
|
|
1477
|
-
if (scope === "collections" || scope === "feeds" || scope === "all") {
|
|
1478
|
-
let sBlocks = this.staticBlockStore ? await this.staticBlockStore.getAllBlocks() : [];
|
|
1479
|
-
if (scope === "collections") sBlocks = sBlocks.filter((b) => b.sourceId?.startsWith("collection:"));
|
|
1480
|
-
if (scope === "feeds") sBlocks = sBlocks.filter((b) => b.sourceId?.startsWith("feed:"));
|
|
1481
|
-
blocks = blocks.concat(sBlocks);
|
|
1649
|
+
blocks = blocks.concat(await this.blockStore.getAllBlocks());
|
|
1650
|
+
if (this.staticBlockStore) {
|
|
1651
|
+
blocks = blocks.concat(await this.staticBlockStore.getAllBlocks());
|
|
1482
1652
|
}
|
|
1483
1653
|
const selectedCount = blocks.length;
|
|
1484
1654
|
const anchorNow = windowAnchor(blocks, parsed, options2);
|
|
@@ -1518,8 +1688,8 @@ var QueryService = class {
|
|
|
1518
1688
|
blocks = blocks.filter((b) => matchingNoteIds.has(b.noteId));
|
|
1519
1689
|
}
|
|
1520
1690
|
}
|
|
1521
|
-
if (parsed.
|
|
1522
|
-
blocks = blocks.filter((b) => effectiveTimeWindow(b.createdAt, parsed.
|
|
1691
|
+
if (parsed.window || options2.range) {
|
|
1692
|
+
blocks = blocks.filter((b) => effectiveTimeWindow(b.createdAt, parsed.window, options2.range, anchorNow));
|
|
1523
1693
|
}
|
|
1524
1694
|
if (parsed.join) {
|
|
1525
1695
|
const joined = await this.applyMetricJoin(parsed, [], blocks);
|
|
@@ -1572,10 +1742,8 @@ var QueryService = class {
|
|
|
1572
1742
|
};
|
|
1573
1743
|
if (parsed.error) return empty;
|
|
1574
1744
|
if (parsed.join) return this.runJoined(parsed, options2);
|
|
1575
|
-
const
|
|
1576
|
-
const
|
|
1577
|
-
const end = options2.rangeEnd ?? Number.MAX_SAFE_INTEGER;
|
|
1578
|
-
const eventRows = windowed ? await this.store.getEventsByTimeRange(start, end) : await this.store.scanAll();
|
|
1745
|
+
const range = options2.rangeStart !== void 0 || options2.rangeEnd !== void 0 ? { start: options2.rangeStart ?? 0, end: options2.rangeEnd ?? Number.MAX_SAFE_INTEGER } : windowRange(parsed.window);
|
|
1746
|
+
const eventRows = range ? await this.store.getEventsByTimeRange(range.start, range.end) : await this.store.scanAll();
|
|
1579
1747
|
const candidates = eventRows.flatMap(projectEventToFacts).filter((row) => row.metricKey === parsed.metric);
|
|
1580
1748
|
const touchesTags = parsed.filters.some((f) => f.key === "tags") || parsed.groupBy.includes("tags");
|
|
1581
1749
|
const noteTags = await this.loadNoteTags(candidates, touchesTags);
|
|
@@ -1593,8 +1761,24 @@ var QueryService = class {
|
|
|
1593
1761
|
const timeDim = parsed.groupBy.find((d) => d === "day" || d === "week");
|
|
1594
1762
|
const tagDims = parsed.groupBy.filter((d) => d !== "day" && d !== "week");
|
|
1595
1763
|
const bucketMs = timeDim ? (timeDim === "week" ? 7 : 1) * DAY : parsed.rollup ? parsed.rollup.size * (parsed.rollup.unit === "w" ? 7 : 1) * DAY : null;
|
|
1596
|
-
const
|
|
1764
|
+
const civilDay = (ts) => {
|
|
1765
|
+
const d = new Date(ts);
|
|
1766
|
+
return Math.floor(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()) / DAY);
|
|
1767
|
+
};
|
|
1768
|
+
const bucketKey = (ts) => {
|
|
1769
|
+
if (timeDim === "day") return civilDay(ts);
|
|
1770
|
+
if (timeDim === "week") {
|
|
1771
|
+
const d = new Date(ts);
|
|
1772
|
+
return civilDay(ts) - (d.getDay() + 6) % 7;
|
|
1773
|
+
}
|
|
1774
|
+
return bucketMs ? Math.floor(ts / bucketMs) : 0;
|
|
1775
|
+
};
|
|
1597
1776
|
const bucketCount = bucketMs ? new Set(matched.map((p) => bucketKey(p.timestamp))).size : matched.length ? 1 : 0;
|
|
1777
|
+
const civilBucketInstant = (anchor, dim) => {
|
|
1778
|
+
const d = new Date(anchor);
|
|
1779
|
+
const back = dim === "week" ? (d.getDay() + 6) % 7 : 0;
|
|
1780
|
+
return new Date(d.getFullYear(), d.getMonth(), d.getDate() - back, 12).getTime();
|
|
1781
|
+
};
|
|
1598
1782
|
const { unit: targetUnit, convert: shouldConvert } = resolveDisplayUnit(matched, {
|
|
1599
1783
|
directive: parsed.displayUnit,
|
|
1600
1784
|
preferred: options2.preferredUnit
|
|
@@ -1619,7 +1803,7 @@ var QueryService = class {
|
|
|
1619
1803
|
(m) => toDisplayValue(m.value, m.unit ?? m.metricUnit, shouldConvert ? targetUnit : void 0)
|
|
1620
1804
|
);
|
|
1621
1805
|
return {
|
|
1622
|
-
ts: bucketMs ? b * bucketMs + bucketMs / 2 : Math.min(...members.map((m) => m.timestamp)),
|
|
1806
|
+
ts: timeDim ? civilBucketInstant(members[0].timestamp, timeDim) : bucketMs ? b * bucketMs + bucketMs / 2 : Math.min(...members.map((m) => m.timestamp)),
|
|
1623
1807
|
value: Math.round(aggregate(values, parsed.agg, members, shouldConvert ? targetUnit : void 0) * 100) / 100
|
|
1624
1808
|
};
|
|
1625
1809
|
});
|
|
@@ -1653,17 +1837,14 @@ var QueryService = class {
|
|
|
1653
1837
|
raw: "",
|
|
1654
1838
|
target: join.target,
|
|
1655
1839
|
filters: join.filters,
|
|
1656
|
-
|
|
1657
|
-
last: join.last
|
|
1840
|
+
window: join.last ? { kind: "relative", size: join.last.size, unit: join.last.unit } : void 0
|
|
1658
1841
|
});
|
|
1659
1842
|
const contentIds = await this.contentIdsFromFindResult(findResult);
|
|
1660
1843
|
if (contentIds.size === 0) return empty;
|
|
1661
1844
|
let facts = await this.deriveMetricFacts(contentIds, parsed.metric);
|
|
1662
|
-
|
|
1663
|
-
if (
|
|
1664
|
-
|
|
1665
|
-
const end = options2.rangeEnd ?? Number.MAX_SAFE_INTEGER;
|
|
1666
|
-
facts = facts.filter((f) => f.timestamp >= start && f.timestamp <= end);
|
|
1845
|
+
const joinRange = options2.rangeStart !== void 0 || options2.rangeEnd !== void 0 ? { start: options2.rangeStart ?? 0, end: options2.rangeEnd ?? Number.MAX_SAFE_INTEGER } : windowRange(parsed.window);
|
|
1846
|
+
if (joinRange) {
|
|
1847
|
+
facts = facts.filter((f) => f.timestamp >= joinRange.start && f.timestamp <= joinRange.end);
|
|
1667
1848
|
}
|
|
1668
1849
|
const touchesTags = parsed.filters.some((f) => f.key === "tags") || parsed.groupBy.includes("tags");
|
|
1669
1850
|
const noteTags = await this.loadNoteTags(facts, touchesTags);
|
|
@@ -2068,15 +2249,15 @@ sum:$metric{} rollup:1w
|
|
|
2068
2249
|
}
|
|
2069
2250
|
|
|
2070
2251
|
// src/version.ts
|
|
2071
|
-
var VERSION = "0.
|
|
2072
|
-
var GIT_SHA = "
|
|
2073
|
-
var BUILD_TIME = "2026-08-
|
|
2252
|
+
var VERSION = "0.9.40";
|
|
2253
|
+
var GIT_SHA = "2462ec9";
|
|
2254
|
+
var BUILD_TIME = "2026-08-26T01:04:47.412Z";
|
|
2074
2255
|
var SEMVER = {
|
|
2075
|
-
version: "0.
|
|
2076
|
-
gitSha: "
|
|
2077
|
-
buildTime: "2026-08-
|
|
2256
|
+
version: "0.9.40",
|
|
2257
|
+
gitSha: "2462ec9",
|
|
2258
|
+
buildTime: "2026-08-26T01:04:47.412Z"
|
|
2078
2259
|
};
|
|
2079
2260
|
|
|
2080
|
-
export { BUILD_TIME, DASHBOARD_GRID_MAX_COLS, DASHBOARD_WIDGET_TYPES, DEFAULT_DASHBOARD_TITLE, DISCIPLINE_FACTORS, EFFORT_DISCIPLINES, GIT_SHA, KG_PER_LB, PLANNED_WIDGET_TYPES, QueryService, SEMVER, UNIT_FAMILIES, VERSION, WQL_AGGREGATORS, WQL_CALC_TARGETS, WQL_COMPARISON_OPS, WQL_CONTENT_FILTER_KEYS, WQL_DISPLAY_UNITS, WQL_FIND_TARGETS, WQL_GRAINS, WQL_INTENSITY_TIERS, WQL_KEYWORDS, WQL_METRIC_AGGREGATES, WQL_METRIC_FAMILIES, WQL_RESULT_PLANES, WQL_ROLLUP_PERIODS, WQL_ROWS_SCOPE_KEYS, WQL_ROWS_TARGETS,
|
|
2261
|
+
export { BUILD_TIME, DASHBOARD_GRID_MAX_COLS, DASHBOARD_WIDGET_TYPES, DEFAULT_DASHBOARD_TITLE, DISCIPLINE_FACTORS, EFFORT_DISCIPLINES, GIT_SHA, KG_PER_LB, PLANNED_WIDGET_TYPES, QueryService, SEMVER, UNIT_FAMILIES, VERSION, WQL_AGGREGATORS, WQL_CALC_TARGETS, WQL_COMPARISON_OPS, WQL_CONTENT_FILTER_KEYS, WQL_DISPLAY_UNITS, WQL_FIND_TARGETS, WQL_GRAINS, WQL_INTENSITY_TIERS, WQL_KEYWORDS, WQL_METRIC_AGGREGATES, WQL_METRIC_FAMILIES, WQL_RESULT_PLANES, WQL_ROLLUP_PERIODS, WQL_ROWS_SCOPE_KEYS, WQL_ROWS_TARGETS, WQL_SOURCES, WQL_SOURCE_VALUES, WQL_TAG_KEYS, WQL_VIRTUAL_DIMS, buildDashboardDocument, buildDashboardScaffold, convert, dashboardSlug, defaultTokenValues, disciplineFactorFor, extractDashboardTokens, extractFrontmatterTags, getList, getScalar, getUnitFamily, isAggregateQuery, isDashboardMeta, isDashboardWidgetType, isEffortDiscipline, isFindQuery, isProposedMetric, isRowsQuery, normalizeSummaryFacts, normalizeWql, parseDashboardNote, parseFlatProperties, parseFrontmatter, parseFrontmatterBody, parseFrontmatterCategories, parseFrontmatterProps, parseQuery, parseQueryWidgetSuffix, parseWqlSuffixes, projectEventToFacts, referencedTokens, resolveCanonicalMetricKey, resolveDisplayUnit, resolveWidgetType, serializeFrontmatter, setDashboardTokenValue, splitAtWhere, splitWidgetBody, staticNotesFromBlocks, staticTagIndexFromBlocks, stripFrontmatter, substituteTokens, toEventRows, toSummaryEventRows, unknownTokensMessage, unknownWidgetTypeMessage, wql, wqlCompletion, wqlCompletionSource, wqlHighlightStyle, wqlLanguage, parser as wqlParser, wql_parser_terms_exports as wqlTerms };
|
|
2081
2262
|
//# sourceMappingURL=index.mjs.map
|
|
2082
2263
|
//# sourceMappingURL=index.mjs.map
|