@objectstack/formula 9.10.0 → 10.0.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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +90 -0
- package/dist/index.d.mts +78 -2
- package/dist/index.d.ts +78 -2
- package/dist/index.js +369 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +365 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/cel-engine.ts +6 -0
- package/src/cel-to-filter.test.ts +218 -0
- package/src/cel-to-filter.ts +411 -0
- package/src/index.ts +6 -0
- package/src/matches-filter.test.ts +110 -0
- package/src/matches-filter.ts +115 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @objectstack/formula@
|
|
2
|
+
> @objectstack/formula@10.0.0 build /home/runner/work/framework/framework/packages/formula
|
|
3
3
|
> tsup --config ../../tsup.config.ts
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.ts
|
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
[34mCLI[39m Cleaning output folder
|
|
11
11
|
[34mESM[39m Build start
|
|
12
12
|
[34mCJS[39m Build start
|
|
13
|
-
[
|
|
14
|
-
[
|
|
15
|
-
[
|
|
16
|
-
[
|
|
17
|
-
[
|
|
18
|
-
[
|
|
13
|
+
[32mCJS[39m [1mdist/index.js [22m[32m45.02 KB[39m
|
|
14
|
+
[32mCJS[39m [1mdist/index.js.map [22m[32m120.34 KB[39m
|
|
15
|
+
[32mCJS[39m ⚡️ Build success in 115ms
|
|
16
|
+
[32mESM[39m [1mdist/index.mjs [22m[32m43.08 KB[39m
|
|
17
|
+
[32mESM[39m [1mdist/index.mjs.map [22m[32m118.46 KB[39m
|
|
18
|
+
[32mESM[39m ⚡️ Build success in 116ms
|
|
19
19
|
[34mDTS[39m Build start
|
|
20
|
-
[32mDTS[39m ⚡️ Build success in
|
|
21
|
-
[32mDTS[39m [1mdist/index.d.mts [22m[
|
|
22
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[
|
|
20
|
+
[32mDTS[39m ⚡️ Build success in 5125ms
|
|
21
|
+
[32mDTS[39m [1mdist/index.d.mts [22m[32m20.19 KB[39m
|
|
22
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m20.19 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,95 @@
|
|
|
1
1
|
# @objectstack/formula
|
|
2
2
|
|
|
3
|
+
## 10.0.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- cfd86ce: ADR-0058 — expression & predicate surface unification. Adds the canonical
|
|
8
|
+
CEL→FilterCondition pushdown compiler in `@objectstack/formula`
|
|
9
|
+
(`compileCelToFilter`, `isPushdownableCel`, `lowerCelAst`) plus an in-memory
|
|
10
|
+
`matchesFilterCondition` backend (one AST, three backends). `plugin-security`
|
|
11
|
+
(RLS `using`, via a SQL bridge) and `plugin-sharing` (`celToFilter`) cut over to
|
|
12
|
+
it, retiring the bespoke regex/field-equality front-ends. Compound sharing
|
|
13
|
+
conditions now compile and enforce end-to-end (closes #1887). The RLS `check`
|
|
14
|
+
clause is now enforced on the write post-image (insert/by-id update), fail-closed.
|
|
15
|
+
Non-pushdownable predicates (arithmetic, functions, subqueries, cross-object) are
|
|
16
|
+
an authoring compile error, never silently dropped (ADR-0049/0055).
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- 48a307a: build: validate UI action `visible` / `disabled` predicates at compile time
|
|
21
|
+
|
|
22
|
+
Extends the ADR-0032 build-time expression check to cover action `visible` and
|
|
23
|
+
`disabled` predicates (stack-level and object-attached), evaluated record-scoped
|
|
24
|
+
like validation rules. A record-header / row action's `visible` is evaluated by
|
|
25
|
+
`ActionEngine` against `{ record, recordId, objectName, user, … }` with
|
|
26
|
+
fail-closed semantics, so a **bare** field reference (`!done` instead of
|
|
27
|
+
`!record.done`) throws at runtime and the action is **silently hidden on every
|
|
28
|
+
record** — the trap behind the #2183 "Mark Done never hides" debugging hunt.
|
|
29
|
+
`os build` now reports it as an error with the corrective `record.<field>`
|
|
30
|
+
message instead of letting it ship.
|
|
31
|
+
|
|
32
|
+
`@objectstack/formula`: `ctx` and `features` are added to the record-scope
|
|
33
|
+
namespace roots (alongside the existing `user`, `data`, `context`, …) so the
|
|
34
|
+
ambient globals real action predicates use (`record.id == ctx.user.id`,
|
|
35
|
+
`features.multiOrgEnabled`) are not false-positives. Verified against the full
|
|
36
|
+
monorepo build (every example + platform bundle still compiles clean).
|
|
37
|
+
|
|
38
|
+
- 25fc0e4: build: extend ADR-0032 predicate validation to all flat record-scoped sites
|
|
39
|
+
|
|
40
|
+
Builds on the action-predicate guard. `os build` now also validates these
|
|
41
|
+
record-scoped predicates for bare field references (`status` instead of
|
|
42
|
+
`record.status`), which otherwise evaluate to nothing at runtime and silently
|
|
43
|
+
mis-behave:
|
|
44
|
+
|
|
45
|
+
- **field conditional rules** — `requiredWhen`, `readonlyWhen`,
|
|
46
|
+
`conditionalRequired`, `visibleWhen` (server-enforced; a broken one is
|
|
47
|
+
fail-open — the required/readonly rule just never fires);
|
|
48
|
+
- **sharing-rule `condition`** (security-critical — decides which rows a
|
|
49
|
+
principal sees);
|
|
50
|
+
- **lifecycle hook `condition`** (skips the handler when false);
|
|
51
|
+
- **nested `when`** on `conditional` validation rules (previously only the
|
|
52
|
+
top-level rule predicate was checked).
|
|
53
|
+
|
|
54
|
+
`@objectstack/formula`: adds `parent` to the record-scope namespace roots —
|
|
55
|
+
master-detail inline grids inject the header record as `parent` for a child
|
|
56
|
+
field's `readonlyWhen`/`requiredWhen` (ADR-0036, #1581), so `parent.status` is
|
|
57
|
+
legitimate, not a bare ref. Verified against the full monorepo build (76 tasks
|
|
58
|
+
clean).
|
|
59
|
+
|
|
60
|
+
Not yet covered (separate follow-up — needs a recursive view/page tree walker
|
|
61
|
+
and per-node scope classification): deeply-nested UI visibility predicates
|
|
62
|
+
(`view` element/section `visibleOn`/`condition`, `page` component `visibility`),
|
|
63
|
+
object field-group `visibleOn`, and app-nav `visible` (user/feature-scoped, not
|
|
64
|
+
record-scoped).
|
|
65
|
+
|
|
66
|
+
- Updated dependencies [d7ff626]
|
|
67
|
+
- Updated dependencies [2a1b16b]
|
|
68
|
+
- Updated dependencies [e16f2a8]
|
|
69
|
+
- Updated dependencies [e411a82]
|
|
70
|
+
- Updated dependencies [a581385]
|
|
71
|
+
- Updated dependencies [220ce5b]
|
|
72
|
+
- Updated dependencies [3efe334]
|
|
73
|
+
- Updated dependencies [feead7e]
|
|
74
|
+
- Updated dependencies [6ca20b3]
|
|
75
|
+
- Updated dependencies [5f875fe]
|
|
76
|
+
- Updated dependencies [b469950]
|
|
77
|
+
- @objectstack/spec@10.0.0
|
|
78
|
+
|
|
79
|
+
## 9.11.0
|
|
80
|
+
|
|
81
|
+
### Patch Changes
|
|
82
|
+
|
|
83
|
+
- Updated dependencies [e7f6539]
|
|
84
|
+
- Updated dependencies [2365d07]
|
|
85
|
+
- Updated dependencies [6595b53]
|
|
86
|
+
- Updated dependencies [fa8964d]
|
|
87
|
+
- Updated dependencies [36138c7]
|
|
88
|
+
- Updated dependencies [a8e4f3b]
|
|
89
|
+
- Updated dependencies [4c213c2]
|
|
90
|
+
- Updated dependencies [2afb612]
|
|
91
|
+
- @objectstack/spec@9.11.0
|
|
92
|
+
|
|
3
93
|
## 9.10.0
|
|
4
94
|
|
|
5
95
|
### Minor Changes
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Expression, ExpressionInput } from '@objectstack/spec';
|
|
2
|
-
import { Environment } from '@marcbachmann/cel-js';
|
|
2
|
+
import { Environment, ASTNode } from '@marcbachmann/cel-js';
|
|
3
|
+
import { FilterCondition } from '@objectstack/spec/data';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* @objectstack/formula — public types
|
|
@@ -296,6 +297,81 @@ declare function normalizeExpressionTree(root: unknown, path?: string[]): {
|
|
|
296
297
|
error: EvalError;
|
|
297
298
|
} | null;
|
|
298
299
|
|
|
300
|
+
type CelFilterFailReason =
|
|
301
|
+
/** CEL did not parse (syntax error). */
|
|
302
|
+
'parse-error'
|
|
303
|
+
/** Shape is not pushdown-able (arithmetic, function call, relation traversal, …). */
|
|
304
|
+
| 'unsupported'
|
|
305
|
+
/** A required `variableRoot` reference was undefined/null in `variables`. */
|
|
306
|
+
| 'unresolved-variable';
|
|
307
|
+
type CelFilterCompileResult = {
|
|
308
|
+
ok: true;
|
|
309
|
+
filter: FilterCondition;
|
|
310
|
+
} | {
|
|
311
|
+
ok: false;
|
|
312
|
+
reason: CelFilterFailReason;
|
|
313
|
+
detail: string;
|
|
314
|
+
};
|
|
315
|
+
interface CelFilterCompileOptions {
|
|
316
|
+
/** Member-access roots that denote a record FIELD path. Default `['record']`. */
|
|
317
|
+
fieldRoots?: readonly string[];
|
|
318
|
+
/** Roots resolved as VALUES against {@link variables}. Default `['current_user']`. */
|
|
319
|
+
variableRoots?: readonly string[];
|
|
320
|
+
/**
|
|
321
|
+
* Value-resolution context, keyed by variable root. e.g.
|
|
322
|
+
* `{ current_user: { id, organization_id, org_user_ids } }`. A `record.*`
|
|
323
|
+
* (field) reference is NEVER resolved here — only `variableRoot` leaves are.
|
|
324
|
+
*/
|
|
325
|
+
variables?: Record<string, unknown>;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Compile a CEL predicate into a {@link FilterCondition}, resolving `variableRoot`
|
|
329
|
+
* leaves against `opts.variables`. Returns a discriminated result — never throws
|
|
330
|
+
* for an authoring-level fault; a `false` result with a reason is the caller's
|
|
331
|
+
* cue to fail closed (deny) or surface a compile error.
|
|
332
|
+
*/
|
|
333
|
+
declare function compileCelToFilter(input: string | {
|
|
334
|
+
source?: string;
|
|
335
|
+
}, opts?: CelFilterCompileOptions): CelFilterCompileResult;
|
|
336
|
+
/**
|
|
337
|
+
* Shape-only check: is this CEL predicate pushdown-able at all? Used by the
|
|
338
|
+
* authoring gate (ADR-0056 D4) to REJECT a predicate the runtime could only
|
|
339
|
+
* silently drop. Does not resolve `variables`.
|
|
340
|
+
*/
|
|
341
|
+
declare function isPushdownableCel(input: string | {
|
|
342
|
+
source?: string;
|
|
343
|
+
}, opts?: Pick<CelFilterCompileOptions, 'fieldRoots' | 'variableRoots'>): {
|
|
344
|
+
ok: true;
|
|
345
|
+
} | {
|
|
346
|
+
ok: false;
|
|
347
|
+
reason: CelFilterFailReason;
|
|
348
|
+
detail: string;
|
|
349
|
+
};
|
|
350
|
+
/**
|
|
351
|
+
* Lower a pre-parsed cel-js AST node — the variant that lets the interpreter and
|
|
352
|
+
* the compiler share ONE parse (ADR-0058 D6, "one AST, two backends").
|
|
353
|
+
*/
|
|
354
|
+
declare function lowerCelAst(ast: ASTNode, opts?: CelFilterCompileOptions, mode?: 'value' | 'shape'): CelFilterCompileResult;
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* matchesFilterCondition — evaluate a Mongo-style {@link FilterCondition} against
|
|
358
|
+
* ONE in-memory record (ADR-0058 D4/D6).
|
|
359
|
+
*
|
|
360
|
+
* This is the third backend for the canonical filter shape, completing the
|
|
361
|
+
* round-trip: `compileCelToFilter` lowers CEL → FilterCondition; the engine runs
|
|
362
|
+
* it as a `where`; `read-scope-sql` lowers it to SQL; and THIS evaluates it
|
|
363
|
+
* against a single record for write-side validation — the RLS `check` clause
|
|
364
|
+
* (post-image of an insert/update), where there is no query to push down to.
|
|
365
|
+
*
|
|
366
|
+
* Security posture: **fail closed.** Anything it cannot evaluate — a malformed
|
|
367
|
+
* node, an unknown operator, a nested relation object a flat record can't
|
|
368
|
+
* satisfy — returns `false` (the write is denied), never `true`. The operator
|
|
369
|
+
* vocabulary mirrors `read-scope-sql.ts` so the in-memory and SQL backends agree.
|
|
370
|
+
*/
|
|
371
|
+
|
|
372
|
+
/** True iff `record` satisfies `filter`. A null/empty filter matches everything. */
|
|
373
|
+
declare function matchesFilterCondition(record: Record<string, unknown>, filter: FilterCondition | null | undefined): boolean;
|
|
374
|
+
|
|
299
375
|
/**
|
|
300
376
|
* Shared expression validator (ADR-0032 §Decision 1/5).
|
|
301
377
|
*
|
|
@@ -393,4 +469,4 @@ declare function introspectScope(role: FieldRole, schema?: ExprSchemaHint): {
|
|
|
393
469
|
*/
|
|
394
470
|
declare const CEL_STDLIB_FUNCTIONS: string[];
|
|
395
471
|
|
|
396
|
-
export { CEL_STDLIB_FUNCTIONS, DEFAULT_LIMITS, type DialectEngine, type EvalContext, type EvalError, type EvalResult, type ExprSchemaHint, type ExprValidationError, type ExprValidationResult, ExpressionEngine, type FieldRole, type SeedPrimitive, type SeedValue, TEMPLATE_FORMATTERS, buildScope, celEngine, cronEngine, expectedDialect, formatValue, getEngine, hasDialect, introspectScope, normalizeExpression, normalizeExpressionTree, register, registerStdLib, resolveSeed, resolveSeedRecord, templateEngine, validateExpression };
|
|
472
|
+
export { CEL_STDLIB_FUNCTIONS, type CelFilterCompileOptions, type CelFilterCompileResult, type CelFilterFailReason, DEFAULT_LIMITS, type DialectEngine, type EvalContext, type EvalError, type EvalResult, type ExprSchemaHint, type ExprValidationError, type ExprValidationResult, ExpressionEngine, type FieldRole, type SeedPrimitive, type SeedValue, TEMPLATE_FORMATTERS, buildScope, celEngine, compileCelToFilter, cronEngine, expectedDialect, formatValue, getEngine, hasDialect, introspectScope, isPushdownableCel, lowerCelAst, matchesFilterCondition, normalizeExpression, normalizeExpressionTree, register, registerStdLib, resolveSeed, resolveSeedRecord, templateEngine, validateExpression };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Expression, ExpressionInput } from '@objectstack/spec';
|
|
2
|
-
import { Environment } from '@marcbachmann/cel-js';
|
|
2
|
+
import { Environment, ASTNode } from '@marcbachmann/cel-js';
|
|
3
|
+
import { FilterCondition } from '@objectstack/spec/data';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* @objectstack/formula — public types
|
|
@@ -296,6 +297,81 @@ declare function normalizeExpressionTree(root: unknown, path?: string[]): {
|
|
|
296
297
|
error: EvalError;
|
|
297
298
|
} | null;
|
|
298
299
|
|
|
300
|
+
type CelFilterFailReason =
|
|
301
|
+
/** CEL did not parse (syntax error). */
|
|
302
|
+
'parse-error'
|
|
303
|
+
/** Shape is not pushdown-able (arithmetic, function call, relation traversal, …). */
|
|
304
|
+
| 'unsupported'
|
|
305
|
+
/** A required `variableRoot` reference was undefined/null in `variables`. */
|
|
306
|
+
| 'unresolved-variable';
|
|
307
|
+
type CelFilterCompileResult = {
|
|
308
|
+
ok: true;
|
|
309
|
+
filter: FilterCondition;
|
|
310
|
+
} | {
|
|
311
|
+
ok: false;
|
|
312
|
+
reason: CelFilterFailReason;
|
|
313
|
+
detail: string;
|
|
314
|
+
};
|
|
315
|
+
interface CelFilterCompileOptions {
|
|
316
|
+
/** Member-access roots that denote a record FIELD path. Default `['record']`. */
|
|
317
|
+
fieldRoots?: readonly string[];
|
|
318
|
+
/** Roots resolved as VALUES against {@link variables}. Default `['current_user']`. */
|
|
319
|
+
variableRoots?: readonly string[];
|
|
320
|
+
/**
|
|
321
|
+
* Value-resolution context, keyed by variable root. e.g.
|
|
322
|
+
* `{ current_user: { id, organization_id, org_user_ids } }`. A `record.*`
|
|
323
|
+
* (field) reference is NEVER resolved here — only `variableRoot` leaves are.
|
|
324
|
+
*/
|
|
325
|
+
variables?: Record<string, unknown>;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Compile a CEL predicate into a {@link FilterCondition}, resolving `variableRoot`
|
|
329
|
+
* leaves against `opts.variables`. Returns a discriminated result — never throws
|
|
330
|
+
* for an authoring-level fault; a `false` result with a reason is the caller's
|
|
331
|
+
* cue to fail closed (deny) or surface a compile error.
|
|
332
|
+
*/
|
|
333
|
+
declare function compileCelToFilter(input: string | {
|
|
334
|
+
source?: string;
|
|
335
|
+
}, opts?: CelFilterCompileOptions): CelFilterCompileResult;
|
|
336
|
+
/**
|
|
337
|
+
* Shape-only check: is this CEL predicate pushdown-able at all? Used by the
|
|
338
|
+
* authoring gate (ADR-0056 D4) to REJECT a predicate the runtime could only
|
|
339
|
+
* silently drop. Does not resolve `variables`.
|
|
340
|
+
*/
|
|
341
|
+
declare function isPushdownableCel(input: string | {
|
|
342
|
+
source?: string;
|
|
343
|
+
}, opts?: Pick<CelFilterCompileOptions, 'fieldRoots' | 'variableRoots'>): {
|
|
344
|
+
ok: true;
|
|
345
|
+
} | {
|
|
346
|
+
ok: false;
|
|
347
|
+
reason: CelFilterFailReason;
|
|
348
|
+
detail: string;
|
|
349
|
+
};
|
|
350
|
+
/**
|
|
351
|
+
* Lower a pre-parsed cel-js AST node — the variant that lets the interpreter and
|
|
352
|
+
* the compiler share ONE parse (ADR-0058 D6, "one AST, two backends").
|
|
353
|
+
*/
|
|
354
|
+
declare function lowerCelAst(ast: ASTNode, opts?: CelFilterCompileOptions, mode?: 'value' | 'shape'): CelFilterCompileResult;
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* matchesFilterCondition — evaluate a Mongo-style {@link FilterCondition} against
|
|
358
|
+
* ONE in-memory record (ADR-0058 D4/D6).
|
|
359
|
+
*
|
|
360
|
+
* This is the third backend for the canonical filter shape, completing the
|
|
361
|
+
* round-trip: `compileCelToFilter` lowers CEL → FilterCondition; the engine runs
|
|
362
|
+
* it as a `where`; `read-scope-sql` lowers it to SQL; and THIS evaluates it
|
|
363
|
+
* against a single record for write-side validation — the RLS `check` clause
|
|
364
|
+
* (post-image of an insert/update), where there is no query to push down to.
|
|
365
|
+
*
|
|
366
|
+
* Security posture: **fail closed.** Anything it cannot evaluate — a malformed
|
|
367
|
+
* node, an unknown operator, a nested relation object a flat record can't
|
|
368
|
+
* satisfy — returns `false` (the write is denied), never `true`. The operator
|
|
369
|
+
* vocabulary mirrors `read-scope-sql.ts` so the in-memory and SQL backends agree.
|
|
370
|
+
*/
|
|
371
|
+
|
|
372
|
+
/** True iff `record` satisfies `filter`. A null/empty filter matches everything. */
|
|
373
|
+
declare function matchesFilterCondition(record: Record<string, unknown>, filter: FilterCondition | null | undefined): boolean;
|
|
374
|
+
|
|
299
375
|
/**
|
|
300
376
|
* Shared expression validator (ADR-0032 §Decision 1/5).
|
|
301
377
|
*
|
|
@@ -393,4 +469,4 @@ declare function introspectScope(role: FieldRole, schema?: ExprSchemaHint): {
|
|
|
393
469
|
*/
|
|
394
470
|
declare const CEL_STDLIB_FUNCTIONS: string[];
|
|
395
471
|
|
|
396
|
-
export { CEL_STDLIB_FUNCTIONS, DEFAULT_LIMITS, type DialectEngine, type EvalContext, type EvalError, type EvalResult, type ExprSchemaHint, type ExprValidationError, type ExprValidationResult, ExpressionEngine, type FieldRole, type SeedPrimitive, type SeedValue, TEMPLATE_FORMATTERS, buildScope, celEngine, cronEngine, expectedDialect, formatValue, getEngine, hasDialect, introspectScope, normalizeExpression, normalizeExpressionTree, register, registerStdLib, resolveSeed, resolveSeedRecord, templateEngine, validateExpression };
|
|
472
|
+
export { CEL_STDLIB_FUNCTIONS, type CelFilterCompileOptions, type CelFilterCompileResult, type CelFilterFailReason, DEFAULT_LIMITS, type DialectEngine, type EvalContext, type EvalError, type EvalResult, type ExprSchemaHint, type ExprValidationError, type ExprValidationResult, ExpressionEngine, type FieldRole, type SeedPrimitive, type SeedValue, TEMPLATE_FORMATTERS, buildScope, celEngine, compileCelToFilter, cronEngine, expectedDialect, formatValue, getEngine, hasDialect, introspectScope, isPushdownableCel, lowerCelAst, matchesFilterCondition, normalizeExpression, normalizeExpressionTree, register, registerStdLib, resolveSeed, resolveSeedRecord, templateEngine, validateExpression };
|