@almadar/runtime 2.1.2 → 2.2.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-9hbY6RWC.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-E5o2Jqe6.js';
3
3
  import { OrbitalSchema, Orbital, Trait } from '@almadar/core';
4
4
 
5
5
  /**
@@ -602,6 +602,7 @@ declare class OrbitalServerRuntime {
602
602
  private preprocessedCache;
603
603
  private entitySharingMap;
604
604
  private eventNamespaceMap;
605
+ private osHandlers;
605
606
  constructor(config?: OrbitalServerRuntimeConfig);
606
607
  /**
607
608
  * Register an OrbitalSchema for execution.
@@ -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-MBjDCrt8.js';
3
- import './types-9hbY6RWC.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-YLqyxdjz.js';
3
+ import './types-E5o2Jqe6.js';
4
4
  import '@almadar/core';
@@ -1,7 +1,10 @@
1
- import { EventBus, createUnifiedLoader, preprocessSchema, StateMachineManager, createContextFromBindings, EffectExecutor } from './chunk-UBXKXCSM.js';
1
+ import { EventBus, createUnifiedLoader, preprocessSchema, StateMachineManager, createContextFromBindings, EffectExecutor } from './chunk-54P2HYHQ.js';
2
2
  import { Router } from 'express';
3
3
  import { evaluateGuard } from '@almadar/evaluator';
4
4
  import { faker } from '@faker-js/faker';
5
+ import * as fs from 'fs';
6
+ import * as net from 'net';
7
+ import { execSync } from 'child_process';
5
8
 
6
9
  var MockPersistenceAdapter = class {
7
10
  stores = /* @__PURE__ */ new Map();
@@ -263,6 +266,258 @@ var MockPersistenceAdapter = class {
263
266
  return store.size;
264
267
  }
265
268
  };
