@almadar/runtime 3.1.4 → 3.2.1

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,4 +1,4 @@
1
- import { I as IEventBus } from './types-CM6txTNy.js';
1
+ import { I as IEventBus } from './types-B8OfRFfV.js';
2
2
  import { EventPayload } from '@almadar/core';
3
3
 
4
4
  /**
@@ -177,6 +177,14 @@ function createLogger(namespace) {
177
177
 
178
178
  // src/BindingResolver.ts
179
179
  var bindLog = createLogger("almadar:runtime:bindings");
180
+ var CLIENT_ONLY_BINDING_ROOTS = /* @__PURE__ */ new Set(["trait"]);
181
+ function isClientOnlyBinding(value) {
182
+ if (!value.startsWith("@")) return false;
183
+ const afterAt = value.slice(1);
184
+ const firstDot = afterAt.indexOf(".");
185
+ const root = firstDot === -1 ? afterAt : afterAt.slice(0, firstDot);
186
+ return CLIENT_ONLY_BINDING_ROOTS.has(root);
187
+ }
180
188
  function interpolateProps(props, ctx) {
181
189
  const result = {};
182
190
  for (const [key, value] of Object.entries(props)) {
@@ -201,6 +209,10 @@ function interpolateValue(value, ctx) {
201
209
  }
202
210
  function interpolateString(value, ctx) {
203
211
  if (value.startsWith("@") && isPureBinding(value)) {
212
+ if (isClientOnlyBinding(value)) {
213
+ bindLog.debug("passthrough:client-only", { binding: value });
214
+ return value;
215
+ }
204
216
  const resolved = resolveBinding(value, ctx);
205
217
  bindLog.debug("resolve", { binding: value, resolvedType: typeof resolved });
206
218
  return resolved;
@@ -215,6 +227,9 @@ function isPureBinding(value) {
215
227
  }
216
228
  function interpolateEmbeddedBindings(value, ctx) {
217
229
  return value.replace(/@[\w]+(?:\.[\w]+)*/g, (match) => {
230
+ if (isClientOnlyBinding(match)) {
231
+ return match;
232
+ }
218
233
  const resolved = resolveBinding(match, ctx);
219
234
  return resolved !== void 0 ? String(resolved) : match;
220
235
  });
@@ -886,13 +901,13 @@ var EffectExecutor = class {
886
901
  }
887
902
  const { operator, args } = parsed;
888
903
  const isCompound = operator === "do" || operator === "when";
889
- const isSet3Elem = operator === "set" && args.length === 2 && typeof args[0] === "string" && args[0].startsWith("@entity.");
904
+ const isSetPathForm = operator === "set" && args.length >= 2 && typeof args[0] === "string" && args[0].startsWith("@entity.");
890
905
  let resolvedArgs;
891
906
  if (isCompound) {
892
907
  resolvedArgs = args;
893
- } else if (isSet3Elem) {
908
+ } else if (isSetPathForm) {
894
909
  const ctx = createContextFromBindings(this.bindings, this.strictBindings, this.contextExtensions);
895
- resolvedArgs = [args[0], interpolateValue(args[1], ctx)];
910
+ resolvedArgs = [args[0], ...args.slice(1).map((a) => interpolateValue(a, ctx))];
896
911
  } else {
897
912
  resolvedArgs = resolveArgs(args, this.bindings, this.strictBindings, this.contextExtensions);
898
913
  }
@@ -972,6 +987,59 @@ var EffectExecutor = class {
972
987
  return results;
973
988
  }
974
989
  // ==========================================================================
990
+ // `emit:` config extraction — close-the-circuit on async/reactive ops.
991
+ //
992
+ // `fetch`, `persist`, `call-service`, `set`, `ref`, `os/watch-*` may carry
993
+ // an `emit:` key in their options object. After the effect's work finishes,
994
+ // the runtime fires the author-configured bus event so downstream state
995
+ // machines can branch on success/failure without stitching async/sequence.
996
+ //
997
+ // See `docs/Almadar_Std_Gaps.md` §3.1 and the emit-config plan for the
998
+ // semantics. The compiled-path shell does the same work in generated JS.
999
+ // ==========================================================================
1000
+ extractEmitConfig(rawOpt) {
1001
+ if (!rawOpt || typeof rawOpt !== "object" || Array.isArray(rawOpt)) {
1002
+ return void 0;
1003
+ }
1004
+ const obj = rawOpt;
1005
+ const emitBlock = obj.emit;
1006
+ if (!emitBlock || typeof emitBlock !== "object" || Array.isArray(emitBlock)) {
1007
+ return void 0;
1008
+ }
1009
+ const block = emitBlock;
1010
+ const asStr = (v) => typeof v === "string" ? v : void 0;
1011
+ return {
1012
+ success: asStr(block.success),
1013
+ failure: asStr(block.failure),
1014
+ on_change: asStr(block.on_change) ?? asStr(block.onChange),
1015
+ on_message: asStr(block.on_message) ?? asStr(block.onMessage)
1016
+ };
1017
+ }
1018
+ emitSuccess(emit, key, payload) {
1019
+ const eventName = emit?.[key];
1020
+ if (eventName) {
1021
+ this.handlers.emit(eventName, payload);
1022
+ }
1023
+ }
1024
+ emitFailure(emit, err) {
1025
+ if (!emit?.failure) return;
1026
+ const error = err instanceof Error ? err.message : String(err);
1027
+ this.handlers.emit(emit.failure, { error });
1028
+ }
1029
+ /**
1030
+ * Narrow the generic emit config into the `os/watch-*` handler surface
1031
+ * (only `on_message` + `failure` are meaningful for streaming ops).
1032
+ * Returns `undefined` when neither field is set so handlers can test
1033
+ * with a single `if (emit)` guard.
1034
+ */
1035
+ osEmit(emit) {
1036
+ if (!emit || !emit.on_message && !emit.failure) return void 0;
1037
+ return {
1038
+ on_message: emit.on_message,
1039
+ failure: emit.failure
1040
+ };
1041
+ }
1042
+ // ==========================================================================
975
1043
  // Effect Dispatch
976
1044
  // ==========================================================================
977
1045
  async dispatch(operator, args) {
@@ -988,10 +1056,12 @@ var EffectExecutor = class {
988
1056
  let entityId;
989
1057
  let field;
990
1058
  let value;
991
- if (args.length === 2 && typeof args[0] === "string" && args[0].startsWith("@entity.")) {
1059
+ let emitCfg;
1060
+ if (typeof args[0] === "string" && args[0].startsWith("@entity.")) {
992
1061
  const path = args[0];
993
1062
  field = path.slice("@entity.".length);
994
1063
  value = args[1];
1064
+ emitCfg = this.extractEmitConfig(args[2]);
995
1065
  entityId = typeof entity?.["id"] === "string" ? entity["id"] : void 0;
996
1066
  if (!entityId) {
997
1067
  effectLog.warn("set:missing-entity-id", { path });
@@ -1001,22 +1071,33 @@ var EffectExecutor = class {
1001
1071
  entityId = args[0];
1002
1072
  field = args[1];
1003
1073
  value = args[2];
1074
+ emitCfg = this.extractEmitConfig(args[3]);
1004
1075
  }
1005
1076
  this.handlers.set(entityId, field, value);
1006
1077
  if (entity && entity["id"] === entityId) {
1007
1078
  entity[field] = value;
1008
1079
  }
1080
+ this.emitSuccess(emitCfg, "success", value);
1009
1081
  break;
1010
1082
  }
1011
1083
  case "persist": {
1012
1084
  const action = args[0];
1013
- if (action === "batch") {
1014
- const operations = args[1];
1015
- await this.handlers.persist("batch", "", { operations });
1016
- } else {
1017
- const entityType = args[1];
1018
- const data = args[2];
1019
- await this.handlers.persist(action, entityType, data);
1085
+ const last = args[args.length - 1];
1086
+ const emitCfg = last && typeof last === "object" && !Array.isArray(last) && "emit" in last ? this.extractEmitConfig(last) : void 0;
1087
+ try {
1088
+ if (action === "batch") {
1089
+ const operations = args[1];
1090
+ await this.handlers.persist("batch", "", { operations });
1091
+ this.emitSuccess(emitCfg, "success", operations);
1092
+ } else {
1093
+ const entityType = args[1];
1094
+ const data = args[2];
1095
+ await this.handlers.persist(action, entityType, data);
1096
+ this.emitSuccess(emitCfg, "success", data);
1097
+ }
1098
+ } catch (err) {
1099
+ this.emitFailure(emitCfg, err);
1100
+ throw err;
1020
1101
  }
1021
1102
  break;
1022
1103
  }
@@ -1024,7 +1105,14 @@ var EffectExecutor = class {
1024
1105
  const service = args[0];
1025
1106
  const action = args[1];
1026
1107
  const params = args[2];
1027
- await this.handlers.callService(service, action, params);
1108
+ const emitCfg = this.extractEmitConfig(args[3]);
1109
+ try {
1110
+ const result = await this.handlers.callService(service, action, params);
1111
+ this.emitSuccess(emitCfg, "success", result);
1112
+ } catch (err) {
1113
+ this.emitFailure(emitCfg, err);
1114
+ throw err;
1115
+ }
1028
1116
  break;
1029
1117
  }
1030
1118
  case "fetch": {
@@ -1032,7 +1120,14 @@ var EffectExecutor = class {
1032
1120
  const entityType = args[0];
1033
1121
  const rawOpt = args[1];
1034
1122
  const options = typeof rawOpt === "string" ? { id: rawOpt } : rawOpt;
1035
- await this.handlers.fetch(entityType, options);
1123
+ const emitCfg = this.extractEmitConfig(rawOpt);
1124
+ try {
1125
+ const result = await this.handlers.fetch(entityType, options);
1126
+ this.emitSuccess(emitCfg, "success", result);
1127
+ } catch (err) {
1128
+ this.emitFailure(emitCfg, err);
1129
+ throw err;
1130
+ }
1036
1131
  } else {
1037
1132
  this.logUnsupported("fetch");
1038
1133
  }
@@ -1043,12 +1138,20 @@ var EffectExecutor = class {
1043
1138
  const refEntityType = args[0];
1044
1139
  const rawRefOpt = args[1];
1045
1140
  const refOptions = typeof rawRefOpt === "string" ? { id: rawRefOpt } : rawRefOpt;
1046
- if (this.handlers.ref) {
1047
- await this.handlers.ref(refEntityType, refOptions);
1048
- } else if (this.handlers.fetch) {
1049
- await this.handlers.fetch(refEntityType, refOptions);
1050
- } else {
1051
- this.logUnsupported("ref");
1141
+ const refEmitCfg = this.extractEmitConfig(rawRefOpt);
1142
+ try {
1143
+ let result = void 0;
1144
+ if (this.handlers.ref) {
1145
+ result = await this.handlers.ref(refEntityType, refOptions);
1146
+ } else if (this.handlers.fetch) {
1147
+ result = await this.handlers.fetch(refEntityType, refOptions);
1148
+ } else {
1149
+ this.logUnsupported("ref");
1150
+ }
1151
+ this.emitSuccess(refEmitCfg, "on_change", result);
1152
+ } catch (err) {
1153
+ this.emitFailure(refEmitCfg, err);
1154
+ throw err;
1052
1155
  }
1053
1156
  break;
1054
1157
  }
@@ -1185,11 +1288,18 @@ var EffectExecutor = class {
1185
1288
  break;
1186
1289
  }
1187
1290
  // OS trigger operators (server-side only)
1291
+ //
1292
+ // Each may carry `emit:` inside a trailing options object. The
1293
+ // executor extracts it and passes it to the handler so the
1294
+ // hardcoded fallback name (e.g. OS_CRON_FIRE) is replaced by the
1295
+ // author-configured event.
1188
1296
  case "os/watch-files": {
1189
1297
  if (this.handlers.osWatchFiles) {
1190
1298
  const glob = args[0];
1191
- const options = args[1];
1192
- this.handlers.osWatchFiles(glob, options ?? {});
1299
+ const rawOptions = args[1];
1300
+ const emitCfg = this.extractEmitConfig(rawOptions);
1301
+ const options = rawOptions ? { recursive: rawOptions.recursive, debounce: rawOptions.debounce } : {};
1302
+ this.handlers.osWatchFiles(glob, options, this.osEmit(emitCfg));
1193
1303
  } else {
1194
1304
  this.logUnsupported("os/watch-files");
1195
1305
  }
@@ -1197,7 +1307,12 @@ var EffectExecutor = class {
1197
1307
  }
1198
1308
  case "os/watch-process": {
1199
1309
  if (this.handlers.osWatchProcess) {
1200
- this.handlers.osWatchProcess(args[0], args[1]);
1310
+ const emitCfg = this.extractEmitConfig(args[2]);
1311
+ this.handlers.osWatchProcess(
1312
+ args[0],
1313
+ args[1],
1314
+ this.osEmit(emitCfg)
1315
+ );
1201
1316
  } else {
1202
1317
  this.logUnsupported("os/watch-process");
1203
1318
  }
@@ -1205,7 +1320,12 @@ var EffectExecutor = class {
1205
1320
  }
1206
1321
  case "os/watch-port": {
1207
1322
  if (this.handlers.osWatchPort) {
1208
- this.handlers.osWatchPort(args[0], args[1] ?? "tcp");
1323
+ const emitCfg = this.extractEmitConfig(args[2]);
1324
+ this.handlers.osWatchPort(
1325
+ args[0],
1326
+ args[1] ?? "tcp",
1327
+ this.osEmit(emitCfg)
1328
+ );
1209
1329
  } else {
1210
1330
  this.logUnsupported("os/watch-port");
1211
1331
  }
@@ -1213,7 +1333,12 @@ var EffectExecutor = class {
1213
1333
  }
1214
1334
  case "os/watch-http": {
1215
1335
  if (this.handlers.osWatchHttp) {
1216
- this.handlers.osWatchHttp(args[0], args[1]);
1336
+ const emitCfg = this.extractEmitConfig(args[2]);
1337
+ this.handlers.osWatchHttp(
1338
+ args[0],
1339
+ args[1],
1340
+ this.osEmit(emitCfg)
1341
+ );
1217
1342
  } else {
1218
1343
  this.logUnsupported("os/watch-http");
1219
1344
  }
@@ -1221,7 +1346,8 @@ var EffectExecutor = class {
1221
1346
  }
1222
1347
  case "os/watch-cron": {
1223
1348
  if (this.handlers.osWatchCron) {
1224
- this.handlers.osWatchCron(args[0]);
1349
+ const emitCfg = this.extractEmitConfig(args[1]);
1350
+ this.handlers.osWatchCron(args[0], this.osEmit(emitCfg));
1225
1351
  } else {
1226
1352
  this.logUnsupported("os/watch-cron");
1227
1353
  }
@@ -1229,7 +1355,8 @@ var EffectExecutor = class {
1229
1355
  }
1230
1356
  case "os/watch-signal": {
1231
1357
  if (this.handlers.osWatchSignal) {
1232
- this.handlers.osWatchSignal(args[0]);
1358
+ const emitCfg = this.extractEmitConfig(args[1]);
1359
+ this.handlers.osWatchSignal(args[0], this.osEmit(emitCfg));
1233
1360
  } else {
1234
1361
  this.logUnsupported("os/watch-signal");
1235
1362
  }
@@ -1237,7 +1364,8 @@ var EffectExecutor = class {
1237
1364
  }
1238
1365
  case "os/watch-env": {
1239
1366
  if (this.handlers.osWatchEnv) {
1240
- this.handlers.osWatchEnv(args[0]);
1367
+ const emitCfg = this.extractEmitConfig(args[1]);
1368
+ this.handlers.osWatchEnv(args[0], this.osEmit(emitCfg));
1241
1369
  } else {
1242
1370
  this.logUnsupported("os/watch-env");
1243
1371
  }
@@ -2559,5 +2687,5 @@ function parseNamespacedEvent(eventName) {
2559
2687
  }
2560
2688
 
2561
2689
  export { EffectExecutor, EventBus, HANDLER_MANIFEST, StateMachineManager, containsBindings, createContextFromBindings, createInitialTraitState, createTestExecutor, createUnifiedLoader, extractBindings, findInitialState, findTransition, getIsolatedCollectionName, getNamespacedEvent, interpolateProps, interpolateValue, isNamespacedEvent, normalizeEventKey, parseNamespacedEvent, preprocessSchema, processEvent };
2562
- //# sourceMappingURL=chunk-6PAKTVTR.js.map
2563
- //# sourceMappingURL=chunk-6PAKTVTR.js.map
2690
+ //# sourceMappingURL=chunk-PXASRZKG.js.map
2691
+ //# sourceMappingURL=chunk-PXASRZKG.js.map