@almadar/runtime 6.46.0 → 6.47.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.
@@ -8,7 +8,7 @@ import { createLogger } from '@almadar/logger';
8
8
  import * as nodeModule from 'module';
9
9
  import { evaluateListenPayloadExpr, evaluateGuard, evaluate } from '@almadar/evaluator';
10
10
  import { DEFAULT_VIEWER, buildResolvedTraitConfigs, isInlineTrait, isEntityCall, applyListenPayloadMapping, personaFromIdentityRow, normalizeUserContext, isRuntimeEntity } from '@almadar/core';
11
- import { ownerFieldsFromSchema, identityEntityName } from '@almadar/core/mock';
11
+ import { ownerFieldsFromSchema, identityEntityName, entityAccessPolicies } from '@almadar/core/mock';
12
12
 
13
13
  // src/identity/routing.ts
14
14
  function eventRouteKey(eventName, eventId) {
@@ -33,6 +33,41 @@ function buildSourceMatcher(src, listenerOrbital) {
33
33
  const wantedTrait = src.trait;
34
34
  return (source) => !!source && source.orbital === wantedOrbital && source.trait === wantedTrait;
35
35
  }
36
+ function applyRowAccess(rows, policy, filter, bindings) {
37
+ if (policy === void 0 && filter === void 0) {
38
+ return rows;
39
+ }
40
+ const predicates = [policy, filter].filter((p) => p !== void 0);
41
+ return rows.filter((entity) => {
42
+ const ctx = createContextFromBindings(
43
+ { entity, payload: bindings.payload, current: entity, user: bindings.user, config: bindings.config },
44
+ false
45
+ );
46
+ return predicates.every((predicate) => {
47
+ try {
48
+ return Boolean(evaluate(predicate, ctx));
49
+ } catch {
50
+ return false;
51
+ }
52
+ });
53
+ });
54
+ }
55
+ function checkMutationAccess(row, policy, bindings) {
56
+ if (policy === void 0) {
57
+ return true;
58
+ }
59
+ const ctx = createContextFromBindings(
60
+ { entity: row, payload: bindings.payload, current: row, user: bindings.user, config: bindings.config },
61
+ false
62
+ );
63
+ try {
64
+ return Boolean(evaluate(policy, ctx));
65
+ } catch {
66
+ return false;
67
+ }
68
+ }
69
+
70
+ // src/OrbitalServerRuntime.ts
36
71
  var _resolvedNodeRequire = null;
37
72
  function nodeRequire(modulePath) {
38
73
  if (!_resolvedNodeRequire) {
@@ -1316,8 +1351,15 @@ var OrbitalServerRuntime = class {
1316
1351
  if (action === "create" || action === "update") {
1317
1352
  this.validateRelationCardinality(type, data || {});
1318
1353
  }
1354
+ const accessBindings = { user: bindingsRef?.user, payload: bindingsRef?.payload, config: bindingsRef?.config };
1355
+ const mutationPolicy = this.resolvedSchema ? entityAccessPolicies(this.resolvedSchema, type)?.[action === "create" ? "create" : action === "update" ? "update" : "delete"] : void 0;
1319
1356
  switch (action) {
1320
1357
  case "create": {
1358
+ if (!checkMutationAccess(data || {}, mutationPolicy, accessBindings)) {
1359
+ throw new Error(
1360
+ `@create denied: the declared access policy for '${type}' rejected this row`
1361
+ );
1362
+ }
1321
1363
  const { id } = await this.persistence.create(type, data || {});
1322
1364
  resultData = { id, ...data || {} };
1323
1365
  break;
@@ -1325,6 +1367,14 @@ var OrbitalServerRuntime = class {
1325
1367
  case "update":
1326
1368
  if (data?.id || entityId) {
1327
1369
  const updateId = data?.id || entityId;
1370
+ if (mutationPolicy !== void 0) {
1371
+ const existing = await this.persistence.getById(type, updateId);
1372
+ if (!existing || !checkMutationAccess(existing, mutationPolicy, accessBindings)) {
1373
+ throw new Error(
1374
+ `@update denied: the declared access policy for '${type}' rejected this row`
1375
+ );
1376
+ }
1377
+ }
1328
1378
  await this.persistence.update(type, updateId, data || {});
1329
1379
  const updated = await this.persistence.getById(type, updateId);
1330
1380
  resultData = updated || { id: updateId, ...data || {} };
@@ -1335,6 +1385,14 @@ var OrbitalServerRuntime = class {
1335
1385
  const nestedId = typeof data === "object" && data !== null ? data.id : void 0;
1336
1386
  const deleteId = directId ?? nestedId ?? entityId;
1337
1387
  if (deleteId) {
1388
+ if (mutationPolicy !== void 0) {
1389
+ const existing = await this.persistence.getById(type, deleteId);
1390
+ if (!existing || !checkMutationAccess(existing, mutationPolicy, accessBindings)) {
1391
+ throw new Error(
1392
+ `@delete denied: the declared access policy for '${type}' rejected this row`
1393
+ );
1394
+ }
1395
+ }
1338
1396
  await this.enforceOnDeleteRules(type, deleteId);
1339
1397
  await this.persistence.delete(type, deleteId);
1340
1398
  resultData = { id: deleteId, deleted: true };
@@ -1423,9 +1481,11 @@ var OrbitalServerRuntime = class {
1423
1481
  try {
1424
1482
  let result = null;
1425
1483
  let total = 0;
1484
+ const readPolicy = this.resolvedSchema ? entityAccessPolicies(this.resolvedSchema, fetchEntityType)?.read : void 0;
1485
+ const accessBindings = { user: bindingsRef?.user, payload: bindingsRef?.payload, config: bindingsRef?.config };
1426
1486
  if (options?.id) {
1427
1487
  const entity = await this.persistence.getById(fetchEntityType, options.id);
1428
- if (entity) {
1488
+ if (entity && applyRowAccess([entity], readPolicy, void 0, accessBindings).length > 0) {
1429
1489
  if (options?.include && options.include.length > 0) {
1430
1490
  await this.populateRelations([entity], fetchEntityType, options.include);
1431
1491
  }
@@ -1435,24 +1495,12 @@ var OrbitalServerRuntime = class {
1435
1495
  }
1436
1496
  } else {
1437
1497
  let entities = await this.persistence.list(fetchEntityType);
1438
- if (options?.filter !== void 0 && options.filter !== null) {
1439
- const predicate = options.filter;
1440
- entities = entities.filter((entity) => {
1441
- const ctx = createContextFromBindings(
1442
- { entity, payload: bindingsRef?.payload, current: entity, user: bindingsRef?.user },
1443
- false
1444
- );
1445
- try {
1446
- return Boolean(evaluate(predicate, ctx));
1447
- } catch (err) {
1448
- effectLog.error("fetch:filter-eval-error", {
1449
- entityType: fetchEntityType,
1450
- error: err instanceof Error ? err : String(err)
1451
- });
1452
- return false;
1453
- }
1454
- });
1455
- }
1498
+ entities = applyRowAccess(
1499
+ entities,
1500
+ readPolicy,
1501
+ options?.filter !== void 0 && options.filter !== null ? options.filter : void 0,
1502
+ accessBindings
1503
+ );
1456
1504
  total = entities.length;
1457
1505
  if (options?.offset && options.offset > 0) {
1458
1506
  entities = entities.slice(options.offset);
package/dist/index.js CHANGED
@@ -424,7 +424,7 @@ function createServerEffectHandlers(opts) {
424
424
  const predicate = options.filter;
425
425
  entities = entities.filter((entity) => {
426
426
  const ctx = createContextFromBindings(
427
- { entity, payload: bindings?.payload, current: entity },
427
+ { entity, payload: bindings?.payload, current: entity, user: bindings?.user, config: bindings?.config },
428
428
  false
429
429
  );
430
430
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/runtime",
3
- "version": "6.46.0",
3
+ "version": "6.47.0",
4
4
  "description": "Interpreted runtime for Almadar orbital applications (OrbitalServerRuntime)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -52,11 +52,11 @@
52
52
  "access": "public"
53
53
  },
54
54
  "dependencies": {
55
- "@almadar/core": "^10.44.0",
55
+ "@almadar/core": "^10.45.0",
56
56
  "@almadar/evaluator": "^2.38.0",
57
57
  "@almadar/logger": "^1.10.0",
58
58
  "@almadar/server": "^2.30.0",
59
- "@almadar/std": "^16.154.0"
59
+ "@almadar/std": "^16.155.0"
60
60
  },
61
61
  "peerDependencies": {
62
62
  "express": "^5.0.0"