269
+ function globToRegex(glob) {
270
+ let regex = "";
271
+ let i = 0;
272
+ while (i < glob.length) {
273
+ const c = glob[i];
274
+ if (c === "*") {
275
+ if (glob[i + 1] === "*") {
276
+ regex += ".*";
277
+ i += 2;
278
+ if (glob[i] === "/") i++;
279
+ continue;
280
+ }
281
+ regex += "[^/]*";
282
+ } else if (c === "?") {
283
+ regex += "[^/]";
284
+ } else if (c === ".") {
285
+ regex += "\\.";
286
+ } else if (c === "/" || c === "-" || c === "_") {
287
+ regex += c;
288
+ } else if (/[{}()[\]^$+|\\]/.test(c)) {
289
+ regex += "\\" + c;
290
+ } else {
291
+ regex += c;
292
+ }
293
+ i++;
294
+ }
295
+ return new RegExp("^" + regex + "$");
296
+ }
297
+ function parseCronField(field, min, max) {
298
+ const values = /* @__PURE__ */ new Set();
299
+ for (const part of field.split(",")) {
300
+ if (part === "*") {
301
+ for (let i = min; i <= max; i++) values.add(i);
302
+ } else if (part.includes("/")) {
303
+ const [range, stepStr] = part.split("/");
304
+ const step = parseInt(stepStr, 10);
305
+ const start = range === "*" ? min : parseInt(range, 10);
306
+ for (let i = start; i <= max; i += step) values.add(i);
307
+ } else if (part.includes("-")) {
308
+ const [lo, hi] = part.split("-").map(Number);
309
+ for (let i = lo; i <= hi; i++) values.add(i);
310
+ } else {
311
+ values.add(parseInt(part, 10));
312
+ }
313
+ }
314
+ return values;
315
+ }
316
+ function parseCron(expression) {
317
+ const parts = expression.trim().split(/\s+/);
318
+ if (parts.length !== 5) {
319
+ throw new Error(`Invalid cron expression (expected 5 fields): ${expression}`);
320
+ }
321
+ return {
322
+ minute: parseCronField(parts[0], 0, 59),
323
+ hour: parseCronField(parts[1], 0, 23),
324
+ day: parseCronField(parts[2], 1, 31),
325
+ month: parseCronField(parts[3], 1, 12),
326
+ weekday: parseCronField(parts[4], 0, 6)
327
+ };
328
+ }
329
+ function cronMatches(fields, date) {
330
+ return fields.minute.has(date.getMinutes()) && fields.hour.has(date.getHours()) && fields.day.has(date.getDate()) && fields.month.has(date.getMonth() + 1) && fields.weekday.has(date.getDay());
331
+ }
332
+ function createOsHandlers(ctx) {
333
+ const cwd = ctx.cwd ?? process.cwd();
334
+ const watchers = [];
335
+ const intervals = [];
336
+ const signalHandlers = [];
337
+ let httpWatchActive = false;
338
+ const debounceConfig = /* @__PURE__ */ new Map();
339
+ const debounceTimers = /* @__PURE__ */ new Map();
340
+ function debouncedEmit(eventType, payload) {
341
+ const ms = debounceConfig.get(eventType);
342
+ if (ms !== void 0 && ms > 0) {
343
+ const existing = debounceTimers.get(eventType);
344
+ if (existing) clearTimeout(existing);
345
+ debounceTimers.set(
346
+ eventType,
347
+ setTimeout(() => {
348
+ debounceTimers.delete(eventType);
349
+ ctx.emitEvent(eventType, payload);
350
+ }, ms)
351
+ );
352
+ } else {
353
+ ctx.emitEvent(eventType, payload);
354
+ }
355
+ }
356
+ const handlers = {
357
+ osWatchFiles: (glob, options) => {
358
+ const recursive = options.recursive !== false;
359
+ const pattern = globToRegex(glob);
360
+ try {
361
+ const watcher = fs.watch(cwd, { recursive }, (_event, filename) => {
362
+ if (filename && pattern.test(filename)) {
363
+ debouncedEmit("OS_FILE_MODIFIED", {
364
+ file: filename,
365
+ glob,
366
+ cwd
367
+ });
368
+ }
369
+ });
370
+ watchers.push(watcher);
371
+ } catch (err) {
372
+ console.warn("[os/watch-files] Failed to start watcher:", err);
373
+ }
374
+ },
375
+ osWatchProcess: (name, subcommand) => {
376
+ const searchTerm = subcommand ? `${name} ${subcommand}` : name;
377
+ let wasRunning = false;
378
+ const interval = setInterval(() => {
379
+ let isRunning = false;
380
+ try {
381
+ const result = execSync(`pgrep -f "${searchTerm}" 2>/dev/null`, {
382
+ encoding: "utf-8",
383
+ stdio: ["pipe", "pipe", "pipe"]
384
+ });
385
+ isRunning = result.trim().length > 0;
386
+ } catch {
387
+ isRunning = false;
388
+ }
389
+ if (isRunning && !wasRunning) {
390
+ debouncedEmit("OS_PROCESS_STARTED", { process: name, subcommand: subcommand ?? null });
391
+ } else if (!isRunning && wasRunning) {
392
+ debouncedEmit("OS_PROCESS_EXITED", { process: name, subcommand: subcommand ?? null });
393
+ }
394
+ wasRunning = isRunning;
395
+ }, 2e3);
396
+ intervals.push(interval);
397
+ },
398
+ osWatchPort: (port, protocol) => {
399
+ if (protocol !== "tcp") {
400
+ console.warn(`[os/watch-port] Only TCP is supported, got: ${protocol}`);
401
+ return;
402
+ }
403
+ let wasOpen = false;
404
+ const interval = setInterval(() => {
405
+ const socket = new net.Socket();
406
+ socket.setTimeout(1e3);
407
+ socket.on("connect", () => {
408
+ socket.destroy();
409
+ if (!wasOpen) {
410
+ wasOpen = true;
411
+ debouncedEmit("OS_PORT_OPENED", { port, protocol });
412
+ }
413
+ });
414
+ socket.on("error", () => {
415
+ socket.destroy();
416
+ if (wasOpen) {
417
+ wasOpen = false;
418
+ debouncedEmit("OS_PORT_CLOSED", { port, protocol });
419
+ }
420
+ });
421
+ socket.on("timeout", () => {
422
+ socket.destroy();
423
+ if (wasOpen) {
424
+ wasOpen = false;
425
+ debouncedEmit("OS_PORT_CLOSED", { port, protocol });
426
+ }
427
+ });
428
+ socket.connect(port, "127.0.0.1");
429
+ }, 3e3);
430
+ intervals.push(interval);
431
+ },
432
+ osWatchHttp: (urlPattern, method) => {
433
+ if (!httpWatchActive) {
434
+ httpWatchActive = true;
435
+ console.warn(
436
+ `[os/watch-http] HTTP interception is only supported in compiled mode. Pattern: ${urlPattern}${method ? `, method: ${method}` : ""}`
437
+ );
438
+ }
439
+ },
440
+ osWatchCron: (expression) => {
441
+ let fields;
442
+ try {
443
+ fields = parseCron(expression);
444
+ } catch (err) {
445
+ console.warn("[os/watch-cron] Invalid expression:", err);
446
+ return;
447
+ }
448
+ let lastFired = -1;
449
+ const interval = setInterval(() => {
450
+ const now = /* @__PURE__ */ new Date();
451
+ const minuteKey = now.getFullYear() * 1e8 + now.getMonth() * 1e6 + now.getDate() * 1e4 + now.getHours() * 100 + now.getMinutes();
452
+ if (minuteKey !== lastFired && cronMatches(fields, now)) {
453
+ lastFired = minuteKey;
454
+ debouncedEmit("OS_CRON_FIRE", {
455
+ expression,
456
+ firedAt: now.toISOString()
457
+ });
458
+ }
459
+ }, 1e3);
460
+ intervals.push(interval);
461
+ },
462
+ osWatchSignal: (signal) => {
463
+ const sig = signal.toUpperCase();
464
+ const handler = () => {
465
+ debouncedEmit(`OS_SIGNAL_${sig}`, { signal: sig });
466
+ };
467
+ try {
468
+ process.on(sig, handler);
469
+ signalHandlers.push({ signal: sig, handler });
470
+ } catch (err) {
471
+ console.warn(`[os/watch-signal] Cannot listen for ${sig}:`, err);
472
+ }
473
+ },
474
+ osWatchEnv: (variable) => {
475
+ let lastValue = process.env[variable];
476
+ const interval = setInterval(() => {
477
+ const current = process.env[variable];
478
+ if (current !== lastValue) {
479
+ const previous = lastValue;
480
+ lastValue = current;
481
+ debouncedEmit("OS_ENV_CHANGED", {
482
+ variable,
483
+ value: current ?? null,
484
+ previous: previous ?? null
485
+ });
486
+ }
487
+ }, 1e3);
488
+ intervals.push(interval);
489
+ },
490
+ osDebounce: (ms, eventType) => {
491
+ debounceConfig.set(eventType, ms);
492
+ }
493
+ };
494
+ function cleanup() {
495
+ for (const w of watchers) {
496
+ try {
497
+ w.close();
498
+ } catch {
499
+ }
500
+ }
501
+ watchers.length = 0;
502
+ for (const i of intervals) {
503
+ clearInterval(i);
504
+ }
505
+ intervals.length = 0;
506
+ for (const { signal, handler } of signalHandlers) {
507
+ try {
508
+ process.removeListener(signal, handler);
509
+ } catch {
510
+ }
511
+ }
512
+ signalHandlers.length = 0;
513
+ httpWatchActive = false;
514
+ for (const timer of debounceTimers.values()) {
515
+ clearTimeout(timer);
516
+ }
517
+ debounceTimers.clear();
518
+ }
519
+ return { handlers, cleanup };
520
+ }
266
521
 
