@almadar/runtime 2.2.1 → 2.3.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.
@@ -1,5 +1,5 @@
1
1
  import { Router } from 'express';
2
- import { I as IEventBus, i as RuntimeEvent, h as EventListener, U as Unsubscribe, E as EffectHandlers, g as Effect, T as TraitState } from './types-E5o2Jqe6.js';
2
+ import { I as IEventBus, i as RuntimeEvent, h as EventListener, U as Unsubscribe, E as EffectHandlers, g as Effect, T as TraitState } from './types-BrbvZxzX.js';
3
3
  import { OrbitalSchema, Orbital, Trait } from '@almadar/core';
4
4
 
5
5
  /**
@@ -517,10 +517,10 @@ interface OrbitalEventResponse {
517
517
  */
518
518
  interface EffectResult {
519
519
  /** Effect type that was executed */
520
- effect: 'persist' | 'call-service' | 'set';
520
+ effect: 'persist' | 'call-service' | 'set' | 'ref' | 'deref' | 'swap' | 'atomic';
521
521
  /** Action performed (e.g., 'create', 'update', 'delete' for persist) */
522
522
  action?: string;
523
- /** Entity type affected (for persist/set) */
523
+ /** Entity type affected (for persist/set/ref/deref/swap) */
524
524
  entityType?: string;
525
525
  /** Result data from the effect */
526
526
  data?: Record<string, unknown>;
@@ -1,4 +1,4 @@
1
1
  import 'express';
2
- export { n as EffectResult, L as LoaderConfig, O as OrbitalEventRequest, c as OrbitalEventResponse, o as OrbitalServerRuntime, d as OrbitalServerRuntimeConfig, P as PersistenceAdapter, R as RuntimeOrbital, h as RuntimeOrbitalSchema, i as RuntimeTrait, q as RuntimeTraitTick, r as createOrbitalServerRuntime } from './OrbitalServerRuntime-YLqyxdjz.js';
3
- import './types-E5o2Jqe6.js';
2
+ export { n as EffectResult, L as LoaderConfig, O as OrbitalEventRequest, c as OrbitalEventResponse, o as OrbitalServerRuntime, d as OrbitalServerRuntimeConfig, P as PersistenceAdapter, R as RuntimeOrbital, h as RuntimeOrbitalSchema, i as RuntimeTrait, q as RuntimeTraitTick, r as createOrbitalServerRuntime } from './OrbitalServerRuntime-Bel-jQ_o.js';
3
+ import './types-BrbvZxzX.js';
4
4
  import '@almadar/core';
@@ -1,6 +1,6 @@
1
- import { EventBus, createUnifiedLoader, preprocessSchema, StateMachineManager, createContextFromBindings, EffectExecutor } from './chunk-54P2HYHQ.js';
1
+ import { EventBus, createUnifiedLoader, preprocessSchema, StateMachineManager, createContextFromBindings, EffectExecutor } from './chunk-GCRRQAGZ.js';
2
2
  import { Router } from 'express';
3
- import { evaluateGuard } from '@almadar/evaluator';
3
+ import { evaluateGuard, evaluate } from '@almadar/evaluator';
4
4
  import { faker } from '@faker-js/faker';
5
5
  import * as fs from 'fs';
6
6
  import * as net from 'net';
@@ -1087,6 +1087,33 @@ var OrbitalServerRuntime = class {
1087
1087
  );
1088
1088
  }
1089
1089
  }
1090
+ const persistedTypes = /* @__PURE__ */ new Set();
1091
+ for (const er of effectResults) {
1092
+ if ((er.effect === "persist" || er.effect === "set" || er.effect === "swap") && er.success && er.entityType) {
1093
+ persistedTypes.add(er.entityType);
1094
+ }
1095
+ }
1096
+ const refTypes = /* @__PURE__ */ new Set();
1097
+ for (const trait of registered.schema.traits || []) {
1098
+ const sm = trait.stateMachine;
1099
+ const transitions = sm?.transitions ?? trait.transitions ?? [];
1100
+ for (const trans of transitions) {
1101
+ for (const eff of trans.effects ?? []) {
1102
+ if (Array.isArray(eff) && eff[0] === "ref" && typeof eff[1] === "string") {
1103
+ refTypes.add(eff[1]);
1104
+ }
1105
+ }
1106
+ }
1107
+ }
1108
+ for (const mutatedEntityType of persistedTypes) {
1109
+ if (refTypes.has(mutatedEntityType)) {
1110
+ try {
1111
+ const fresh = await this.persistence.list(mutatedEntityType);
1112
+ fetchedData[mutatedEntityType] = fresh;
1113
+ } catch {
1114
+ }
1115
+ }
1116
+ }
1090
1117
  const states = {};
