You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Temporal schedules — the during condition operator. Grants (and require() gates) can now be constrained to real schedules — recurrences, calendar ranges, unions of windows — via dtrexp expressions: .where('$.now during "T0900:1800 E1:5"') means weekdays, 09:00–18:00. Joins before / after / between in the Time operator family; works in string sugar, canonical JSON, combinators, gates, and on both the sync and async check paths.
.during(expression) chainer on grants — fluent shorthand that ANDs a ['$.now', 'during', …] leaf into the grant's condition at commit; composes with .where() regardless of call order, and repeated calls AND together.
Any date-like left side. Beyond $.now, the checked instant can come from context data ('$.booking.start during "…"') as a Date, epoch milliseconds, ISO string, or { epochMilliseconds } object. A non-date-like value evaluates false (fail-closed), never throws at check time.
$.now.epochMilliseconds — the raw instant, now part of the derived $.now.* fields.
The out_of_schedule deny reason + public permission.reason. When a check fails only because of a during schedule — it would be granted at a covered instant — the reason is 'out_of_schedule' instead of the generic 'condition_failed' / 'require_failed', so a UI can say "outside your access hours" and mean it ("granted, but not now"). If a value predicate also failed, the generic reason stands. The denial reason (previously event-only) is now also exposed as a public reason getter on Permission.
Error codes INVALID_DTREXP and DTREXP_NEVER_MATCHES. Expressions are validated at every grants entry point (chainer commit, constructor, setGrants, grants-list, require(), restore()): malformed ones throw with dtrexp's message and character position; parseable-but-never-matching ones (D30 M2) are rejected as authoring bugs.
Security
The during surface is bounded like the rest of the condition DSL: expression length cap (1000 chars), commit-time validation, and a bounded FIFO parse cache — untrusted serialized grants can't reach the check path unvalidated or grow memory through distinct expression strings.
Timezone comes from the reserved context.tz (IANA name), the same key $.now.* uses; with no tz, both evaluate in the system zone — one timezone story across the condition system.
Changed
AccessReason is now DenyReason. The type only ever describes a denial (undefined when granted), so it's named for what it is. AccessReason remains exported as a deprecated alias — existing imports keep compiling — and will be removed in v4.
Dependencies
Added dtrexp1.0.1 (exactly pinned, same author, zero transitive dependencies). Engines unchanged (Node ≥ 20).
Tooling
CI now enforces the full quality bar. The workflow runs typecheck → lint → build → cover across the Node 20/22/24 matrix and a dedicated mutation job (Stryker, on Node 24) — so 100% coverage and the mutation threshold are gated on every push and PR, not just build + test.
CI pins TZ=Europe/Istanbul so system-timezone default paths stay distinguishable from UTC in tests and mutation runs.