@hotmeshio/hotmesh 0.16.0 → 0.16.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.
@@ -2,3 +2,4 @@
2
2
  import { AsyncLocalStorage } from 'async_hooks';
3
3
  export declare const asyncLocalStorage: AsyncLocalStorage<Map<string, any>>;
4
4
  export declare const activityAsyncLocalStorage: AsyncLocalStorage<Map<string, any>>;
5
+ export declare const virtualAsyncLocalStorage: AsyncLocalStorage<Map<string, any>>;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.activityAsyncLocalStorage = exports.asyncLocalStorage = void 0;
3
+ exports.virtualAsyncLocalStorage = exports.activityAsyncLocalStorage = exports.asyncLocalStorage = void 0;
4
4
  const async_hooks_1 = require("async_hooks");
5
5
  exports.asyncLocalStorage = new async_hooks_1.AsyncLocalStorage();
6
6
  exports.activityAsyncLocalStorage = new async_hooks_1.AsyncLocalStorage();
7
+ exports.virtualAsyncLocalStorage = new async_hooks_1.AsyncLocalStorage();
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/hotmesh",
3
- "version": "0.16.0",
3
+ "version": "0.16.1",
4
4
  "description": "Durable Workflow",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",
@@ -30,9 +30,12 @@ class CronHandler {
30
30
  * ```
31
31
  */
32
32
  nextDelay(cronExpression) {
33
- if (!(0, utils_1.isValidCron)(cronExpression)) {
33
+ if (cronExpression == null || typeof cronExpression !== 'string') {
34
34
  return -1;
35
35
  }
36
+ if (!(0, utils_1.isValidCron)(cronExpression)) {
37
+ throw new Error(`Invalid cron expression: ${cronExpression}`);
38
+ }
36
39
  const interval = (0, cron_parser_1.parseExpression)(cronExpression, { utc: true });
37
40
  const nextDate = interval.next().toDate();
38
41
  const now = new Date();
@@ -42,7 +42,7 @@ class TaskService {
42
42
  async registerTimeHook(jobId, gId, activityId, type, inSeconds = enums_1.HMSH_FIDELITY_SECONDS, dad, transaction) {
43
43
  const fromNow = Date.now() + inSeconds * 1000;
44
44
  const fidelityMS = enums_1.HMSH_FIDELITY_SECONDS * 1000;
45
- const awakenTimeSlot = Math.floor(fromNow / fidelityMS) * fidelityMS;
45
+ const awakenTimeSlot = Math.ceil(fromNow / fidelityMS) * fidelityMS;
46
46
  await this.store.registerTimeHook(jobId, gId, activityId, type, awakenTimeSlot, dad, transaction);
47
47
  }
48
48
  /**
@@ -1,5 +1,5 @@
1
1
  import { HotMesh } from '../hotmesh';
2
- import { VirtualConnectParams, VirtualCronParams, VirtualExecParams, VirtualFlushParams, VirtualInstanceOptions, VirtualInterruptParams } from '../../types/virtual';
2
+ import { VirtualConnectParams, VirtualContext, VirtualCronParams, VirtualExecParams, VirtualFlushParams, VirtualInstanceOptions, VirtualInterruptParams } from '../../types/virtual';
3
3
  import { ProviderConfig, ProvidersConfig } from '../../types/provider';
4
4
  /**
5
5
  * Virtual creates a virtual network of functions, connecting any
@@ -185,6 +185,27 @@ declare class Virtual {
185
185
  * ```
186
186
  */
187
187
  static interrupt(params: VirtualInterruptParams): Promise<boolean>;
188
+ /**
189
+ * Returns the execution context for the current Virtual callback.
190
+ * Must be called from inside a `Virtual.cron` or `Virtual.exec`
191
+ * callback — throws if called outside that scope.
192
+ *
193
+ * @example
194
+ * ```typescript
195
+ * await Virtual.cron({
196
+ * topic: 'my.cron',
197
+ * connection,
198
+ * args: [],
199
+ * options: { id: 'daily', interval: '0 0 * * *' },
200
+ * callback: async () => {
201
+ * const ctx = Virtual.getContext();
202
+ * console.log(ctx.workflowId); // 'daily'
203
+ * console.log(ctx.attempt); // 1
204
+ * },
205
+ * });
206
+ * ```
207
+ */
208
+ static getContext(): VirtualContext;
188
209
  /**
189
210
  * Shuts down all virtual instances. Call this method
190
211
  * from the SIGTERM handler in your application.
@@ -6,8 +6,14 @@ const hotmesh_1 = require("../hotmesh");
6
6
  const enums_1 = require("../../modules/enums");
7
7
  const utils_1 = require("../../modules/utils");
8
8
  const key_1 = require("../../modules/key");
9
+ const storage_1 = require("../../modules/storage");
9
10
  const cron_1 = require("../pipe/functions/cron");
10
11
  const factory_1 = require("./schemas/factory");
12
+ const DEFAULT_RETRY_POLICY = {
13
+ maximumAttempts: 3,
14
+ backoffCoefficient: 2,
15
+ maximumInterval: 30,
16
+ };
11
17
  /**
12
18
  * Virtual creates a virtual network of functions, connecting any
13
19
  * function as an idempotent, cacheable endpoint. Call functions
@@ -173,13 +179,19 @@ class Virtual {
173
179
  {
174
180
  topic: params.topic,
175
181
  connection,
176
- retry: params.retry ?? {
177
- maximumAttempts: 3,
178
- backoffCoefficient: 2,
179
- maximumInterval: 30,
180
- },
182
+ retry: params.retry ?? DEFAULT_RETRY_POLICY,
181
183
  callback: async function (input) {
182
- const response = await params.callback.apply(this, input.data.args);
184
+ const context = new Map([
185
+ ['topic', params.topic],
186
+ ['workflowId', input.metadata.jid ?? ''],
187
+ ['workflowName', input.metadata.wfn ?? ''],
188
+ ['dimension', input.metadata.dad ?? ''],
189
+ ['attempt', input.metadata.try ?? 1],
190
+ ['guid', input.metadata.guid],
191
+ ['traceId', input.metadata.trc ?? ''],
192
+ ['spanId', input.metadata.spn ?? ''],
193
+ ]);
194
+ const response = await storage_1.virtualAsyncLocalStorage.run(context, () => params.callback.apply(this, input.data.args));
183
195
  return {
184
196
  metadata: { ...input.metadata },
185
197
  data: { response },
@@ -386,6 +398,42 @@ class Virtual {
386
398
  }
387
399
  return true;
388
400
  }
401
+ /**
402
+ * Returns the execution context for the current Virtual callback.
403
+ * Must be called from inside a `Virtual.cron` or `Virtual.exec`
404
+ * callback — throws if called outside that scope.
405
+ *
406
+ * @example
407
+ * ```typescript
408
+ * await Virtual.cron({
409
+ * topic: 'my.cron',
410
+ * connection,
411
+ * args: [],
412
+ * options: { id: 'daily', interval: '0 0 * * *' },
413
+ * callback: async () => {
414
+ * const ctx = Virtual.getContext();
415
+ * console.log(ctx.workflowId); // 'daily'
416
+ * console.log(ctx.attempt); // 1
417
+ * },
418
+ * });
419
+ * ```
420
+ */
421
+ static getContext() {
422
+ const store = storage_1.virtualAsyncLocalStorage.getStore();
423
+ if (!store) {
424
+ throw new Error('Virtual.getContext() called outside of a Virtual callback execution context');
425
+ }
426
+ return {
427
+ topic: store.get('topic'),
428
+ workflowId: store.get('workflowId'),
429
+ workflowName: store.get('workflowName'),
430
+ dimension: store.get('dimension'),
431
+ attempt: store.get('attempt'),
432
+ guid: store.get('guid'),
433
+ traceId: store.get('traceId'),
434
+ spanId: store.get('spanId'),
435
+ };
436
+ }
389
437
  /**
390
438
  * Shuts down all virtual instances. Call this method
391
439
  * from the SIGTERM handler in your application.
@@ -15,7 +15,7 @@ export { ExtensionType, JobCompletionOptions, JobData, JobsData, JobInterruptOpt
15
15
  export { MappingStatements } from './map';
16
16
  export { Pipe, PipeContext, PipeItem, PipeItems, PipeObject, ReduceObject, } from './pipe';
17
17
  export { ProviderClass, ProviderClient, ProviderConfig, ProviderTransaction, Providers, TransactionResultList, ProviderNativeClient, ProviderOptions, } from './provider';
18
- export { VirtualConnectParams, VirtualExecParams, VirtualCronParams, VirtualExecOptions, VirtualCronOptions, VirtualInterruptOptions, VirtualInterruptParams, VirtualFlushParams, } from './virtual';
18
+ export { VirtualConnectParams, VirtualContext, VirtualExecParams, VirtualCronParams, VirtualExecOptions, VirtualCronOptions, VirtualInterruptOptions, VirtualInterruptParams, VirtualFlushParams, } from './virtual';
19
19
  export { PostgresClassType, PostgresClientOptions, PostgresClientType, PostgresConsumerGroup, PostgresPendingMessage, PostgresPoolClientType, PostgresQueryConfigType, PostgresQueryResultType, PostgresStreamMessage, PostgresStreamOptions, PostgresTransaction, } from './postgres';
20
20
  export { ActivateMessage, CronMessage, JobMessage, JobMessageCallback, PingMessage, PongMessage, QuorumMessage, QuorumMessageCallback, QuorumProfile, RollCallMessage, RollCallOptions, SubscriptionCallback, SubscriptionOptions, SystemHealth, ThrottleMessage, ThrottleOptions, WorkMessage, } from './quorum';
21
21
  export { NatsAckPolicy, NatsAckPolicyExplicitType, NatsClassType, NatsClientType, NatsClientOptions, NatsConsumerConfigType, NatsJetStreamManager, NatsConnection, NatsJetStreamType, NatsConnectionOptions, NatsConsumerConfig, NatsConsumerInfo, NatsConsumerManager, NatsDeliveryInfo, NatsJetStreamOptions, NatsError, NatsErrorType, NatsJetStreamClient, NatsJsMsg, NatsMessageType, NatsMsgExpect, NatsPubAck, NatsPubAckType, NatsPublishOptions, NatsRetentionPolicy, NatsRetentionPolicyWorkqueueType, NatsSequenceInfo, NatsStorageMemoryType, NatsStorageType, NatsStreamConfig, NatsStreamInfo, NatsStreamManager, NatsStreamConfigType, NatsStreamInfoType, NatsStreamOptions, NatsStreamState, NatsTransaction, } from './nats';
@@ -208,4 +208,72 @@ interface VirtualInterruptParams {
208
208
  */
209
209
  options: VirtualInterruptOptions;
210
210
  }