1091
1118
  for (const [name, state] of registered.manager.getAllStates()) {
1092
1119
  states[name] = state.currentState;
@@ -1113,6 +1140,8 @@ var OrbitalServerRuntime = class {
1113
1140
  */
1114
1141
  async executeEffects(registered, traitName, effects, payload, entityData, entityId, emittedEvents, fetchedData, clientEffects, effectResults, user) {
1115
1142
  const entityType = registered.schema.entity.name;
1143
+ let bindingsRef = null;
1144
+ let contextRef = null;
1116
1145
  const handlers = {
1117
1146
  emit: (event, eventPayload) => {
1118
1147
  if (this.config.debug) {
@@ -1319,12 +1348,165 @@ var OrbitalServerRuntime = class {
1319
1348
  fetchedData[fetchEntityType] = entities;
1320
1349
  result = entities;
1321
1350
  }
1351
+ if (bindingsRef && result) {
1352
+ const records = Array.isArray(result) ? result : [result];
1353
+ if (records.length > 0) {
1354
+ const merged = Object.assign([...records], records[0]);
1355
+ bindingsRef[fetchEntityType] = merged;
1356
+ if (fetchEntityType === entityType) {
1357
+ bindingsRef.entity = merged;
1358
+ }
1359
+ }
1360
+ }
1322
1361
  return result;
1323
1362
  } catch (error) {
1324
1363
  console.error(`[OrbitalRuntime] Fetch error for ${fetchEntityType}:`, error);
1325
1364
  return null;
1326
1365
  }
1327
1366
  },
1367
+ // Resource operators: ref, deref, swap, watch, atomic
1368
+ ref: async (refEntityType, options) => {
1369
+ try {
1370
+ return await handlers.fetch(refEntityType, options);
1371
+ } catch (error) {
1372
+ console.error(`[OrbitalRuntime] ref error for ${refEntityType}:`, error);
1373
+ return null;
1374
+ }
1375
+ },
1376
+ deref: async (derefEntityType, options) => {
1377
+ try {
1378
+ let result = null;
1379
+ if (options?.id) {
1380
+ const entity = await this.persistence.getById(derefEntityType, options.id);
1381
+ if (entity) {
1382
+ fetchedData[derefEntityType] = [entity];
1383
+ result = entity;
1384
+ }
1385
+ } else {
1386
+ const entities = await this.persistence.list(derefEntityType);
1387
+ fetchedData[derefEntityType] = entities;
1388
+ result = entities;
1389
+ }
1390
+ if (bindingsRef && result) {
1391
+ const records = Array.isArray(result) ? result : [result];
1392
+ if (records.length > 0) {
1393
+ const merged = Object.assign([...records], records[0]);
1394
+ bindingsRef[derefEntityType] = merged;
1395
+ if (derefEntityType === entityType) {
1396
+ bindingsRef.entity = merged;
1397
+ }
1398
+ }
1399
+ }
1400
+ effectResults.push({
1401
+ effect: "deref",
1402
+ entityType: derefEntityType,
1403
+ success: true
1404
+ });
1405
+ return result;
1406
+ } catch (error) {
1407
+ effectResults.push({
1408
+ effect: "deref",
1409
+ entityType: derefEntityType,
1410
+ success: false,
1411
+ error: error instanceof Error ? error.message : String(error)
1412
+ });
1413
+ return null;
1414
+ }
1415
+ },
1416
+ swap: async (swapEntityType, swapEntityId, transform) => {
1417
+ try {
1418
+ const current = await this.persistence.getById(swapEntityType, swapEntityId);
1419
+ if (!current) {
1420
+ effectResults.push({
1421
+ effect: "swap",
1422
+ entityType: swapEntityType,
1423
+ success: false,
1424
+ error: `Entity ${swapEntityType}/${swapEntityId} not found`
1425
+ });
1426
+ return null;
1427
+ }
1428
+ const ctx = createContextFromBindings({
1429
+ current,
1430
+ entity: entityData,
1431
+ payload
1432
+ });
1433
+ let newData;
1434
+ if (Array.isArray(transform)) {
1435
+ const result = evaluate(
1436
+ transform,
1437
+ ctx
1438
+ );
1439
+ if (result && typeof result === "object" && !Array.isArray(result)) {
1440
+ newData = result;
1441
+ } else {
1442
+ newData = current;
1443
+ }
1444
+ } else if (typeof transform === "object" && transform !== null) {
1445
+ newData = { ...current, ...transform };
1446
+ } else {
1447
+ effectResults.push({
1448
+ effect: "swap",
1449
+ entityType: swapEntityType,
1450
+ success: false,
1451
+ error: "swap! transform must be an S-expression or object"
1452
+ });
1453
+ return null;
1454
+ }
1455
+ await this.persistence.update(swapEntityType, swapEntityId, newData);
1456
+ effectResults.push({
1457
+ effect: "swap",
1458
+ entityType: swapEntityType,
1459
+ data: { id: swapEntityId, ...newData },
1460
+ success: true
1461
+ });
1462
+ return newData;
1463
+ } catch (error) {
1464
+ effectResults.push({
1465
+ effect: "swap",
1466
+ entityType: swapEntityType,
1467
+ success: false,
1468
+ error: error instanceof Error ? error.message : String(error)
1469
+ });
1470
+ return null;
1471
+ }
1472
+ },
1473
+ watch: (_watchEntityType, _watchOptions) => {
1474
+ if (this.config.debug) {
1475
+ console.log(`[OrbitalRuntime] watch is a no-op on server: ${_watchEntityType}`);
1476
+ }
1477
+ },
1478
+ atomic: async (atomicEffects) => {
1479
+ let atomicFailed = false;
1480
+ let atomicError = "";
1481
+ const atomicExecutor = new EffectExecutor({
1482
+ handlers,
1483
+ bindings: bindingsRef ?? {},
1484
+ context: contextRef ?? { traitName, state: "unknown", transition: "unknown" },
1485
+ debug: this.config.debug
1486
+ });
1487
+ for (const innerEffect of atomicEffects) {
1488
+ if (atomicFailed) break;
1489
+ try {
1490
+ await atomicExecutor.execute(innerEffect);
1491
+ } catch (err) {
1492
+ atomicFailed = true;
1493
+ atomicError = err instanceof Error ? err.message : String(err);
1494
+ }
1495
+ }
1496
+ if (atomicFailed) {
1497
+ effectResults.push({
1498
+ effect: "atomic",
1499
+ success: false,
1500
+ error: `Atomic block failed: ${atomicError}`
1501
+ });
1502
+ } else {
1503
+ effectResults.push({
1504
+ effect: "atomic",
1505
+ success: true,
1506
+ data: { innerCount: atomicEffects.length }
1507
+ });
1508
+ }
1509
+ },
1328
1510
  // Client-side effects - collect for forwarding to client
1329
1511
  renderUI: (slot, pattern, props, priority) => {
1330
1512
  clientEffects.push(["render-ui", slot, pattern, props, priority]);
@@ -1353,12 +1535,17 @@ var OrbitalServerRuntime = class {
1353
1535
  user
1354
1536
  // @user bindings from Firebase auth
1355
1537
  };
1538
+ if (entityType) {
1539
+ bindings[entityType] = entityData;
1540
+ }
1541
+ bindingsRef = bindings;
1356
1542
  const context = {
1357
1543
  traitName,
1358
1544
  state: state?.currentState || "unknown",
1359
1545
  transition: "unknown",
1360
1546
  entityId
1361
1547
  };
1548
+ contextRef = context;
1362
1549
  const executor = new EffectExecutor({
1363
1550
  handlers,
1364
1551
  bindings,