@aooth/arbac 0.1.77 → 0.1.78

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 CHANGED
@@ -780,8 +780,8 @@ const NO_FIELDS = Object.freeze({});
780
780
  * conjunction — so every row path sees the restriction. The custom value
781
781
  * stays on the scope. Scopes are never mutated: a changed scope is a copy.
782
782
  * Memoized per rule registry and scope object (process-wide), and
783
- * idempotent (a folded scope folds to itself); without row rules (and no
784
- * `onUnknownField`) the input list is returned as is.
783
+ * idempotent (a folded scope folds to itself); without row rules the input
784
+ * list is returned as is (after the `onUnknownField` report, if any).
785
785
  *
786
786
  * @since 0.1.72
787
787
  */
@@ -799,7 +799,10 @@ function applyScopeFieldFilters(scopes, fields, opts = {}) {
799
799
  }
800
800
  const { rules, memo } = state;
801
801
  const { onUnknownField } = opts;
802
- if (rules.length === 0 && !onUnknownField) return scopes;
802
+ if (rules.length === 0) {
803
+ if (onUnknownField) for (const scope of scopes) reportUnknownFields(scope, registry, onUnknownField);
804
+ return scopes;
805
+ }
803
806
  const fold = (scope) => {
804
807
  if (scope === null || typeof scope !== "object") return scope;
805
808
  const hit = memo.get(scope);
@@ -837,6 +840,16 @@ function applyScopeFieldFilters(scopes, fields, opts = {}) {
837
840
  const out = scopes.map(fold);
838
841
  return out.every((s, i) => s === scopes[i]) ? scopes : out;
839
842
  }
843
+ /** {@link applyScopeFieldFilters}' unknown-field report without row rules, recursing into `with`. */
844
+ function reportUnknownFields(scope, registry, onUnknownField) {
845
+ if (scope === null || typeof scope !== "object") return;
846
+ const record = scope;
847
+ for (const key of Object.keys(scope)) {
848
+ if (BUILT_IN_KEYS.has(key) || Object.hasOwn(registry, key)) continue;
849
+ if (record[key] !== void 0) onUnknownField(key);
850
+ }
851
+ if (scope.with) for (const sub of Object.values(scope.with)) reportUnknownFields(sub, registry, onUnknownField);
852
+ }
840
853
  /**
841
854
  * Marks a `with.<rel>` sub-scope {@link conjoinScopes} built from ONE side's
842
855
  * declaration. The other side is silent on the relation, so ITS policy for
package/dist/index.d.cts CHANGED
@@ -619,9 +619,10 @@ declare function conjoinScopes<S extends TDbScope>(a: readonly S[], b: readonly
619
619
  /** Options of {@link applyScopeFieldFilters}. @since 0.1.72 */
620
620
  interface TApplyScopeFieldFiltersOptions {
621
621
  /**
622
- * Called for a non-built-in scope key with no rule in `fields` (once per
623
- * scope object carrying it) — e.g. to warn about a field that will fail
624
- * the first attenuated conjunction.
622
+ * Called for a non-built-in scope key with no rule in `fields` — e.g. to
623
+ * warn about a field that will fail the first attenuated conjunction. With
624
+ * row rules registered it fires once per scope object carrying the key
625
+ * (memoized fold); without any, on every call.
625
626
  */
626
627
  onUnknownField?: (field: string) => void;
627
628
  }
@@ -632,8 +633,8 @@ interface TApplyScopeFieldFiltersOptions {
632
633
  * conjunction — so every row path sees the restriction. The custom value
633
634
  * stays on the scope. Scopes are never mutated: a changed scope is a copy.
634
635
  * Memoized per rule registry and scope object (process-wide), and
635
- * idempotent (a folded scope folds to itself); without row rules (and no
636
- * `onUnknownField`) the input list is returned as is.
636
+ * idempotent (a folded scope folds to itself); without row rules the input
637
+ * list is returned as is (after the `onUnknownField` report, if any).
637
638
  *
638
639
  * @since 0.1.72
639
640
  */
package/dist/index.d.mts CHANGED
@@ -619,9 +619,10 @@ declare function conjoinScopes<S extends TDbScope>(a: readonly S[], b: readonly
619
619
  /** Options of {@link applyScopeFieldFilters}. @since 0.1.72 */
620
620
  interface TApplyScopeFieldFiltersOptions {
621
621
  /**
622
- * Called for a non-built-in scope key with no rule in `fields` (once per
623
- * scope object carrying it) — e.g. to warn about a field that will fail
624
- * the first attenuated conjunction.
622
+ * Called for a non-built-in scope key with no rule in `fields` — e.g. to
623
+ * warn about a field that will fail the first attenuated conjunction. With
624
+ * row rules registered it fires once per scope object carrying the key
625
+ * (memoized fold); without any, on every call.
625
626
  */
626
627
  onUnknownField?: (field: string) => void;
627
628
  }
@@ -632,8 +633,8 @@ interface TApplyScopeFieldFiltersOptions {
632
633
  * conjunction — so every row path sees the restriction. The custom value
633
634
  * stays on the scope. Scopes are never mutated: a changed scope is a copy.
634
635
  * Memoized per rule registry and scope object (process-wide), and
635
- * idempotent (a folded scope folds to itself); without row rules (and no
636
- * `onUnknownField`) the input list is returned as is.
636
+ * idempotent (a folded scope folds to itself); without row rules the input
637
+ * list is returned as is (after the `onUnknownField` report, if any).
637
638
  *
638
639
  * @since 0.1.72
639
640
  */
package/dist/index.mjs CHANGED
@@ -780,8 +780,8 @@ const NO_FIELDS = Object.freeze({});
780
780
  * conjunction — so every row path sees the restriction. The custom value
781
781
  * stays on the scope. Scopes are never mutated: a changed scope is a copy.
782
782
  * Memoized per rule registry and scope object (process-wide), and
783
- * idempotent (a folded scope folds to itself); without row rules (and no
784
- * `onUnknownField`) the input list is returned as is.
783
+ * idempotent (a folded scope folds to itself); without row rules the input
784
+ * list is returned as is (after the `onUnknownField` report, if any).
785
785
  *
786
786
  * @since 0.1.72
787
787
  */
@@ -799,7 +799,10 @@ function applyScopeFieldFilters(scopes, fields, opts = {}) {
799
799
  }
800
800
  const { rules, memo } = state;
801
801
  const { onUnknownField } = opts;
802
- if (rules.length === 0 && !onUnknownField) return scopes;
802
+ if (rules.length === 0) {
803
+ if (onUnknownField) for (const scope of scopes) reportUnknownFields(scope, registry, onUnknownField);
804
+ return scopes;
805
+ }
803
806
  const fold = (scope) => {
804
807
  if (scope === null || typeof scope !== "object") return scope;
805
808
  const hit = memo.get(scope);
@@ -837,6 +840,16 @@ function applyScopeFieldFilters(scopes, fields, opts = {}) {
837
840
  const out = scopes.map(fold);
838
841
  return out.every((s, i) => s === scopes[i]) ? scopes : out;
839
842
  }
843
+ /** {@link applyScopeFieldFilters}' unknown-field report without row rules, recursing into `with`. */
844
+ function reportUnknownFields(scope, registry, onUnknownField) {
845
+ if (scope === null || typeof scope !== "object") return;
846
+ const record = scope;
847
+ for (const key of Object.keys(scope)) {
848
+ if (BUILT_IN_KEYS.has(key) || Object.hasOwn(registry, key)) continue;
849
+ if (record[key] !== void 0) onUnknownField(key);
850
+ }
851
+ if (scope.with) for (const sub of Object.values(scope.with)) reportUnknownFields(sub, registry, onUnknownField);
852
+ }
840
853
  /**
841
854
  * Marks a `with.<rel>` sub-scope {@link conjoinScopes} built from ONE side's
842
855
  * declaration. The other side is silent on the relation, so ITS policy for
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aooth/arbac",
3
- "version": "0.1.77",
3
+ "version": "0.1.78",
4
4
  "description": "Batteries-included RBAC: builder API, privilege factories, scope merge utilities",
5
5
  "keywords": [
6
6
  "access-control",
@@ -45,7 +45,7 @@
45
45
  "access": "public"
46
46
  },
47
47
  "dependencies": {
48
- "@aooth/arbac-core": "0.1.77"
48
+ "@aooth/arbac-core": "0.1.78"
49
49
  },
50
50
  "scripts": {
51
51
  "build": "vp pack",