@almadar/runtime 6.19.0 → 6.21.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,3 +1,4 @@
1
+ import { parseCron, cronMinuteKey, cronMatches } from './chunk-U4PL237A.js';
1
2
  import { createLogger } from '@almadar/logger';
2
3
  import { resolveBinding, evaluate, createMinimalContext, evaluateGuard } from '@almadar/evaluator';
3
4
  export { createMinimalContext } from '@almadar/evaluator';
@@ -5,7 +6,6 @@ import { isKnownStdOperator } from '@almadar/std/registry';
5
6
  import { faker } from '@faker-js/faker';
6
7
  import { OrbitalSchemaSchema, isInlineTrait, isEntityCall, isEntityReference, parseEntityRef, parseImportedTraitRef, isPageReference, isPageReferenceString, isPageReferenceObject, parsePageRef } from '@almadar/core';
7
8
 
8
- // src/EventBus.ts
9
9
  var log = createLogger("almadar:runtime:eventbus");
10
10
  var EventBus = class {
11
11
  listeners = /* @__PURE__ */ new Map();
@@ -125,6 +125,7 @@ var EventBus = class {
125
125
 
126
126
  // src/TickScheduler.ts
127
127
  var EVERY_PASS = 0;
128
+ var CRON_CHECK_INTERVAL_MS = 1e3;
128
129
  var TickScheduler = class {
129
130
  ticks = /* @__PURE__ */ new Map();
130
131
  nextId = 1;
@@ -142,6 +143,7 @@ var TickScheduler = class {
142
143
  add(intervalMs, onDue) {
143
144
  const id = this.nextId++;
144
145
  this.ticks.set(id, {
146
+ kind: "interval",
145
147
  intervalMs: intervalMs > 0 ? intervalMs : EVERY_PASS,
146
148
  accumulatedMs: 0,
147
149
  lastTimestamp: null,
@@ -155,6 +157,33 @@ var TickScheduler = class {
155
157
  }
156
158
  };
157
159
  }
160
+ /**
161
+ * Register a cron-scheduled tick (`ticks { ... every "0 9 * * *" }`).
162
+ * Fires at most once per matching calendar minute — the same dedup shape
163
+ * `os/watch-cron` uses. Throws if `expression` isn't a valid 5-field cron
164
+ * string; callers on a path the compiler has already validated (e.g.
165
+ * generated code) won't hit this, but a raw runtime value might.
166
+ */
167
+ addCron(expression, onDue) {
168
+ const fields = parseCron(expression);
169
+ const id = this.nextId++;
170
+ this.ticks.set(id, {
171
+ kind: "cron",
172
+ fields,
173
+ accumulatedMs: CRON_CHECK_INTERVAL_MS,
174
+ // check on the first pass, not after a full second
175
+ lastTimestamp: null,
176
+ lastFiredMinuteKey: -1,
177
+ onDue
178
+ });
179
+ this.ensureRunning();
180
+ return {
181
+ stop: () => {
182
+ this.ticks.delete(id);
183
+ this.maybeStop();
184
+ }
185
+ };
186
+ }
158
187
  /** Stop every tick and the shared loop. */
159
188
  stopAll() {
160
189
  this.ticks.clear();
@@ -188,6 +217,10 @@ var TickScheduler = class {
188
217
  }
189
218
  advance(timestamp) {
190
219
  for (const tick of this.ticks.values()) {
220
+ if (tick.kind === "cron") {
221
+ this.advanceCron(tick, timestamp);
222
+ continue;
223
+ }
191
224
  if (tick.intervalMs <= EVERY_PASS) {
192
225
  tick.onDue();
193
226
  continue;
@@ -204,10 +237,54 @@ var TickScheduler = class {
204
237
  }
205
238
  }
206
239
  }
240
+ /** Cron ticks check calendar-match at most once a second, deduped per matching minute. */
241
+ advanceCron(tick, timestamp) {
242
+ if (tick.lastTimestamp === null) {
243
+ tick.lastTimestamp = timestamp;
244
+ return;
245
+ }
246
+ tick.accumulatedMs += timestamp - tick.lastTimestamp;
247
+ tick.lastTimestamp = timestamp;
248
+ if (tick.accumulatedMs < CRON_CHECK_INTERVAL_MS) return;
249
+ tick.accumulatedMs = 0;
250
+ const now = /* @__PURE__ */ new Date();
251
+ const minuteKey = cronMinuteKey(now);
252
+ if (minuteKey !== tick.lastFiredMinuteKey && cronMatches(tick.fields, now)) {
253
+ tick.lastFiredMinuteKey = minuteKey;
254
+ tick.onDue();
255
+ }
256
+ }
207
257
  };
208
258
  function createTickScheduler() {
209
259
  return new TickScheduler();
210
260
  }
261
+
262
+ // src/duration.ts
263
+ function parseDurationString(interval) {
264
+ const match = interval.match(/^(\d+)(ms|s|m|h)?$/);
265
+ if (!match) {
266
+ throw new Error(
267
+ `Invalid duration '${interval}': expected a shape like '5ms'/'5s'/'1m'/'1h'`
268
+ );
269
+ }
270
+ const value = parseInt(match[1], 10);
271
+ const unit = match[2] || "ms";
272
+ switch (unit) {
273
+ case "ms":
274
+ return value;
275
+ case "s":
276
+ return value * 1e3;
277
+ case "m":
278
+ return value * 60 * 1e3;
279
+ case "h":
280
+ return value * 60 * 60 * 1e3;
281
+ default:
282
+ return value;
283
+ }
284
+ }
285
+ function isValidDurationString(interval) {
286
+ return /^(\d+)(ms|s|m|h)?$/.test(interval);
287
+ }
211
288
  var bindLog = createLogger("almadar:runtime:bindings");
212
289
  var renderLog = createLogger("almadar:runtime:render-ui");
213
290
  var CLIENT_ONLY_BINDING_ROOTS = /* @__PURE__ */ new Set(["trait"]);
@@ -3808,6 +3885,6 @@ var InMemoryPersistence = class {
3808
3885
  }
3809
3886
  };
3810
3887
 
3811
- export { EffectExecutor, EventBus, HANDLER_MANIFEST, InMemoryPersistence, MockPersistenceAdapter, StateMachineManager, TickScheduler, buildEmitsFromTraits, collectDeclaredConfigDefaults, collectDeclaredEntityDefaults, containsBindings, createContextFromBindings, createInitialTraitState, createMockPersistence, createTestExecutor, createTickScheduler, createUnifiedLoader, extractBindings, findInitialState, findTransition, formatPayloadValidationError, getIsolatedCollectionName, getNamespacedEvent, interpolateProps, interpolateValue, isBrowser, isElectron, isNamespacedEvent, isNode, normalizeEventKey, parseNamespacedEvent, preprocessSchema, processEvent, validateEventPayload, validatePayloadShapes };
3812
- //# sourceMappingURL=chunk-JZ4IDIUU.js.map
3813
- //# sourceMappingURL=chunk-JZ4IDIUU.js.map
3888
+ export { EffectExecutor, EventBus, HANDLER_MANIFEST, InMemoryPersistence, MockPersistenceAdapter, StateMachineManager, TickScheduler, buildEmitsFromTraits, collectDeclaredConfigDefaults, collectDeclaredEntityDefaults, containsBindings, createContextFromBindings, createInitialTraitState, createMockPersistence, createTestExecutor, createTickScheduler, createUnifiedLoader, extractBindings, findInitialState, findTransition, formatPayloadValidationError, getIsolatedCollectionName, getNamespacedEvent, interpolateProps, interpolateValue, isBrowser, isElectron, isNamespacedEvent, isNode, isValidDurationString, normalizeEventKey, parseDurationString, parseNamespacedEvent, preprocessSchema, processEvent, validateEventPayload, validatePayloadShapes };
3889
+ //# sourceMappingURL=chunk-53VRCHPK.js.map
3890
+ //# sourceMappingURL=chunk-53VRCHPK.js.map