@objectstack/formula 10.2.0 → 11.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/dist/index.mjs CHANGED
@@ -2,6 +2,7 @@
2
2
  import { Environment } from "@marcbachmann/cel-js";
3
3
 
4
4
  // src/stdlib.ts
5
+ import { createEvalUser } from "@objectstack/spec";
5
6
  function partsInTz(d, tz) {
6
7
  const parts = new Intl.DateTimeFormat("en-US", {
7
8
  timeZone: tz,
@@ -122,13 +123,37 @@ function registerNumericCoercions(env) {
122
123
  }
123
124
  return env;
124
125
  }
126
+ function toEvalUser(u) {
127
+ const legacyRole = typeof u.role === "string" && u.role ? [u.role] : [];
128
+ const roles = Array.isArray(u.roles) ? u.roles : [];
129
+ const canonical = createEvalUser({
130
+ id: u.id,
131
+ name: typeof u.name === "string" ? u.name : void 0,
132
+ email: typeof u.email === "string" ? u.email : void 0,
133
+ roles: [...roles, ...legacyRole],
134
+ organizationId: typeof u.organizationId === "string" || u.organizationId === null ? u.organizationId : void 0
135
+ });
136
+ if (typeof u.role === "string" && u.role) {
137
+ canonical.role = u.role;
138
+ }
139
+ return canonical;
140
+ }
125
141
  function buildScope(ctx) {
126
142
  const scope = {};
127
143
  if (ctx.record !== void 0) scope.record = ctx.record;
128
144
  if (ctx.previous !== void 0) scope.previous = ctx.previous;
129
145
  if (ctx.input !== void 0) scope.input = ctx.input;
130
146
  const os = {};
131
- if (ctx.user !== void 0) os.user = ctx.user;
147
+ if (ctx.user !== void 0) {
148
+ const currentUser = toEvalUser(ctx.user);
149
+ scope.current_user = currentUser;
150
+ scope.user = currentUser;
151
+ scope.ctx = {
152
+ ...typeof scope.ctx === "object" && scope.ctx !== null ? scope.ctx : {},
153
+ user: currentUser
154
+ };
155
+ os.user = currentUser;
156
+ }
132
157
  if (ctx.org !== void 0) os.org = ctx.org;
133
158
  if (ctx.env !== void 0) os.env = ctx.env;
134
159
  if (Object.keys(os).length > 0) scope.os = os;
@@ -218,6 +243,17 @@ function firstUndeclaredReference(source, knownFields = []) {
218
243
  }
219
244
  return null;
220
245
  }
246
+ function inferCelType(source, knownFields = []) {
247
+ if (typeof source !== "string" || !source.trim()) return null;
248
+ try {
249
+ const env = knownFields.length === 0 ? recordScopeEnv ?? (recordScopeEnv = buildScopedEnv([])) : buildScopedEnv(knownFields);
250
+ const result = env.parse(source).check?.();
251
+ if (!result || result.valid === false) return null;
252
+ return typeof result.type === "string" ? result.type : null;
253
+ } catch {
254
+ return null;
255
+ }
256
+ }
221
257
  function coerce(value) {
222
258
  if (typeof value === "bigint") {
223
259
  if (value >= BigInt(Number.MIN_SAFE_INTEGER) && value <= BigInt(Number.MAX_SAFE_INTEGER)) {
@@ -1167,6 +1203,30 @@ function levenshtein(a, b) {
1167
1203
  }
1168
1204
  return dp[m];
1169
1205
  }
1206
+ var ROLE_IN_RE = /(['"])([a-z0-9_]+)\1\s+in\s+(?:current_user|user|ctx\.user)\.roles\b/g;
1207
+ var ROLE_CONTAINS_RE = /(?:current_user|user|ctx\.user)\.roles\s*\.\s*contains\(\s*(['"])([a-z0-9_]+)\1\s*\)/g;
1208
+ var ROLE_EXISTS_RE = /(?:current_user|user|ctx\.user)\.roles\s*\.\s*exists\s*\([^,)]{0,64},[^)=]{0,128}==\s*(['"])([a-z0-9_]+)\1/g;
1209
+ var ROLE_EQ_RE = /(?:current_user|user|ctx\.user)\.role\s*==\s*(['"])([a-z0-9_]+)\1/g;
1210
+ function checkRoleCatalog(source, schema, errors) {
1211
+ const catalog = schema?.roleCatalog;
1212
+ if (!catalog || catalog.length === 0) return;
1213
+ const known = new Set(catalog);
1214
+ const seen = /* @__PURE__ */ new Set();
1215
+ for (const re of [ROLE_IN_RE, ROLE_CONTAINS_RE, ROLE_EXISTS_RE, ROLE_EQ_RE]) {
1216
+ re.lastIndex = 0;
1217
+ let m;
1218
+ while ((m = re.exec(source)) !== null) {
1219
+ const name = m[2];
1220
+ if (known.has(name) || seen.has(name)) continue;
1221
+ seen.add(name);
1222
+ const suggestion = nearest(name, catalog);
1223
+ errors.push({
1224
+ source,
1225
+ message: `unknown role \`${name}\` \u2014 not a defined role` + (suggestion ? `; did you mean \`${suggestion}\`?` : ".") + ` Valid roles: ${catalog.join(", ")}.`
1226
+ });
1227
+ }
1228
+ }
1229
+ }
1170
1230
  function validateExpression(role, input, schema) {
1171
1231
  const { dialect, source } = toSource2(input);
1172
1232
  const errors = [];
@@ -1198,6 +1258,7 @@ function validateExpression(role, input, schema) {
1198
1258
  });
1199
1259
  } else {
1200
1260
  checkFieldExistence(source, schema, errors);
1261
+ checkRoleCatalog(source, schema, errors);
1201
1262
  if (schema?.scope === "record") {
1202
1263
  const bare = firstUndeclaredReference(source);
1203
1264
  if (bare) {
@@ -1230,10 +1291,32 @@ function introspectScope(role, schema) {
1230
1291
  return {
1231
1292
  dialect: expectedDialect(role),
1232
1293
  fields: [...schema?.fields ?? []],
1233
- roots: ["record", "previous", "input", "os", "vars"],
1294
+ roots: ["record", "previous", "input", "os", "current_user", "user", "vars"],
1295
+ roles: [...schema?.roleCatalog ?? []],
1234
1296
  functions: CEL_STDLIB_FUNCTIONS
1235
1297
  };
1236
1298
  }
1299
+ function celTypeToValueType(celType) {
1300
+ switch (celType) {
1301
+ case "int":
1302
+ case "uint":
1303
+ case "double":
1304
+ return "number";
1305
+ case "string":
1306
+ return "text";
1307
+ case "bool":
1308
+ return "boolean";
1309
+ case "google.protobuf.Timestamp":
1310
+ return "date";
1311
+ default:
1312
+ return "unknown";
1313
+ }
1314
+ }
1315
+ function inferExpressionType(input, schema) {
1316
+ const { source } = toSource2(input);
1317
+ if (!source.trim()) return "unknown";
1318
+ return celTypeToValueType(inferCelType(source, schema?.fields));
1319
+ }
1237
1320
  var CEL_STDLIB_FUNCTIONS = [
1238
1321
  // Dates (registered stdlib)
1239
1322
  "now",
@@ -1287,6 +1370,7 @@ export {
1287
1370
  formatValue,
1288
1371
  getEngine,
1289
1372
  hasDialect,
1373
+ inferExpressionType,
1290
1374
  introspectScope,
1291
1375
  isPushdownableCel,
1292
1376
  lowerCelAst,