@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.
@@ -160,6 +160,15 @@
160
160
  },
161
161
  {
162
162
  "$ref": "#/$defs/schemaOperatorPhrase"
163
+ },
164
+ {
165
+ "$ref": "#/$defs/seriesSpecPhrase"
166
+ },
167
+ {
168
+ "$ref": "#/$defs/asOfPhrase"
169
+ },
170
+ {
171
+ "$ref": "#/$defs/timeBucketPhrase"
163
172
  }
164
173
  ]
165
174
  },
@@ -607,6 +616,226 @@
607
616
  "maxItems": 2
608
617
  }
609
618
  },
619
+ "seriesSpec": {
620
+ "description": "A $resample or $rolling spec (QUERY-FORMAT.md section 8.16): a VERBATIM literal, never an expression, so the width, the aggregate, the fill policy, the wall clock and the row selectors are read once when the query compiles. Closed: an unknown member is JQ0003. 'every'/'width' is an ISO 8601 duration or a count of milliseconds; 'origin'/'start'/'end' are epoch milliseconds or an RFC 3339 string; 'at'/'value' are singular paths where '$' reads as the ROW. A named 'zone' needs an injected zoneProvider - this suite bundles no time-zone database.",
621
+ "type": "object",
622
+ "properties": {
623
+ "every": {
624
+ "$ref": "#/$defs/seriesSpan"
625
+ },
626
+ "width": {
627
+ "$ref": "#/$defs/seriesSpan"
628
+ },
629
+ "origin": {
630
+ "$ref": "#/$defs/seriesInstant"
631
+ },
632
+ "start": {
633
+ "$ref": "#/$defs/seriesInstant"
634
+ },
635
+ "end": {
636
+ "$ref": "#/$defs/seriesInstant"
637
+ },
638
+ "aggregate": {
639
+ "enum": [
640
+ "sum",
641
+ "mean",
642
+ "min",
643
+ "max",
644
+ "first",
645
+ "last",
646
+ "count"
647
+ ]
648
+ },
649
+ "fill": {
650
+ "enum": [
651
+ "omit",
652
+ "null",
653
+ "zero",
654
+ "locf",
655
+ "linear"
656
+ ]
657
+ },
658
+ "minPeriods": {
659
+ "type": "integer",
660
+ "minimum": 1
661
+ },
662
+ "at": {
663
+ "$ref": "#/$defs/rowSelector"
664
+ },
665
+ "value": {
666
+ "$ref": "#/$defs/rowSelector"
667
+ },
668
+ "zone": {
669
+ "type": "string",
670
+ "minLength": 1
671
+ },
672
+ "offset": {
673
+ "type": "number"
674
+ },
675
+ "disambiguation": {
676
+ "enum": [
677
+ "reject",
678
+ "earlier",
679
+ "later"
680
+ ]
681
+ }
682
+ },
683
+ "additionalProperties": false
684
+ },
685
+ "seriesSpan": {
686
+ "description": "A width: an ISO 8601 duration string ('PT1H', 'P1M') or a count of milliseconds. A duration mixing the calendar and clock families ('P1MT1H') is refused by the compiler.",
687
+ "anyOf": [
688
+ {
689
+ "type": "string",
690
+ "minLength": 1
691
+ },
692
+ {
693
+ "type": "number",
694
+ "exclusiveMinimum": 0
695
+ }
696
+ ]
697
+ },
698
+ "seriesInstant": {
699
+ "description": "An instant: epoch milliseconds, or an RFC 3339 string. A full-time names no instant and is refused by the compiler.",
700
+ "anyOf": [
701
+ {
702
+ "type": "number"
703
+ },
704
+ {
705
+ "type": "string",
706
+ "minLength": 1
707
+ }
708
+ ]
709
+ },
710
+ "rowSelector": {
711
+ "description": "Where a member lives in a source row: a singular JSONPath whose '$' is the ROW rather than the document ('$.on', \"$['recorded at']\", '$.meta.at'). Wildcards, descendants and filters are refused (JQ0003) - a selector names one member.",
712
+ "type": "string",
713
+ "pattern": "^\\$[.\\[]"
714
+ },
715
+ "seriesSpecPhrase": {
716
+ "description": "$resample / $rolling [series, spec]: an operand expression and a VERBATIM spec literal. $resample returns sorted {at, value, count} buckets one per 'every'; $rolling returns one row per input instant over a window measured in time. (LLM profile: a member-name constraint is relaxed here; the canonical schema enforces it)",
717
+ "type": "object",
718
+ "minProperties": 1,
719
+ "maxProperties": 1,
720
+ "additionalProperties": {
721
+ "type": "array",
722
+ "prefixItems": [
723
+ {
724
+ "$ref": "#/$defs/expression"
725
+ },
726
+ {
727
+ "$ref": "#/$defs/seriesSpec"
728
+ }
729
+ ],
730
+ "items": false,
731
+ "minItems": 2,
732
+ "maxItems": 2
733
+ }
734
+ },
735
+ "asOfSpec": {
736
+ "description": "The $asof spec (QUERY-FORMAT.md section 8.16): a VERBATIM literal. 'by' joins within groups; 'leftAt'/'rightAt' say where each side keeps its instant when the two documents spell it differently. Closed: an unknown member is JQ0003.",
737
+ "type": "object",
738
+ "properties": {
739
+ "direction": {
740
+ "enum": [
741
+ "backward",
742
+ "forward",
743
+ "nearest"
744
+ ]
745
+ },
746
+ "tolerance": {
747
+ "$ref": "#/$defs/seriesSpan"
748
+ },
749
+ "by": {
750
+ "$ref": "#/$defs/rowSelector"
751
+ },
752
+ "leftAt": {
753
+ "$ref": "#/$defs/rowSelector"
754
+ },
755
+ "rightAt": {
756
+ "$ref": "#/$defs/rowSelector"
757
+ }
758
+ },
759
+ "additionalProperties": false
760
+ },
761
+ "asOfPhrase": {
762
+ "description": "$asof [left, right] or [left, right, spec]: two operand expressions and an optional VERBATIM spec literal. Answers one {left, right, distance} per left row; a left row with no match keeps right: null and stays in the answer.",
763
+ "type": "object",
764
+ "properties": {
765
+ "$asof": {
766
+ "type": "array",
767
+ "prefixItems": [
768
+ {
769
+ "$ref": "#/$defs/expression"
770
+ },
771
+ {
772
+ "$ref": "#/$defs/expression"
773
+ },
774
+ {
775
+ "$ref": "#/$defs/asOfSpec"
776
+ }
777
+ ],
778
+ "items": false,
779
+ "minItems": 2,
780
+ "maxItems": 3
781
+ }
782
+ },
783
+ "required": [
784
+ "$asof"
785
+ ],
786
+ "additionalProperties": false
787
+ },
788
+ "calendarContext": {
789
+ "description": "$time-bucket's optional fourth position: the wall clock its boundaries fall on. UTC is the default and needs nothing; a named 'zone' needs an injected zoneProvider.",
790
+ "type": "object",
791
+ "properties": {
792
+ "zone": {
793
+ "type": "string",
794
+ "minLength": 1
795
+ },
796
+ "offset": {
797
+ "type": "number"
798
+ },
799
+ "disambiguation": {
800
+ "enum": [
801
+ "reject",
802
+ "earlier",
803
+ "later"
804
+ ]
805
+ }
806
+ },
807
+ "additionalProperties": false
808
+ },
809
+ "timeBucketPhrase": {
810
+ "description": "$time-bucket [at, every] / [at, every, origin] / [at, every, origin, context]: the instant labelling the bucket 'at' falls in. The first three positions are operand expressions ('origin' may be null for the ladder's own default); the fourth is a VERBATIM calendar context literal.",
811
+ "type": "object",
812
+ "properties": {
813
+ "$time-bucket": {
814
+ "type": "array",
815
+ "prefixItems": [
816
+ {
817
+ "$ref": "#/$defs/expression"
818
+ },
819
+ {
820
+ "$ref": "#/$defs/expression"
821
+ },
822
+ {
823
+ "$ref": "#/$defs/expression"
824
+ },
825
+ {
826
+ "$ref": "#/$defs/calendarContext"
827
+ }
828
+ ],
829
+ "items": false,
830
+ "minItems": 2,
831
+ "maxItems": 4
832
+ }
833
+ },
834
+ "required": [
835
+ "$time-bucket"
836
+ ],
837
+ "additionalProperties": false
838
+ },
610
839
  "asClause": {
611
840
  "description": "The $as clause: variable-name to JSON Schema literal members, applied per tuple after $for/$let binding and before $where. Each name must be bound by the same phrase's $for (including $at names) or $let - a compiler rule (JQ0005); each binding validates per item at runtime (JQ2008). (LLM profile: a member-name constraint is relaxed here; the canonical schema enforces it)",
612
841
  "type": "object",
@@ -93,7 +93,10 @@
93
93
  { "$ref": "#/$defs/geohashPhrase" },
94
94
  { "$ref": "#/$defs/ternaryOperatorPhrase" },
95
95
  { "$ref": "#/$defs/replacePhrase" },
96
- { "$ref": "#/$defs/schemaOperatorPhrase" }
96
+ { "$ref": "#/$defs/schemaOperatorPhrase" },
97
+ { "$ref": "#/$defs/seriesSpecPhrase" },
98
+ { "$ref": "#/$defs/asOfPhrase" },
99
+ { "$ref": "#/$defs/timeBucketPhrase" }
97
100
  ]
