@directive-run/core 1.7.0 → 1.9.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/dist/index.cjs +4 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +290 -1
- package/dist/index.d.ts +290 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -526,6 +526,295 @@ declare const MAX_SWEEP_POINTS = 10000;
|
|
|
526
526
|
*/
|
|
527
527
|
declare function sweepUnder<F = Record<string, unknown>>(options: SweepUnderOptions<F>): SweepReport;
|
|
528
528
|
|
|
529
|
+
/**
|
|
530
|
+
* Structural diff between two snapshots of a system's constraint `whenSpec`
|
|
531
|
+
* map. The "git diff for business rules" — operates on the predicate AST
|
|
532
|
+
* instead of source-text lines.
|
|
533
|
+
*
|
|
534
|
+
* A predicate is a tree of leaf clauses (`{ fact: operator(value) }`) and
|
|
535
|
+
* combinators (`$all` / `$any` / `$not`). `diffRules` flattens both trees
|
|
536
|
+
* into path-keyed leaf lists and compares: missing path → added/removed,
|
|
537
|
+
* same path with different operand → changed (with relaxed/tightened
|
|
538
|
+
* classification for numeric thresholds).
|
|
539
|
+
*
|
|
540
|
+
* Pure module — imports only utils. No engine / store / predicate-runtime
|
|
541
|
+
* dependency: predicates are walked as plain JSON.
|
|
542
|
+
*/
|
|
543
|
+
/** A leaf clause extracted from a predicate tree, keyed by its dotted path. */
|
|
544
|
+
interface LeafClause {
|
|
545
|
+
/** Dotted path through the predicate. E.g. `phase`, `$all[0].elapsed`, `$not.paused`. */
|
|
546
|
+
path: string;
|
|
547
|
+
/** Operator name. `$eq` is implied for bare-value equality. */
|
|
548
|
+
op: string;
|
|
549
|
+
/** Operand. */
|
|
550
|
+
value: unknown;
|
|
551
|
+
}
|
|
552
|
+
/** Kind of change observed for a single clause. */
|
|
553
|
+
type ChangeKind = "added" | "removed" | "changed" | "relaxed" | "tightened";
|
|
554
|
+
/** A single change between two predicates at a specific path. */
|
|
555
|
+
interface Change {
|
|
556
|
+
path: string;
|
|
557
|
+
kind: ChangeKind;
|
|
558
|
+
before?: {
|
|
559
|
+
op: string;
|
|
560
|
+
value: unknown;
|
|
561
|
+
};
|
|
562
|
+
after?: {
|
|
563
|
+
op: string;
|
|
564
|
+
value: unknown;
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
/** Status of a single constraint across the two snapshots. */
|
|
568
|
+
type ConstraintStatus = "added" | "removed" | "changed" | "unchanged";
|
|
569
|
+
interface ConstraintDiff {
|
|
570
|
+
id: string;
|
|
571
|
+
status: ConstraintStatus;
|
|
572
|
+
/** Empty for `unchanged`. For `added` / `removed`, every leaf is listed once. */
|
|
573
|
+
changes: Change[];
|
|
574
|
+
}
|
|
575
|
+
interface RulesDiffReport {
|
|
576
|
+
constraints: ConstraintDiff[];
|
|
577
|
+
summary: {
|
|
578
|
+
added: number;
|
|
579
|
+
removed: number;
|
|
580
|
+
changed: number;
|
|
581
|
+
unchanged: number;
|
|
582
|
+
totalClauseChanges: number;
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
interface DiffRulesOptions {
|
|
586
|
+
before: RulesMapInput;
|
|
587
|
+
after: RulesMapInput;
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Accepted shapes for the `before` / `after` predicate maps:
|
|
591
|
+
* - A plain map `{ [constraintId]: whenSpec }`
|
|
592
|
+
* - The `system.inspect().constraints` array form `Array<{ id, whenSpec? }>`
|
|
593
|
+
* - Either wrapped as `{ constraints: <one-of-the-above> }`
|
|
594
|
+
*
|
|
595
|
+
* Anything else throws at `toRulesMap` so a bad input fails loud.
|
|
596
|
+
*/
|
|
597
|
+
type RulesMapInput = unknown;
|
|
598
|
+
/**
|
|
599
|
+
* Coerce supported input shapes into a flat `Record<constraintId, whenSpec>`.
|
|
600
|
+
* Constraints without a `whenSpec` (function-form `when`) are dropped with
|
|
601
|
+
* an undefined entry so callers can distinguish them from absent constraints
|
|
602
|
+
* if they care; the diff treats `undefined` as "no data" and skips clause
|
|
603
|
+
* walking.
|
|
604
|
+
*/
|
|
605
|
+
declare function toRulesMap(raw: RulesMapInput): Record<string, unknown>;
|
|
606
|
+
/**
|
|
607
|
+
* Walk a predicate tree and emit every leaf clause with its dotted path.
|
|
608
|
+
* Combinators (`$all` / `$any` / `$not`) become indexed path segments.
|
|
609
|
+
* Bare-value equality (`{ phase: "red" }`) emits as `op: "$eq"`.
|
|
610
|
+
*
|
|
611
|
+
* Array-form predicates (`[{ fact, op, value }, ...]`) are also accepted —
|
|
612
|
+
* each clause's `fact` becomes the path, the operator is `op`, and the
|
|
613
|
+
* value is `value`.
|
|
614
|
+
*
|
|
615
|
+
* **What gets dropped:** non-object combinator children (e.g. `$not: "red"`
|
|
616
|
+
* with a string operand instead of a predicate object), function-form
|
|
617
|
+
* predicates (the engine accepts a function for `when`; this walker only
|
|
618
|
+
* sees data), and bare primitive predicates (a top-level `true` or `"x"`).
|
|
619
|
+
* The walker is a tree visitor — anything not shaped like a predicate node
|
|
620
|
+
* is silently skipped, never thrown on.
|
|
621
|
+
*/
|
|
622
|
+
declare function flattenPredicate(spec: unknown, pathPrefix?: string, out?: LeafClause[]): LeafClause[];
|
|
623
|
+
/**
|
|
624
|
+
* Diff two snapshots of a system's constraint whenSpec map.
|
|
625
|
+
*
|
|
626
|
+
* @example
|
|
627
|
+
* ```ts
|
|
628
|
+
* const report = diffRules({
|
|
629
|
+
* before: { blockCheckout: { cartTotal: { $gte: 100 } } },
|
|
630
|
+
* after: { blockCheckout: { cartTotal: { $gte: 50 } } },
|
|
631
|
+
* });
|
|
632
|
+
*
|
|
633
|
+
* report.constraints[0].status; // "changed"
|
|
634
|
+
* report.constraints[0].changes[0].kind; // "relaxed"
|
|
635
|
+
* report.summary.totalClauseChanges; // 1
|
|
636
|
+
* ```
|
|
637
|
+
*
|
|
638
|
+
* The input shape is forgiving — either a flat `{ id: whenSpec }` map, the
|
|
639
|
+
* `system.inspect().constraints` array form, or either wrapped as
|
|
640
|
+
* `{ constraints: ... }`. Constraints whose `when` is a function (no
|
|
641
|
+
* `whenSpec`) are tracked as added/removed but cannot be clause-diffed
|
|
642
|
+
* (the function form is opaque).
|
|
643
|
+
*/
|
|
644
|
+
declare function diffRules(options: DiffRulesOptions): RulesDiffReport;
|
|
645
|
+
/**
|
|
646
|
+
* Diff two predicate trees and return the list of leaf-level changes.
|
|
647
|
+
* Exported for reuse beyond the full per-constraint report.
|
|
648
|
+
*/
|
|
649
|
+
declare function diffClauses(before: unknown, after: unknown): Change[];
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Compile a `FactPredicate` to a parameterized Postgres-style SQL WHERE
|
|
653
|
+
* clause. The same predicate that gates a constraint on the client can
|
|
654
|
+
* filter rows on the server — *one source of truth, two execution sites*.
|
|
655
|
+
*
|
|
656
|
+
* Pure transformation. No engine, no driver, no string concatenation of
|
|
657
|
+
* user values (all operands flow through the parameter array → safe
|
|
658
|
+
* against SQL injection by construction).
|
|
659
|
+
*/
|
|
660
|
+
|
|
661
|
+
interface PredicateToSqlOptions {
|
|
662
|
+
/** Table to query. Validated against the same identifier rule as columns. */
|
|
663
|
+
table: string;
|
|
664
|
+
/**
|
|
665
|
+
* Allowlist of fact / column keys the predicate may reference. STRONGLY
|
|
666
|
+
* RECOMMENDED for any predicate that crosses a trust boundary (AI
|
|
667
|
+
* generation, user input, JSON-over-the-wire). Without an allowlist, a
|
|
668
|
+
* predicate may reference any column the table has.
|
|
669
|
+
*/
|
|
670
|
+
allowedKeys?: readonly string[];
|
|
671
|
+
/**
|
|
672
|
+
* Customize the `SELECT` projection. Default `"*"`. Accepts:
|
|
673
|
+
* - `"*"`
|
|
674
|
+
* - a column identifier (`"id"` or `"users.id"`)
|
|
675
|
+
* - an array of column identifiers (`["id", "name"]`) — joined with `,`
|
|
676
|
+
* Free-form strings (e.g. `"COUNT(*)"`) are rejected — build the wrapper
|
|
677
|
+
* SQL manually with {@link predicateToWhere} for those cases.
|
|
678
|
+
*/
|
|
679
|
+
select?: string | readonly string[];
|
|
680
|
+
/**
|
|
681
|
+
* Parameter placeholder generator. Default is Postgres-style `$1`, `$2`.
|
|
682
|
+
* Pass `() => "?"` for MySQL/SQLite, or supply your own scheme.
|
|
683
|
+
*/
|
|
684
|
+
placeholder?: (oneBasedIndex: number) => string;
|
|
685
|
+
}
|
|
686
|
+
interface PredicateToSqlResult {
|
|
687
|
+
/** Full `SELECT … FROM table WHERE …` statement. */
|
|
688
|
+
sql: string;
|
|
689
|
+
/** Just the `WHERE` clause body (without the literal `WHERE`). */
|
|
690
|
+
where: string;
|
|
691
|
+
/** Parameters, in `$1`-then-`$2`-then-… order. */
|
|
692
|
+
params: unknown[];
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Compile a {@link FactPredicate} to a parameterized SQL statement.
|
|
696
|
+
*
|
|
697
|
+
* Operand values **never** appear in the SQL string — they flow through
|
|
698
|
+
* the `params` array. Table and column identifiers are regex-validated.
|
|
699
|
+
*
|
|
700
|
+
* @example
|
|
701
|
+
* ```ts
|
|
702
|
+
* const where = { age: { $gte: 18 }, status: { $in: ["active", "pending"] } };
|
|
703
|
+
*
|
|
704
|
+
* predicateToSQL(where, { table: "users" });
|
|
705
|
+
* // → { sql: "SELECT * FROM users WHERE (age >= $1 AND status = ANY($2))",
|
|
706
|
+
* // where: "(age >= $1 AND status = ANY($2))",
|
|
707
|
+
* // params: [18, ["active", "pending"]] }
|
|
708
|
+
*
|
|
709
|
+
* // MySQL/SQLite placeholder:
|
|
710
|
+
* predicateToSQL(where, { table: "users", placeholder: () => "?" });
|
|
711
|
+
*
|
|
712
|
+
* // Recommended for AI/user-supplied predicates:
|
|
713
|
+
* predicateToSQL(where, { table: "users", allowedKeys: ["age", "status"] });
|
|
714
|
+
* ```
|
|
715
|
+
*/
|
|
716
|
+
declare function predicateToSQL<F = Record<string, unknown>>(predicate: FactPredicate<F>, options: PredicateToSqlOptions): PredicateToSqlResult;
|
|
717
|
+
/**
|
|
718
|
+
* Lower-level variant — returns just the `WHERE` clause body and the
|
|
719
|
+
* `params` array, no `SELECT ... FROM` wrapper. Use this when you need
|
|
720
|
+
* to embed the WHERE in a larger query (JOIN, UPDATE, DELETE, COUNT).
|
|
721
|
+
*
|
|
722
|
+
* @example
|
|
723
|
+
* ```ts
|
|
724
|
+
* const { where, params } = predicateToWhere({ age: { $gte: 18 } });
|
|
725
|
+
* await db.query(`UPDATE users SET tier = 'adult' WHERE ${where}`, params);
|
|
726
|
+
* ```
|
|
727
|
+
*/
|
|
728
|
+
declare function predicateToWhere<F = Record<string, unknown>>(predicate: FactPredicate<F>, options?: Omit<PredicateToSqlOptions, "table" | "select">): {
|
|
729
|
+
where: string;
|
|
730
|
+
params: unknown[];
|
|
731
|
+
};
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Compile a `FactPredicate` to a MongoDB query document. Most of
|
|
735
|
+
* Directive's predicate operator set is already Mongo-compatible by
|
|
736
|
+
* design — this translator handles the few ops that need rewriting
|
|
737
|
+
* (`$startsWith`/`$endsWith`/`$contains` → `$regex`, `$between` →
|
|
738
|
+
* `$gte`+`$lte`, `$matches` → `$regex`/`$options`).
|
|
739
|
+
*
|
|
740
|
+
* Combinators map cleanly: `$all` → `$and`, `$any` → `$or`, `$not` → `$nor`.
|
|
741
|
+
*
|
|
742
|
+
* Pure transformation. No driver dependency.
|
|
743
|
+
*/
|
|
744
|
+
|
|
745
|
+
interface PredicateToMongoOptions {
|
|
746
|
+
/**
|
|
747
|
+
* Allowlist of fact / field keys the predicate may reference. STRONGLY
|
|
748
|
+
* RECOMMENDED for any predicate that crosses a trust boundary.
|
|
749
|
+
*/
|
|
750
|
+
allowedKeys?: readonly string[];
|
|
751
|
+
/**
|
|
752
|
+
* Allow `.` in field names (for sub-document traversal: `"user.role"`).
|
|
753
|
+
* Default is `false` — `.` is rejected so the predicate cannot
|
|
754
|
+
* accidentally read sub-document fields the developer did not anticipate.
|
|
755
|
+
*/
|
|
756
|
+
allowDottedPaths?: boolean;
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Compile a {@link FactPredicate} to a MongoDB query document.
|
|
760
|
+
*
|
|
761
|
+
* Field names are validated to NOT start with `$` (which would land
|
|
762
|
+
* `$where` and other server-side JS evaluation operators directly in the
|
|
763
|
+
* query — an injection vector for AI/user-generated predicates).
|
|
764
|
+
*
|
|
765
|
+
* @example
|
|
766
|
+
* ```ts
|
|
767
|
+
* predicateToMongo({ age: { $gte: 18 }, status: { $in: ["active", "pending"] } })
|
|
768
|
+
* // → { age: { $gte: 18 }, status: { $in: ["active", "pending"] } }
|
|
769
|
+
*
|
|
770
|
+
* predicateToMongo({ name: { $startsWith: "Al" } })
|
|
771
|
+
* // → { name: { $regex: "^Al" } }
|
|
772
|
+
*
|
|
773
|
+
* predicateToMongo({ $any: [{ tier: "gold" }, { score: { $gte: 100 } }] })
|
|
774
|
+
* // → { $or: [{ tier: "gold" }, { score: { $gte: 100 } }] }
|
|
775
|
+
* ```
|
|
776
|
+
*/
|
|
777
|
+
declare function predicateToMongo<F = Record<string, unknown>>(predicate: FactPredicate<F>, options?: PredicateToMongoOptions): Record<string, unknown>;
|
|
778
|
+
|
|
779
|
+
/**
|
|
780
|
+
* Compile a `FactPredicate` to a PostgREST querystring.
|
|
781
|
+
*
|
|
782
|
+
* PostgREST's filter grammar is column-scoped: `?age=gte.18&status=in.(active,pending)`.
|
|
783
|
+
* Logical groups use `and=(...)` / `or=(...)` / `not.and=(...)`.
|
|
784
|
+
*
|
|
785
|
+
* Reference: https://postgrest.org/en/stable/api.html#operators
|
|
786
|
+
*/
|
|
787
|
+
|
|
788
|
+
interface PredicateToPostgrestOptions {
|
|
789
|
+
/** Allowlist of column keys the predicate may reference. */
|
|
790
|
+
allowedKeys?: readonly string[];
|
|
791
|
+
/**
|
|
792
|
+
* Encoding mode for the returned string.
|
|
793
|
+
* - `"querystring"` (default): a full querystring without a leading `?`,
|
|
794
|
+
* with each clause value URL-encoded — drops straight into `fetch`.
|
|
795
|
+
* - `"raw"`: the same content, leaving `(`, `)`, `,` unencoded for
|
|
796
|
+
* readability. Use this only when you'll encode the result yourself.
|
|
797
|
+
*/
|
|
798
|
+
mode?: "querystring" | "raw";
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Compile a {@link FactPredicate} to a PostgREST querystring.
|
|
802
|
+
*
|
|
803
|
+
* @example
|
|
804
|
+
* ```ts
|
|
805
|
+
* predicateToPostgrest({ age: { $gte: 18 }, status: { $in: ["active", "pending"] } })
|
|
806
|
+
* // → "age=gte.18&status=in.%28active%2Cpending%29"
|
|
807
|
+
*
|
|
808
|
+
* // Raw mode for debugging / building URLs by hand:
|
|
809
|
+
* predicateToPostgrest({ age: { $gte: 18 } }, { mode: "raw" })
|
|
810
|
+
* // → "age=gte.18"
|
|
811
|
+
*
|
|
812
|
+
* predicateToPostgrest({ $any: [{ tier: "gold" }, { score: { $gte: 100 } }] }, { mode: "raw" })
|
|
813
|
+
* // → "or=(tier.eq.gold,score.gte.100)"
|
|
814
|
+
* ```
|
|
815
|
+
*/
|
|
816
|
+
declare function predicateToPostgrest<F = Record<string, unknown>>(predicate: FactPredicate<F>, options?: PredicateToPostgrestOptions): string;
|
|
817
|
+
|
|
529
818
|
/** Brand symbol for branded types */
|
|
530
819
|
declare const Brand: unique symbol;
|
|
531
820
|
/** Branded type - adds a unique brand to a base type */
|
|
@@ -1786,4 +2075,4 @@ declare const Backoff: {
|
|
|
1786
2075
|
readonly Exponential: "exponential";
|
|
1787
2076
|
};
|
|
1788
2077
|
|
|
1789
|
-
export { Backoff, type Branded, type ChainableSchemaType, ClauseResult, CreateSystemOptionsNamed, CreateSystemOptionsSingle, CrossModuleDeps, DefinitionMeta, ErrorBoundaryConfig, type ExtendedSchemaType, FactPredicate, FactTemplate, Facts, MAX_REPLAY_FRAMES, MAX_SWEEP_POINTS, type ModuleConfig, type ModuleConfigWithDeps, ModuleDef, ModuleHooks, ModuleSchema, ModulesMap, NamespacedSystem, PatchSpec, Plugin, type PredicateBacktestReport, type ReplayDiffSample, type ReplayFrame, type ReplayUnderOptions, Requirement, RequirementSet, type RequirementTypeStatus, RequirementWithId, SchemaType, type SignalClock, SingleModuleSystem, type SweepHole, type SweepPoint, type SweepReport, type SweepUnderOptions, type TimerFactOpts, type TimerFactState, TraceOption, applyPatch, completeTimer, createModule, createModuleFactory, createRequirementStatusPlugin, createStatusHook, createSystem, createSystemWithStatus, defaultClock, elapsedMs, evaluateKeySelector, evaluatePredicate, evaluatePredicateExplained, evaluateTemplate, extractDeps, extractTemplateKeys, forType, framesFromHistory, framesFromSnapshots, generateRequirementId, initialTimerState, isPredicate, isRequirementType, isTemplate, memoizePredicate, pauseTimer, realClock, registerRepeat, remainingMs, replayUnder, req, resetTimer, resumeTimer, startTimer, sweepUnder, t, tickTimer, timerOps, toReplayFrames, validatePredicate, virtualClock };
|
|
2078
|
+
export { Backoff, type Branded, type ChainableSchemaType, type Change, type ChangeKind, ClauseResult, type ConstraintDiff, type ConstraintStatus, CreateSystemOptionsNamed, CreateSystemOptionsSingle, CrossModuleDeps, DefinitionMeta, type DiffRulesOptions, ErrorBoundaryConfig, type ExtendedSchemaType, FactPredicate, FactTemplate, Facts, type LeafClause, MAX_REPLAY_FRAMES, MAX_SWEEP_POINTS, type ModuleConfig, type ModuleConfigWithDeps, ModuleDef, ModuleHooks, ModuleSchema, ModulesMap, NamespacedSystem, PatchSpec, Plugin, type PredicateBacktestReport, type PredicateToMongoOptions, type PredicateToPostgrestOptions, type PredicateToSqlOptions, type PredicateToSqlResult, type ReplayDiffSample, type ReplayFrame, type ReplayUnderOptions, Requirement, RequirementSet, type RequirementTypeStatus, RequirementWithId, type RulesDiffReport, type RulesMapInput, SchemaType, type SignalClock, SingleModuleSystem, type SweepHole, type SweepPoint, type SweepReport, type SweepUnderOptions, type TimerFactOpts, type TimerFactState, TraceOption, applyPatch, completeTimer, createModule, createModuleFactory, createRequirementStatusPlugin, createStatusHook, createSystem, createSystemWithStatus, defaultClock, diffClauses, diffRules, elapsedMs, evaluateKeySelector, evaluatePredicate, evaluatePredicateExplained, evaluateTemplate, extractDeps, extractTemplateKeys, flattenPredicate, forType, framesFromHistory, framesFromSnapshots, generateRequirementId, initialTimerState, isPredicate, isRequirementType, isTemplate, memoizePredicate, pauseTimer, predicateToMongo, predicateToPostgrest, predicateToSQL, predicateToWhere, realClock, registerRepeat, remainingMs, replayUnder, req, resetTimer, resumeTimer, startTimer, sweepUnder, t, tickTimer, timerOps, toReplayFrames, toRulesMap, validatePredicate, virtualClock };
|
package/dist/index.d.ts
CHANGED
|
@@ -526,6 +526,295 @@ declare const MAX_SWEEP_POINTS = 10000;
|
|
|
526
526
|
*/
|
|
527
527
|
declare function sweepUnder<F = Record<string, unknown>>(options: SweepUnderOptions<F>): SweepReport;
|
|
528
528
|
|
|
529
|
+
/**
|
|
530
|
+
* Structural diff between two snapshots of a system's constraint `whenSpec`
|
|
531
|
+
* map. The "git diff for business rules" — operates on the predicate AST
|
|
532
|
+
* instead of source-text lines.
|
|
533
|
+
*
|
|
534
|
+
* A predicate is a tree of leaf clauses (`{ fact: operator(value) }`) and
|
|
535
|
+
* combinators (`$all` / `$any` / `$not`). `diffRules` flattens both trees
|
|
536
|
+
* into path-keyed leaf lists and compares: missing path → added/removed,
|
|
537
|
+
* same path with different operand → changed (with relaxed/tightened
|
|
538
|
+
* classification for numeric thresholds).
|
|
539
|
+
*
|
|
540
|
+
* Pure module — imports only utils. No engine / store / predicate-runtime
|
|
541
|
+
* dependency: predicates are walked as plain JSON.
|
|
542
|
+
*/
|
|
543
|
+
/** A leaf clause extracted from a predicate tree, keyed by its dotted path. */
|
|
544
|
+
interface LeafClause {
|
|
545
|
+
/** Dotted path through the predicate. E.g. `phase`, `$all[0].elapsed`, `$not.paused`. */
|
|
546
|
+
path: string;
|
|
547
|
+
/** Operator name. `$eq` is implied for bare-value equality. */
|
|
548
|
+
op: string;
|
|
549
|
+
/** Operand. */
|
|
550
|
+
value: unknown;
|
|
551
|
+
}
|
|
552
|
+
/** Kind of change observed for a single clause. */
|
|
553
|
+
type ChangeKind = "added" | "removed" | "changed" | "relaxed" | "tightened";
|
|
554
|
+
/** A single change between two predicates at a specific path. */
|
|
555
|
+
interface Change {
|
|
556
|
+
path: string;
|
|
557
|
+
kind: ChangeKind;
|
|
558
|
+
before?: {
|
|
559
|
+
op: string;
|
|
560
|
+
value: unknown;
|
|
561
|
+
};
|
|
562
|
+
after?: {
|
|
563
|
+
op: string;
|
|
564
|
+
value: unknown;
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
/** Status of a single constraint across the two snapshots. */
|
|
568
|
+
type ConstraintStatus = "added" | "removed" | "changed" | "unchanged";
|
|
569
|
+
interface ConstraintDiff {
|
|
570
|
+
id: string;
|
|
571
|
+
status: ConstraintStatus;
|
|
572
|
+
/** Empty for `unchanged`. For `added` / `removed`, every leaf is listed once. */
|
|
573
|
+
changes: Change[];
|
|
574
|
+
}
|
|
575
|
+
interface RulesDiffReport {
|
|
576
|
+
constraints: ConstraintDiff[];
|
|
577
|
+
summary: {
|
|
578
|
+
added: number;
|
|
579
|
+
removed: number;
|
|
580
|
+
changed: number;
|
|
581
|
+
unchanged: number;
|
|
582
|
+
totalClauseChanges: number;
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
interface DiffRulesOptions {
|
|
586
|
+
before: RulesMapInput;
|
|
587
|
+
after: RulesMapInput;
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Accepted shapes for the `before` / `after` predicate maps:
|
|
591
|
+
* - A plain map `{ [constraintId]: whenSpec }`
|
|
592
|
+
* - The `system.inspect().constraints` array form `Array<{ id, whenSpec? }>`
|
|
593
|
+
* - Either wrapped as `{ constraints: <one-of-the-above> }`
|
|
594
|
+
*
|
|
595
|
+
* Anything else throws at `toRulesMap` so a bad input fails loud.
|
|
596
|
+
*/
|
|
597
|
+
type RulesMapInput = unknown;
|
|
598
|
+
/**
|
|
599
|
+
* Coerce supported input shapes into a flat `Record<constraintId, whenSpec>`.
|
|
600
|
+
* Constraints without a `whenSpec` (function-form `when`) are dropped with
|
|
601
|
+
* an undefined entry so callers can distinguish them from absent constraints
|
|
602
|
+
* if they care; the diff treats `undefined` as "no data" and skips clause
|
|
603
|
+
* walking.
|
|
604
|
+
*/
|
|
605
|
+
declare function toRulesMap(raw: RulesMapInput): Record<string, unknown>;
|
|
606
|
+
/**
|
|
607
|
+
* Walk a predicate tree and emit every leaf clause with its dotted path.
|
|
608
|
+
* Combinators (`$all` / `$any` / `$not`) become indexed path segments.
|
|
609
|
+
* Bare-value equality (`{ phase: "red" }`) emits as `op: "$eq"`.
|
|
610
|
+
*
|
|
611
|
+
* Array-form predicates (`[{ fact, op, value }, ...]`) are also accepted —
|
|
612
|
+
* each clause's `fact` becomes the path, the operator is `op`, and the
|
|
613
|
+
* value is `value`.
|
|
614
|
+
*
|
|
615
|
+
* **What gets dropped:** non-object combinator children (e.g. `$not: "red"`
|
|
616
|
+
* with a string operand instead of a predicate object), function-form
|
|
617
|
+
* predicates (the engine accepts a function for `when`; this walker only
|
|
618
|
+
* sees data), and bare primitive predicates (a top-level `true` or `"x"`).
|
|
619
|
+
* The walker is a tree visitor — anything not shaped like a predicate node
|
|
620
|
+
* is silently skipped, never thrown on.
|
|
621
|
+
*/
|
|
622
|
+
declare function flattenPredicate(spec: unknown, pathPrefix?: string, out?: LeafClause[]): LeafClause[];
|
|
623
|
+
/**
|
|
624
|
+
* Diff two snapshots of a system's constraint whenSpec map.
|
|
625
|
+
*
|
|
626
|
+
* @example
|
|
627
|
+
* ```ts
|
|
628
|
+
* const report = diffRules({
|
|
629
|
+
* before: { blockCheckout: { cartTotal: { $gte: 100 } } },
|
|
630
|
+
* after: { blockCheckout: { cartTotal: { $gte: 50 } } },
|
|
631
|
+
* });
|
|
632
|
+
*
|
|
633
|
+
* report.constraints[0].status; // "changed"
|
|
634
|
+
* report.constraints[0].changes[0].kind; // "relaxed"
|
|
635
|
+
* report.summary.totalClauseChanges; // 1
|
|
636
|
+
* ```
|
|
637
|
+
*
|
|
638
|
+
* The input shape is forgiving — either a flat `{ id: whenSpec }` map, the
|
|
639
|
+
* `system.inspect().constraints` array form, or either wrapped as
|
|
640
|
+
* `{ constraints: ... }`. Constraints whose `when` is a function (no
|
|
641
|
+
* `whenSpec`) are tracked as added/removed but cannot be clause-diffed
|
|
642
|
+
* (the function form is opaque).
|
|
643
|
+
*/
|
|
644
|
+
declare function diffRules(options: DiffRulesOptions): RulesDiffReport;
|
|
645
|
+
/**
|
|
646
|
+
* Diff two predicate trees and return the list of leaf-level changes.
|
|
647
|
+
* Exported for reuse beyond the full per-constraint report.
|
|
648
|
+
*/
|
|
649
|
+
declare function diffClauses(before: unknown, after: unknown): Change[];
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Compile a `FactPredicate` to a parameterized Postgres-style SQL WHERE
|
|
653
|
+
* clause. The same predicate that gates a constraint on the client can
|
|
654
|
+
* filter rows on the server — *one source of truth, two execution sites*.
|
|
655
|
+
*
|
|
656
|
+
* Pure transformation. No engine, no driver, no string concatenation of
|
|
657
|
+
* user values (all operands flow through the parameter array → safe
|
|
658
|
+
* against SQL injection by construction).
|
|
659
|
+
*/
|
|
660
|
+
|
|
661
|
+
interface PredicateToSqlOptions {
|
|
662
|
+
/** Table to query. Validated against the same identifier rule as columns. */
|
|
663
|
+
table: string;
|
|
664
|
+
/**
|
|
665
|
+
* Allowlist of fact / column keys the predicate may reference. STRONGLY
|
|
666
|
+
* RECOMMENDED for any predicate that crosses a trust boundary (AI
|
|
667
|
+
* generation, user input, JSON-over-the-wire). Without an allowlist, a
|
|
668
|
+
* predicate may reference any column the table has.
|
|
669
|
+
*/
|
|
670
|
+
allowedKeys?: readonly string[];
|
|
671
|
+
/**
|
|
672
|
+
* Customize the `SELECT` projection. Default `"*"`. Accepts:
|
|
673
|
+
* - `"*"`
|
|
674
|
+
* - a column identifier (`"id"` or `"users.id"`)
|
|
675
|
+
* - an array of column identifiers (`["id", "name"]`) — joined with `,`
|
|
676
|
+
* Free-form strings (e.g. `"COUNT(*)"`) are rejected — build the wrapper
|
|
677
|
+
* SQL manually with {@link predicateToWhere} for those cases.
|
|
678
|
+
*/
|
|
679
|
+
select?: string | readonly string[];
|
|
680
|
+
/**
|
|
681
|
+
* Parameter placeholder generator. Default is Postgres-style `$1`, `$2`.
|
|
682
|
+
* Pass `() => "?"` for MySQL/SQLite, or supply your own scheme.
|
|
683
|
+
*/
|
|
684
|
+
placeholder?: (oneBasedIndex: number) => string;
|
|
685
|
+
}
|
|
686
|
+
interface PredicateToSqlResult {
|
|
687
|
+
/** Full `SELECT … FROM table WHERE …` statement. */
|
|
688
|
+
sql: string;
|
|
689
|
+
/** Just the `WHERE` clause body (without the literal `WHERE`). */
|
|
690
|
+
where: string;
|
|
691
|
+
/** Parameters, in `$1`-then-`$2`-then-… order. */
|
|
692
|
+
params: unknown[];
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Compile a {@link FactPredicate} to a parameterized SQL statement.
|
|
696
|
+
*
|
|
697
|
+
* Operand values **never** appear in the SQL string — they flow through
|
|
698
|
+
* the `params` array. Table and column identifiers are regex-validated.
|
|
699
|
+
*
|
|
700
|
+
* @example
|
|
701
|
+
* ```ts
|
|
702
|
+
* const where = { age: { $gte: 18 }, status: { $in: ["active", "pending"] } };
|
|
703
|
+
*
|
|
704
|
+
* predicateToSQL(where, { table: "users" });
|
|
705
|
+
* // → { sql: "SELECT * FROM users WHERE (age >= $1 AND status = ANY($2))",
|
|
706
|
+
* // where: "(age >= $1 AND status = ANY($2))",
|
|
707
|
+
* // params: [18, ["active", "pending"]] }
|
|
708
|
+
*
|
|
709
|
+
* // MySQL/SQLite placeholder:
|
|
710
|
+
* predicateToSQL(where, { table: "users", placeholder: () => "?" });
|
|
711
|
+
*
|
|
712
|
+
* // Recommended for AI/user-supplied predicates:
|
|
713
|
+
* predicateToSQL(where, { table: "users", allowedKeys: ["age", "status"] });
|
|
714
|
+
* ```
|
|
715
|
+
*/
|
|
716
|
+
declare function predicateToSQL<F = Record<string, unknown>>(predicate: FactPredicate<F>, options: PredicateToSqlOptions): PredicateToSqlResult;
|
|
717
|
+
/**
|
|
718
|
+
* Lower-level variant — returns just the `WHERE` clause body and the
|
|
719
|
+
* `params` array, no `SELECT ... FROM` wrapper. Use this when you need
|
|
720
|
+
* to embed the WHERE in a larger query (JOIN, UPDATE, DELETE, COUNT).
|
|
721
|
+
*
|
|
722
|
+
* @example
|
|
723
|
+
* ```ts
|
|
724
|
+
* const { where, params } = predicateToWhere({ age: { $gte: 18 } });
|
|
725
|
+
* await db.query(`UPDATE users SET tier = 'adult' WHERE ${where}`, params);
|
|
726
|
+
* ```
|
|
727
|
+
*/
|
|
728
|
+
declare function predicateToWhere<F = Record<string, unknown>>(predicate: FactPredicate<F>, options?: Omit<PredicateToSqlOptions, "table" | "select">): {
|
|
729
|
+
where: string;
|
|
730
|
+
params: unknown[];
|
|
731
|
+
};
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Compile a `FactPredicate` to a MongoDB query document. Most of
|
|
735
|
+
* Directive's predicate operator set is already Mongo-compatible by
|
|
736
|
+
* design — this translator handles the few ops that need rewriting
|
|
737
|
+
* (`$startsWith`/`$endsWith`/`$contains` → `$regex`, `$between` →
|
|
738
|
+
* `$gte`+`$lte`, `$matches` → `$regex`/`$options`).
|
|
739
|
+
*
|
|
740
|
+
* Combinators map cleanly: `$all` → `$and`, `$any` → `$or`, `$not` → `$nor`.
|
|
741
|
+
*
|
|
742
|
+
* Pure transformation. No driver dependency.
|
|
743
|
+
*/
|
|
744
|
+
|
|
745
|
+
interface PredicateToMongoOptions {
|
|
746
|
+
/**
|
|
747
|
+
* Allowlist of fact / field keys the predicate may reference. STRONGLY
|
|
748
|
+
* RECOMMENDED for any predicate that crosses a trust boundary.
|
|
749
|
+
*/
|
|
750
|
+
allowedKeys?: readonly string[];
|
|
751
|
+
/**
|
|
752
|
+
* Allow `.` in field names (for sub-document traversal: `"user.role"`).
|
|
753
|
+
* Default is `false` — `.` is rejected so the predicate cannot
|
|
754
|
+
* accidentally read sub-document fields the developer did not anticipate.
|
|
755
|
+
*/
|
|
756
|
+
allowDottedPaths?: boolean;
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Compile a {@link FactPredicate} to a MongoDB query document.
|
|
760
|
+
*
|
|
761
|
+
* Field names are validated to NOT start with `$` (which would land
|
|
762
|
+
* `$where` and other server-side JS evaluation operators directly in the
|
|
763
|
+
* query — an injection vector for AI/user-generated predicates).
|
|
764
|
+
*
|
|
765
|
+
* @example
|
|
766
|
+
* ```ts
|
|
767
|
+
* predicateToMongo({ age: { $gte: 18 }, status: { $in: ["active", "pending"] } })
|
|
768
|
+
* // → { age: { $gte: 18 }, status: { $in: ["active", "pending"] } }
|
|
769
|
+
*
|
|
770
|
+
* predicateToMongo({ name: { $startsWith: "Al" } })
|
|
771
|
+
* // → { name: { $regex: "^Al" } }
|
|
772
|
+
*
|
|
773
|
+
* predicateToMongo({ $any: [{ tier: "gold" }, { score: { $gte: 100 } }] })
|
|
774
|
+
* // → { $or: [{ tier: "gold" }, { score: { $gte: 100 } }] }
|
|
775
|
+
* ```
|
|
776
|
+
*/
|
|
777
|
+
declare function predicateToMongo<F = Record<string, unknown>>(predicate: FactPredicate<F>, options?: PredicateToMongoOptions): Record<string, unknown>;
|
|
778
|
+
|
|
779
|
+
/**
|
|
780
|
+
* Compile a `FactPredicate` to a PostgREST querystring.
|
|
781
|
+
*
|
|
782
|
+
* PostgREST's filter grammar is column-scoped: `?age=gte.18&status=in.(active,pending)`.
|
|
783
|
+
* Logical groups use `and=(...)` / `or=(...)` / `not.and=(...)`.
|
|
784
|
+
*
|
|
785
|
+
* Reference: https://postgrest.org/en/stable/api.html#operators
|
|
786
|
+
*/
|
|
787
|
+
|
|
788
|
+
interface PredicateToPostgrestOptions {
|
|
789
|
+
/** Allowlist of column keys the predicate may reference. */
|
|
790
|
+
allowedKeys?: readonly string[];
|
|
791
|
+
/**
|
|
792
|
+
* Encoding mode for the returned string.
|
|
793
|
+
* - `"querystring"` (default): a full querystring without a leading `?`,
|
|
794
|
+
* with each clause value URL-encoded — drops straight into `fetch`.
|
|
795
|
+
* - `"raw"`: the same content, leaving `(`, `)`, `,` unencoded for
|
|
796
|
+
* readability. Use this only when you'll encode the result yourself.
|
|
797
|
+
*/
|
|
798
|
+
mode?: "querystring" | "raw";
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Compile a {@link FactPredicate} to a PostgREST querystring.
|
|
802
|
+
*
|
|
803
|
+
* @example
|
|
804
|
+
* ```ts
|
|
805
|
+
* predicateToPostgrest({ age: { $gte: 18 }, status: { $in: ["active", "pending"] } })
|
|
806
|
+
* // → "age=gte.18&status=in.%28active%2Cpending%29"
|
|
807
|
+
*
|
|
808
|
+
* // Raw mode for debugging / building URLs by hand:
|
|
809
|
+
* predicateToPostgrest({ age: { $gte: 18 } }, { mode: "raw" })
|
|
810
|
+
* // → "age=gte.18"
|
|
811
|
+
*
|
|
812
|
+
* predicateToPostgrest({ $any: [{ tier: "gold" }, { score: { $gte: 100 } }] }, { mode: "raw" })
|
|
813
|
+
* // → "or=(tier.eq.gold,score.gte.100)"
|
|
814
|
+
* ```
|
|
815
|
+
*/
|
|
816
|
+
declare function predicateToPostgrest<F = Record<string, unknown>>(predicate: FactPredicate<F>, options?: PredicateToPostgrestOptions): string;
|
|
817
|
+
|
|
529
818
|
/** Brand symbol for branded types */
|
|
530
819
|
declare const Brand: unique symbol;
|
|
531
820
|
/** Branded type - adds a unique brand to a base type */
|
|
@@ -1786,4 +2075,4 @@ declare const Backoff: {
|
|
|
1786
2075
|
readonly Exponential: "exponential";
|
|
1787
2076
|
};
|
|
1788
2077
|
|
|
1789
|
-
export { Backoff, type Branded, type ChainableSchemaType, ClauseResult, CreateSystemOptionsNamed, CreateSystemOptionsSingle, CrossModuleDeps, DefinitionMeta, ErrorBoundaryConfig, type ExtendedSchemaType, FactPredicate, FactTemplate, Facts, MAX_REPLAY_FRAMES, MAX_SWEEP_POINTS, type ModuleConfig, type ModuleConfigWithDeps, ModuleDef, ModuleHooks, ModuleSchema, ModulesMap, NamespacedSystem, PatchSpec, Plugin, type PredicateBacktestReport, type ReplayDiffSample, type ReplayFrame, type ReplayUnderOptions, Requirement, RequirementSet, type RequirementTypeStatus, RequirementWithId, SchemaType, type SignalClock, SingleModuleSystem, type SweepHole, type SweepPoint, type SweepReport, type SweepUnderOptions, type TimerFactOpts, type TimerFactState, TraceOption, applyPatch, completeTimer, createModule, createModuleFactory, createRequirementStatusPlugin, createStatusHook, createSystem, createSystemWithStatus, defaultClock, elapsedMs, evaluateKeySelector, evaluatePredicate, evaluatePredicateExplained, evaluateTemplate, extractDeps, extractTemplateKeys, forType, framesFromHistory, framesFromSnapshots, generateRequirementId, initialTimerState, isPredicate, isRequirementType, isTemplate, memoizePredicate, pauseTimer, realClock, registerRepeat, remainingMs, replayUnder, req, resetTimer, resumeTimer, startTimer, sweepUnder, t, tickTimer, timerOps, toReplayFrames, validatePredicate, virtualClock };
|
|
2078
|
+
export { Backoff, type Branded, type ChainableSchemaType, type Change, type ChangeKind, ClauseResult, type ConstraintDiff, type ConstraintStatus, CreateSystemOptionsNamed, CreateSystemOptionsSingle, CrossModuleDeps, DefinitionMeta, type DiffRulesOptions, ErrorBoundaryConfig, type ExtendedSchemaType, FactPredicate, FactTemplate, Facts, type LeafClause, MAX_REPLAY_FRAMES, MAX_SWEEP_POINTS, type ModuleConfig, type ModuleConfigWithDeps, ModuleDef, ModuleHooks, ModuleSchema, ModulesMap, NamespacedSystem, PatchSpec, Plugin, type PredicateBacktestReport, type PredicateToMongoOptions, type PredicateToPostgrestOptions, type PredicateToSqlOptions, type PredicateToSqlResult, type ReplayDiffSample, type ReplayFrame, type ReplayUnderOptions, Requirement, RequirementSet, type RequirementTypeStatus, RequirementWithId, type RulesDiffReport, type RulesMapInput, SchemaType, type SignalClock, SingleModuleSystem, type SweepHole, type SweepPoint, type SweepReport, type SweepUnderOptions, type TimerFactOpts, type TimerFactState, TraceOption, applyPatch, completeTimer, createModule, createModuleFactory, createRequirementStatusPlugin, createStatusHook, createSystem, createSystemWithStatus, defaultClock, diffClauses, diffRules, elapsedMs, evaluateKeySelector, evaluatePredicate, evaluatePredicateExplained, evaluateTemplate, extractDeps, extractTemplateKeys, flattenPredicate, forType, framesFromHistory, framesFromSnapshots, generateRequirementId, initialTimerState, isPredicate, isRequirementType, isTemplate, memoizePredicate, pauseTimer, predicateToMongo, predicateToPostgrest, predicateToSQL, predicateToWhere, realClock, registerRepeat, remainingMs, replayUnder, req, resetTimer, resumeTimer, startTimer, sweepUnder, t, tickTimer, timerOps, toReplayFrames, toRulesMap, validatePredicate, virtualClock };
|