@inixiative/json-rules 2.26.0 → 2.27.0

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/README.md CHANGED
@@ -279,7 +279,7 @@ toSql(rule, { now });
279
279
  | Option | Default | Governs |
280
280
  | --- | --- | --- |
281
281
  | `now` | — (required when a relative/period expression is present) | the anchor instant |
282
- | `timeZone` | `'UTC'` | how `now` and period boundaries localize |
282
+ | `timeZone` | `'UTC'` | how `now` and period boundaries localize — a zone name, or a value source read from context or bindings |
283
283
  | `weekStart` | `'monday'` (ISO / isoWeek) | start of `week` for `this`/`last`/`next` |
284
284
 
285
285
  Compilers resolve expressions to concrete `Date` bounds at compile time, so
@@ -369,6 +369,67 @@ like any absent field.
369
369
  `toSql()` keeps `path: '$.x'` as a same-row column comparison. Every other scope ref — a
370
370
  `$$.` path or any prefixed `field` — is check-only; both compilers throw.
371
371
 
372
+ ### Offsets and Unit Amounts
373
+
374
+ An `offset` moves the comparison value. It is a value source of its own, with the comparison
375
+ value's contract: `{ value }`, `{ path }` (`$.` from the row, bare from context) or `{ bind }`
376
+ (with `bindOptional`). A field rule's offset reads a number, added to the comparison value; a
377
+ date rule's reads a rolling shift (`{ ago }` / `{ ahead }`) anchored on the comparison value
378
+ instead of `now`:
379
+
380
+ ```ts
381
+ // net score at or under par: gross <= par + handicap
382
+ { field: 'grossScore', operator: Operator.lessThanEquals, path: '$.par',
383
+ offset: { path: '$.handicap' } }
384
+
385
+ // within budget plus a tolerance supplied at evaluation
386
+ { field: 'spend', operator: Operator.lessThanEquals, path: '$.budget',
387
+ offset: { bind: 'tolerance' } }
388
+
389
+ // completed within 30 days before the created date
390
+ { field: 'completedAt', dateOperator: DateOperator.onOrAfter, path: '$.createdDate',
391
+ offset: { value: { ago: { days: 30 } } } }
392
+
393
+ // on or after the fifth of this month — an edge the expression grammar can't name alone
394
+ { field: 'paidAt', dateOperator: DateOperator.onOrAfter, value: { start: { this: 'month' } },
395
+ offset: { value: { ahead: { days: 4 } } } }
396
+ ```
397
+
398
+ `resolveBindings` resolves an offset's bind as it does the comparison value's, and
399
+ `bindingNames` / `requiredBindings` list it. A date offset read per row (a column holding
400
+ `{ ago: … }`) is check-only; to size a shift from the row, read the amount instead.
401
+
402
+ Any relative-date unit — in a `value` expression or an offset's rolling shift — is a number or a
403
+ value source: `{ path }` from the row (`$.`) or context, `{ bind }`, or `{ value }`. A relative
404
+ window can take its size from the row it judges:
405
+
406
+ ```ts
407
+ // quiet for longer than this incident's rule allows
408
+ { field: 'lastBreachedAt', dateOperator: DateOperator.before,
409
+ value: { ago: { seconds: { path: '$.platformAlertRule.autoResolveAfterSeconds' } } } }
410
+ ```
411
+
412
+ Offsets apply to the comparison operators (`equals` … `greaterThanEquals`, `before` …
413
+ `notAfter`) and to both ends of `between` / `notBetween`. Units apply as Postgres applies an
414
+ interval to a wall-clock time in the evaluation's `timeZone` (UTC by default): months (years,
415
+ quarters, months), then days (weeks, days), then time — so every rail lands on the same instant
416
+ at a month end and across a DST change. Calendar units (years … days) are whole numbers and every
417
+ unit is non-negative: a literal that isn't fails validation, and a value read from data that
418
+ isn't reads as null. A null comparison value, offset or magnitude, or a range missing an end,
419
+ matches nothing (SQL's NULL arithmetic); a negation keeps null fields only. Numeric offsets add
420
+ in double precision on every rail.
421
+
422
+ | | `check()` | `toSql()` | `toPrisma()` |
423
+ | --- | --- | --- | --- |
424
+ | literal, bound or context offset / amount | yes | resolved to a parameter | resolved to a value |
425
+ | `$.` numeric offset or unit amount | yes | `col + n` / `col ± make_interval(…)` | throws |
426
+ | `$.` date offset (a stored `{ ago }`) | yes | throws | throws |
427
+ | `$$.` anything | yes | throws | throws |
428
+
429
+ `checkRuleAgainstLens` gates offset and magnitude refs like `path` (they must resolve through
430
+ the lens and read a number), and an offset must fit the field's kind: a number on a numeric
431
+ field, a rolling shift on a DateTime.
432
+
372
433
  ## Rule Introspection
373
434
 
374
435
  Reading a stored rule's own content — which values it names, which bindings it needs — is
@@ -546,6 +607,8 @@ Not every backend supports every rule shape.
546
607
  | `dayIn` / `dayNotIn` | Yes | No | Yes |
547
608
  | Windowing (`orderBy` / `take` / `skip`) | Yes | Extremal (`take:1`, aligned) | No |
548
609
  | `path: '$.field'` current-element / same-row refs | Yes | No | Yes |
610
+ | `offset` and unit amounts — value, bind or context | Yes | Yes | Yes |
611
+ | `offset` and unit amounts — `$.` row refs | Yes | No | Yes (not a date offset's) |
549
612
  | `$$.` scope refs and `$`-prefixed `field` | Yes | No | No |
550
613
 
551
614
  ### NULL Semantics