267
522
  // src/OrbitalServerRuntime.ts
268
523
  var InMemoryPersistence = class {
@@ -305,6 +560,7 @@ var OrbitalServerRuntime = class {
305
560
  preprocessedCache = /* @__PURE__ */ new Map();
306
561
  entitySharingMap = {};
307
562
  eventNamespaceMap = {};
563
+ osHandlers = null;
308
564
  constructor(config = {}) {
309
565
  this.config = {
310
566
  mode: "mock",
@@ -333,6 +589,13 @@ var OrbitalServerRuntime = class {
333
589
  } else {
334
590
  this.persistence = config.persistence || new InMemoryPersistence();
335
591
  }
592
+ this.osHandlers = createOsHandlers({
593
+ emitEvent: (type, payload) => this.eventBus.emit(type, payload)
594
+ });
595
+ this.config.effectHandlers = {
596
+ ...this.osHandlers.handlers,
597
+ ...this.config.effectHandlers
598
+ };
336
599
  }
337
600
  // ==========================================================================
338
601
  // Schema Registration
@@ -760,6 +1023,10 @@ var OrbitalServerRuntime = class {
760
1023
  this.listenerCleanups = [];
761
1024
  this.orbitals.clear();
762
1025
  this.eventBus.clear();
1026
+ if (this.osHandlers) {
1027
+ this.osHandlers.cleanup();
1028
+ this.osHandlers = null;
1029
+ }
763
1030
  }
764
1031
  // ==========================================================================
765
1032
  // Event Processing