98
101
  },
99
102
  "mapConstructor": {
@@ -308,7 +311,7 @@
308
311
  "$index-of", "$range", "$get",
309
312
  "$start-of", "$end-of", "$date-format",
310
313
  "$distance", "$within", "$bbox-intersects", "$geo-simplify",
311
- "$similarity", "$default"
314
+ "$similarity", "$overlaps", "$default"
312
315
  ]
313
316
  },
314
317
  "additionalProperties": {
@@ -433,6 +436,123 @@
433
436
  "maxItems": 2
434
437
  }
435
438
  },
439
+ "seriesSpec": {
440
+ "description": "A $resample or $rolling spec (QUERY-FORMAT.md section 8.16): a VERBATIM literal, never an expression, so the width, the aggregate, the fill policy, the wall clock and the row selectors are read once when the query compiles. Closed: an unknown member is JQ0003. 'every'/'width' is an ISO 8601 duration or a count of milliseconds; 'origin'/'start'/'end' are epoch milliseconds or an RFC 3339 string; 'at'/'value' are singular paths where '$' reads as the ROW. A named 'zone' needs an injected zoneProvider - this suite bundles no time-zone database.",
441
+ "type": "object",
442
+ "properties": {
443
+ "every": { "$ref": "#/$defs/seriesSpan" },
444
+ "width": { "$ref": "#/$defs/seriesSpan" },
445
+ "origin": { "$ref": "#/$defs/seriesInstant" },
446
+ "start": { "$ref": "#/$defs/seriesInstant" },
447
+ "end": { "$ref": "#/$defs/seriesInstant" },
448
+ "aggregate": { "enum": ["sum", "mean", "min", "max", "first", "last", "count"] },
449
+ "fill": { "enum": ["omit", "null", "zero", "locf", "linear"] },
450
+ "minPeriods": { "type": "integer", "minimum": 1 },
451
+ "at": { "$ref": "#/$defs/rowSelector" },
452
+ "value": { "$ref": "#/$defs/rowSelector" },
453
+ "zone": { "type": "string", "minLength": 1 },
454
+ "offset": { "type": "number" },
455
+ "disambiguation": { "enum": ["reject", "earlier", "later"] }
456
+ },
457
+ "additionalProperties": false
458
+ },
459
+ "seriesSpan": {
460
+ "description": "A width: an ISO 8601 duration string ('PT1H', 'P1M') or a count of milliseconds. A duration mixing the calendar and clock families ('P1MT1H') is refused by the compiler.",
461
+ "anyOf": [
462
+ { "type": "string", "minLength": 1 },
463
+ { "type": "number", "exclusiveMinimum": 0 }
464
+ ]
465
+ },
466
+ "seriesInstant": {
467
+ "description": "An instant: epoch milliseconds, or an RFC 3339 string. A full-time names no instant and is refused by the compiler.",
468
+ "anyOf": [
469
+ { "type": "number" },
470
+ { "type": "string", "minLength": 1 }
471
+ ]
472
+ },
473
+ "rowSelector": {
474
+ "description": "Where a member lives in a source row: a singular JSONPath whose '$' is the ROW rather than the document ('$.on', \"$['recorded at']\", '$.meta.at'). Wildcards, descendants and filters are refused (JQ0003) - a selector names one member.",
475
+ "type": "string",
476
+ "pattern": "^\\$[.\\[]"
477
+ },
478
+ "seriesSpecPhrase": {
479
+ "description": "$resample / $rolling [series, spec]: an operand expression and a VERBATIM spec literal. $resample returns sorted {at, value, count} buckets one per 'every'; $rolling returns one row per input instant over a window measured in time.",
480
+ "type": "object",
481
+ "minProperties": 1,
482
+ "maxProperties": 1,
483
+ "propertyNames": { "enum": ["$resample", "$rolling"] },
484
+ "additionalProperties": {
485
+ "type": "array",
486
+ "prefixItems": [
487
+ { "$ref": "#/$defs/expression" },
488
+ { "$ref": "#/$defs/seriesSpec" }
489
+ ],
490
+ "items": false,
491
+ "minItems": 2,
492
+ "maxItems": 2
493
+ }
494
+ },
495
+ "asOfSpec": {
496
+ "description": "The $asof spec (QUERY-FORMAT.md section 8.16): a VERBATIM literal. 'by' joins within groups; 'leftAt'/'rightAt' say where each side keeps its instant when the two documents spell it differently. Closed: an unknown member is JQ0003.",
497
+ "type": "object",
498
+ "properties": {
499
+ "direction": { "enum": ["backward", "forward", "nearest"] },
500
+ "tolerance": { "$ref": "#/$defs/seriesSpan" },
501
+ "by": { "$ref": "#/$defs/rowSelector" },
502
+ "leftAt": { "$ref": "#/$defs/rowSelector" },
503
+ "rightAt": { "$ref": "#/$defs/rowSelector" }
504
+ },
505
+ "additionalProperties": false
506
+ },
507
+ "asOfPhrase": {
508
+ "description": "$asof [left, right] or [left, right, spec]: two operand expressions and an optional VERBATIM spec literal. Answers one {left, right, distance} per left row; a left row with no match keeps right: null and stays in the answer.",
509
+ "type": "object",
510
+ "properties": {
511
+ "$asof": {
512
+ "type": "array",
513
+ "prefixItems": [
514
+ { "$ref": "#/$defs/expression" },
515
+ { "$ref": "#/$defs/expression" },
516
+ { "$ref": "#/$defs/asOfSpec" }
517
+ ],
518
+ "items": false,
519
+ "minItems": 2,
520
+ "maxItems": 3
521
+ }
522
+ },
523
+ "required": ["$asof"],
524
+ "additionalProperties": false
525
+ },
526
+ "calendarContext": {
527
+ "description": "$time-bucket's optional fourth position: the wall clock its boundaries fall on. UTC is the default and needs nothing; a named 'zone' needs an injected zoneProvider.",
528
+ "type": "object",
529
+ "properties": {
530
+ "zone": { "type": "string", "minLength": 1 },
531
+ "offset": { "type": "number" },
532
+ "disambiguation": { "enum": ["reject", "earlier", "later"] }
533
+ },
534
+ "additionalProperties": false
535
+ },
536
+ "timeBucketPhrase": {
537
+ "description": "$time-bucket [at, every] / [at, every, origin] / [at, every, origin, context]: the instant labelling the bucket 'at' falls in. The first three positions are operand expressions ('origin' may be null for the ladder's own default); the fourth is a VERBATIM calendar context literal.",
538
+ "type": "object",
539
+ "properties": {
540
+ "$time-bucket": {
541
+ "type": "array",
542
+ "prefixItems": [
543
+ { "$ref": "#/$defs/expression" },
544
+ { "$ref": "#/$defs/expression" },
545
+ { "$ref": "#/$defs/expression" },
546
+ { "$ref": "#/$defs/calendarContext" }
547
+ ],
548
+ "items": false,
549
+ "minItems": 2,
550
+ "maxItems": 4
551
+ }
552
+ },
553
+ "required": ["$time-bucket"],
554
+ "additionalProperties": false
555
+ },
436
556
  "asClause": {
437
557
  "description": "The $as clause: variable-name to JSON Schema literal members, applied per tuple after $for/$let binding and before $where. Each name must be bound by the same phrase's $for (including $at names) or $let - a compiler rule (JQ0005); each binding validates per item at runtime (JQ2008).",
438
558
  "type": "object",
@@ -433,6 +433,25 @@ const OPERATOR_ALIASES = {
433
433
  "$nearest-neighbours": "$orderby on a $similarity key, then $subsequence",
434
434
  "$nearest-neighbors": "$orderby on a $similarity key, then $subsequence",
435
435
  "$top-k": "$orderby then $subsequence", $embed: null, $normalize: null,
436
+ // time series: the spellings a writer arrives with from Timescale,
437
+ // pandas, SQL and kdb+. The bucketing family all points at the two
438
+ // operators that exist - the scalar label and the aggregating one -
439
+ // and the fill policies point at `$resample`, because a fill is a
440
+ // MEMBER of a resample spec rather than an operation of its own
441
+ $time_bucket: "$time-bucket", "$date-bin": "$time-bucket",
442
+ $date_bin: "$time-bucket", $bucket: "$time-bucket",
443
+ "$time-bucket-gapfill": "$resample with a 'fill'",
444
+ $gapfill: "$resample with a 'fill'", $locf: "$resample with fill 'locf'",
445
+ $interpolate: "$resample with fill 'linear'",
446
+ $downsample: "$resample", $upsample: "$resample with a 'fill'",
447
+ $rollup: "$resample", $groupbytime: "$resample",
448
+ "$moving-average": "$rolling with aggregate 'mean'",
449
+ $rollingwindow: "$rolling", "$time-window": "$rolling",
450
+ "$as-of": "$asof", "$asof-join": "$asof", $aj: "$asof",
451
+ "$merge-asof": "$asof", "$latest-at": "$asof",
452
+ $overlap: "$overlaps", "$interval-overlaps": "$overlaps",
453
+ $during: "$overlaps", $meets: null, $abuts: null,
454
+ $now: null, "$current-timestamp": null,
436
455
  };
437
456
 
438
457
  // JQ0002 for an unknown $-key, with a "did you mean" suggestion. A
@@ -577,6 +596,11 @@ function argCards(args) {
577
596
  return cards;
578
597
  }
579
598
 
599
+ // The operators that read a calendar boundary, and so may be given a
600
+ // named zone. `$asof` and `$overlaps` are pure instant arithmetic and
601
+ // have no clock to resolve.
602
+ const CLOCK_OPERATORS = new Set(['$time-bucket', '$resample', '$rolling']);
603
+
580
604
  // A registry operator call: arity and shape come uniformly from the
581
605
  // table's `params` descriptor (JQ0003), the static cardinality from its
582
606
  // `result` - individual operators never re-check structure.
@@ -590,6 +614,11 @@ function normalizeOperatorCall(key, entry, arg, docPath, opPath, scope, ctx) {
590
614
  // configurable through the compilation's limits (compileOp hands the
591
615
  // node through to the entry's compile)
592
616
  if (key === '$range' && ctx.limits !== null) node.limits = ctx.limits;
617
+ // the series operators resolve their wall clock when the query
618
+ // compiles, and a named zone needs the tzdb this suite does not bundle
619
+ // (SERIES D7). The provider is a compilation capability, like a
620
+ // collation, so it reaches the entry the same way $range's guard does
621
+ if (ctx.zoneProvider !== null && CLOCK_OPERATORS.has(key)) node.zoneProvider = ctx.zoneProvider;
593
622
  return Object.freeze(node);
594
623
  }
595
624
 
@@ -1258,6 +1287,19 @@ function validateNamedFunctions(value, what) {
1258
1287
  return value;
1259
1288
  }
1260
1289
 
1290
+ // Validate the injected time-zone provider (options.zoneProvider): the
1291
+ // two-question seam SERIES D7 fixes, because this suite bundles no
1292
+ // tzdb and a JSON document cannot carry one. Violations are host
1293
+ // programming errors (TypeError), like options.extensions.
1294
+ function validateZoneProvider(value) {
1295
+ if (value === null || typeof value !== 'object'
1296
+ || typeof value.toParts !== 'function' || typeof value.toEpoch !== 'function') {
1297
+ throw new TypeError('options.zoneProvider must be an object with'
1298
+ + ' toParts(epoch, zone) and toEpoch(parts, zone, disambiguation)');
1299
+ }
1300
+ return value;
1301
+ }
1302
+
1261
1303
  // The known limits (section 8.12). Only limits the engine actually
1262
1304
  // enforces are accepted - an accepted-but-unenforced limit would be a
1263
1305
  // silent false guarantee.
@@ -1423,6 +1465,7 @@ export function normalizeQuery(doc, options = {}) {
1423
1465
  const extensions = options.extensions == null ? null : validateExtensions(options.extensions);
1424
1466
  const functions = options.functions == null ? null : validateNamedFunctions(options.functions, 'functions');
1425
1467
  const collations = options.collations == null ? null : validateNamedFunctions(options.collations, 'collations');
1468
+ const zoneProvider = options.zoneProvider == null ? null : validateZoneProvider(options.zoneProvider);
1426
1469
  const limits = options.limits == null ? null : validateLimits(options.limits);
1427
1470
  const declaredExternals = options.externals == null
1428
1471
  ? null
@@ -1436,7 +1479,7 @@ export function normalizeQuery(doc, options = {}) {
1436
1479
  : { pathFunctions: options.pathFunctions };
1437
1480
  const ctx = {
1438
1481
  nextSlot: 1, externals: new Map(), compileTypeTest, extensions,
1439
- functions, collations, limits, pathOptions, declaredExternals,
1482
+ functions, collations, zoneProvider, limits, pathOptions, declaredExternals,
1440
1483
  // package-internal: set only by analyzeQuery (Appendix C.1); the
1441
1484
  // compile entry point never passes it
1442
1485
  analysis: options.analysis === true,