211
- export { VirtualConnectParams, VirtualExecParams, VirtualCronParams, VirtualExecOptions, VirtualCronOptions, VirtualInterruptOptions, VirtualInterruptParams, VirtualFlushOptions, VirtualFlushParams, VirtualInstanceOptions, };
211
+ /**
212
+ * Execution context available inside Virtual callbacks via
213
+ * `Virtual.getContext()`. Populated automatically by the
214
+ * AsyncLocalStorage wrapper in `Virtual.connect()`.
215
+ *
216
+ * @example
217
+ * ```typescript
218
+ * await Virtual.cron({
219
+ * topic: 'billing.daily',
220
+ * connection,
221
+ * args: [],
222
+ * options: { id: 'billing-run', interval: '0 0 * * *' },
223
+ * callback: async () => {
224
+ * const ctx = Virtual.getContext();
225
+ * // ctx.workflowId === 'billing-run'
226
+ * // ctx.topic === 'billing.daily'
227
+ * // ctx.guid === unique per invocation
228
+ * },
229
+ * });
230
+ * ```
231
+ */
232
+ interface VirtualContext {
233
+ /**
234
+ * The worker topic that routed this invocation.
235
+ * Matches the `topic` passed to `Virtual.cron()` or
236
+ * `Virtual.connect()`.
237
+ */
238
+ topic: string;
239
+ /**
240
+ * Workflow / job ID. For cron callbacks this is the `id`
241
+ * from `options.id`. For `Virtual.exec` calls this is the
242
+ * auto-generated or user-supplied job identifier.
243
+ */
244
+ workflowId: string;
245
+ /**
246
+ * Internal workflow name (the graph subscription topic,
247
+ * e.g. `hmsh.cron` or `hmsh.call`).
248
+ */
249
+ workflowName: string;
250
+ /**
251
+ * Dimensional address for this execution within the
252
+ * workflow's DAG. Encodes the cycle iteration and
253
+ * parallel branch position.
254
+ */
255
+ dimension: string;
256
+ /**
257
+ * Current retry attempt (1-based). `1` on the first try,
258
+ * incremented on each retry per the `RetryPolicy`.
259
+ */
260
+ attempt: number;
261
+ /**
262
+ * Globally unique identifier for the stream message that
263
+ * triggered this callback. A new GUID is minted for every
264
+ * invocation, including retries and cycle iterations, so it
265
+ * serves as an idempotency key for exactly-once processing.
266
+ */
267
+ guid: string;
268
+ /**
269
+ * OpenTelemetry trace ID propagated from the originating
270
+ * workflow. Empty string when tracing is not configured.
271
+ */
272
+ traceId: string;
273
+ /**
274
+ * OpenTelemetry span ID propagated from the parent activity.
275
+ * Empty string when tracing is not configured.
276
+ */
277
+ spanId: string;
278
+ }
279
+ export { VirtualConnectParams, VirtualContext, VirtualExecParams, VirtualCronParams, VirtualExecOptions, VirtualCronOptions, VirtualInterruptOptions, VirtualInterruptParams, VirtualFlushOptions, VirtualFlushParams, VirtualInstanceOptions, };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/hotmesh",
3
- "version": "0.16.0",
3
+ "version": "0.16.1",
4
4
  "description": "Durable Workflow",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",