@hotmeshio/hotmesh 0.5.2 → 0.5.4

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.
Files changed (38) hide show
  1. package/README.md +93 -175
  2. package/build/index.d.ts +1 -3
  3. package/build/index.js +1 -5
  4. package/build/modules/enums.d.ts +4 -0
  5. package/build/modules/enums.js +5 -1
  6. package/build/modules/utils.d.ts +1 -9
  7. package/build/modules/utils.js +0 -6
  8. package/build/package.json +3 -4
  9. package/build/services/connector/factory.d.ts +2 -2
  10. package/build/services/connector/factory.js +11 -8
  11. package/build/services/connector/providers/postgres.d.ts +47 -0
  12. package/build/services/connector/providers/postgres.js +107 -0
  13. package/build/services/hotmesh/index.d.ts +8 -0
  14. package/build/services/hotmesh/index.js +27 -0
  15. package/build/services/memflow/client.d.ts +1 -1
  16. package/build/services/memflow/client.js +8 -6
  17. package/build/services/memflow/worker.js +3 -0
  18. package/build/services/pipe/functions/cron.js +1 -1
  19. package/build/services/store/providers/postgres/kvtables.js +19 -6
  20. package/build/services/store/providers/postgres/postgres.js +13 -2
  21. package/build/services/stream/providers/postgres/postgres.d.ts +6 -3
  22. package/build/services/stream/providers/postgres/postgres.js +169 -59
  23. package/build/services/sub/providers/postgres/postgres.d.ts +9 -0
  24. package/build/services/sub/providers/postgres/postgres.js +109 -18
  25. package/build/services/worker/index.js +4 -0
  26. package/build/types/hotmesh.d.ts +19 -5
  27. package/build/types/index.d.ts +0 -2
  28. package/env.example +11 -0
  29. package/index.ts +0 -4
  30. package/package.json +3 -4
  31. package/build/services/meshdata/index.d.ts +0 -795
  32. package/build/services/meshdata/index.js +0 -1235
  33. package/build/services/meshos/index.d.ts +0 -293
  34. package/build/services/meshos/index.js +0 -547
  35. package/build/types/manifest.d.ts +0 -52
  36. package/build/types/manifest.js +0 -2
  37. package/build/types/meshdata.d.ts +0 -252
  38. package/build/types/meshdata.js +0 -2
@@ -1,1235 +0,0 @@
1
- "use strict";
2
- var _a;
3
- Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.MeshData = void 0;
5
- const utils_1 = require("../../modules/utils");
6
- const memflow_1 = require("../memflow");
7
- const hotmesh_1 = require("../hotmesh");
8
- const hotmesh_2 = require("../../types/hotmesh");
9
- const enums_1 = require("../../modules/enums");
10
- /**
11
- * The `MeshData` service extends the `MemFlow` service.
12
- * It serves to unify both data record and
13
- * transactional workflow principles into a single
14
- * *Operational Data Layer*. Deployments with a 'search'
15
- * provider configured (e.g.,Redis FT.SEARCH) can deliver
16
- * both OLTP (transactions) and OLAP (analytics)
17
- * with no additional infrastructure.
18
- *
19
- * The following example depicts the full end-to-end
20
- * lifecycle of a `MeshData` app, including the
21
- * connection of a worker function, the execution of
22
- * a remote function, the retrieval of data,
23
- * the creation of a search index, and the execution
24
- * of a full-text search query.
25
- *
26
- * @example
27
- * ```typescript
28
- * import { MeshData, Types } from '@hotmeshio/hotmesh';
29
- * import * as Redis from 'redis';
30
- *
31
- * //1) Define a search schema
32
- * const schema = {
33
- * schema: {
34
- * id: { type: 'TAG', sortable: true },
35
- * plan: { type: 'TAG', sortable: true },
36
- * active: { type: 'TEXT', sortable: false },
37
- * },
38
- * index: 'user',
39
- * prefix: ['user'], //index items with keys starting with 'user'
40
- * } as unknown as Types.WorkflowSearchOptions;
41
- *
42
- * //2) Initialize MeshData and Redis
43
- * const meshData = new MeshData(
44
- * {
45
- * class: Redis,
46
- * options: { url: 'redis://:key_admin@redis:6379' },
47
- * },
48
- * schema,
49
- * );
50
- *
51
- * //3) Connect a 'user' worker function
52
- * await meshData.connect({
53
- * entity: 'user',
54
- * target: async function(userID: string): Promise<string> {
55
- * //used the `search` extension to add searchable data
56
- * const search = await MeshData.workflow.search();
57
- * await search.set('active', 'yes');
58
- * return `Welcome, ${userID}.`;
59
- * },
60
- * options: { namespace: 'meshdata' },
61
- * });
62
- *
63
- * const userID = 'someTestUser123';
64
- *
65
- * //4) Call the 'user' worker function; include search data
66
- * const response = await meshData.exec({
67
- * entity: 'user',
68
- * args: [userID],
69
- * options: {
70
- * ttl: 'infinity',
71
- * id: userID,
72
- * search: {
73
- * data: { id: userID, plan: 'pro' }
74
- * },
75
- * namespace: 'meshdata',
76
- * },
77
- * });
78
- *
79
- * //5) Read data by field name
80
- * const data = await meshData.get(
81
- * 'user',
82
- * userID,
83
- * {
84
- * fields: ['plan', 'id', 'active'],
85
- * namespace: 'meshdata'
86
- * },
87
- * );
88
- *
89
- * //6) Create a search index
90
- * await meshData.createSearchIndex('user', { namespace: 'meshdata' }, schema);
91
- *
92
- * //7) Perform Full Text Search on the indexed dataset
93
- * const results = await meshData.findWhere('user', {
94
- * query: [{ field: 'id', is: '=', value: userID }],
95
- * limit: { start: 0, size: 100 },
96
- * return: ['plan', 'id', 'active']
97
- * });
98
- *
99
- * //8) Shutdown MeshData
100
- * await MeshData.shutdown();
101
- * ```
102
- *
103
- */
104
- class MeshData {
105
- /**
106
- * Instances a new `MeshData` service.
107
- * @param {ProviderConfig|ProvidersConfig} connection - the connection class and options
108
- * @param {WorkflowSearchOptions} search - the search options for JSON-based configuration of the backend search module (e.g., Redis FT.Search)
109
- * @example
110
- * // Example 1) Instantiate MeshData with `ioredis`
111
- * import Redis from 'ioredis';
112
- *
113
- * const meshData = new MeshData({
114
- * class: Redis,
115
- * options: {
116
- * host: 'localhost',
117
- * port: 6379,
118
- * password: 'shhh123',
119
- * db: 0,
120
- * }});
121
- *
122
- * // Example 2) Instantiate MeshData with `redis`
123
- * import * as Redis from 'redis';
124
- *
125
- * const meshData = new MeshData({
126
- * class: Redis,
127
- * options: {
128
- * url: 'redis://:shhh123@localhost:6379'
129
- * }});
130
- *
131
- * // Instantiate MeshData with `postgres`
132
- * //...
133
- */
134
- constructor(connection, search) {
135
- /**
136
- * unused; allows wrapped functions to be stringified
137
- * so their source can be shared on the network for
138
- * remote analysis. this is useful for targeting which
139
- * version of a function is being executed.
140
- * @private
141
- */
142
- this.connectionSignatures = {};
143
- /**
144
- * Cached local instances (map) of HotMesh organized by namespace
145
- * @private
146
- */
147
- this.instances = new Map();
148
- /**
149
- * Exposes the the service mesh control plane through the
150
- * mesh 'events' (pub/sub) system. This is useful for
151
- * monitoring and managing the operational data layer.
152
- */
153
- this.mesh = {
154
- /**
155
- * subscribes to the mesh control plane
156
- * @param {QuorumMessageCallback} callback - the callback function
157
- * @param {SubscriptionOptions} options - connection options
158
- * @returns {Promise<void>}
159
- */
160
- sub: async (callback, options = {}) => {
161
- const hotMesh = await this.getHotMesh(options.namespace || 'memflow');
162
- const callbackWrapper = (topic, message) => {
163
- if (message.type === 'pong' && !message.originator) {
164
- if (message.profile?.worker_topic) {
165
- const [entity] = message.profile.worker_topic.split('-');
166
- if (entity) {
167
- message.profile.entity = message.entity = entity;
168
- if (this.connectionSignatures[entity]) {
169
- message.profile.signature = this.connectionSignatures[entity];
170
- }
171
- }
172
- }
173
- }
174
- else if (message?.topic) {
175
- const [entity] = message.topic.split('-');
176
- if (entity) {
177
- message.entity = entity;
178
- }
179
- }
180
- callback(topic, message);
181
- };
182
- await hotMesh.quorum?.sub(callbackWrapper);
183
- },
184
- /**
185
- * publishes a message to the mesh control plane
186
- * @param {QuorumMessage} message - the message payload
187
- * @param {SubscriptionOptions} options - connection options
188
- * @returns {Promise<void>}
189
- */
190
- pub: async (message, options = {}) => {
191
- const hotMesh = await this.getHotMesh(options.namespace || 'memflow');
192
- await hotMesh.quorum?.pub(message);
193
- },
194
- /**
195
- * unsubscribes from the mesh control plane
196
- * @param {QuorumMessageCallback} callback - the callback function
197
- * @param {SubscriptionOptions} options - connection options
198
- * @returns {Promise<void>}
199
- */
200
- unsub: async (callback, options = {}) => {
201
- const hotMesh = await this.getHotMesh(options.namespace || 'memflow');
202
- await hotMesh.quorum?.unsub(callback);
203
- },
204
- };
205
- this.connection = connection;
206
- if (search) {
207
- this.search = search;
208
- }
209
- }
210
- /**
211
- * @private
212
- */
213
- validate(entity) {
214
- if (entity.includes(':') || entity.includes('$') || entity.includes(' ')) {
215
- throw "Invalid string [':','$',' ' not allowed]";
216
- }
217
- }
218
- /**
219
- * @private
220
- */
221
- async getConnection() {
222
- return this.connection;
223
- }
224
- /**
225
- * Return a MemFlow client
226
- * @private
227
- */
228
- getClient() {
229
- return new memflow_1.MemFlow.Client({
230
- connection: this.connection,
231
- });
232
- }
233
- /**
234
- * @private
235
- */
236
- safeKey(key) {
237
- return `_${key}`;
238
- }
239
- /**
240
- * @private
241
- * todo: move to `utils` (might already be there);
242
- * also might be better as specialized provider utils
243
- */
244
- arrayToHash(input) {
245
- const max = input.length;
246
- const hashes = [];
247
- for (let i = 1; i < max; i++) {
248
- const fields = input[i];
249
- if (Array.isArray(fields)) {
250
- const hash = {};
251
- const hashId = input[i - 1];
252
- for (let j = 0; j < fields.length; j += 2) {
253
- const fieldKey = fields[j].replace(/^_/, '');
254
- const fieldValue = fields[j + 1];
255
- hash[fieldKey] = fieldValue;
256
- }
257
- if (typeof hashId === 'string') {
258
- hash['$'] = hashId;
259
- }
260
- hashes.push(hash);
261
- }
262
- }
263
- return hashes;
264
- }
265
- /**
266
- * serialize using the HotMesh `toString` format
267
- * @private
268
- */
269
- toString(value) {
270
- switch (typeof value) {
271
- case 'string':
272
- break;
273
- case 'boolean':
274
- value = value ? '/t' : '/f';
275
- break;
276
- case 'number':
277
- value = '/d' + value.toString();
278
- break;
279
- case 'undefined':
280
- return undefined;
281
- case 'object':
282
- if (value === null) {
283
- value = '/n';
284
- }
285
- else {
286
- value = '/s' + JSON.stringify(value);
287
- }
288
- break;
289
- }
290
- return value;
291
- }
292
- /**
293
- * returns an entity-namespaced guid
294
- * @param {string|null} entity - entity namespace
295
- * @param {string} [id] - workflow id (allowed to be namespaced)
296
- * @returns {string}
297
- * @private
298
- */
299
- static mintGuid(entity, id) {
300
- if (!id && !entity) {
301
- throw 'Invalid arguments [entity and id are both null]';
302
- }
303
- else if (!id) {
304
- id = hotmesh_1.HotMesh.guid();
305
- }
306
- else if (entity) {
307
- entity = `${entity}-`;
308
- }
309
- else {
310
- entity = '';
311
- }
312
- return `${entity}${id}`;
313
- }
314
- /**
315
- * Returns a HotMesh client
316
- * @param {string} [namespace='memflow'] - the namespace for the client
317
- * @returns {Promise<HotMesh>}
318
- */
319
- async getHotMesh(namespace = 'memflow') {
320
- //try to reuse an existing client
321
- let hotMesh = await this.instances.get(namespace);
322
- if (!hotMesh) {
323
- //expanded config always takes precedence over concise config
324
- hotMesh = hotmesh_1.HotMesh.init({
325
- appId: namespace,
326
- engine: { connection: this.connection },
327
- });
328
- this.instances.set(namespace, hotMesh);
329
- hotMesh = await hotMesh;
330
- this.instances.set(namespace, hotMesh);
331
- }
332
- return hotMesh;
333
- }
334
- /**
335
- * Returns the HASH key given an `entity` name and workflow/job. The
336
- * item identified by this key is a HASH record with multidimensional process
337
- * data interleaved with the function state data.
338
- * @param {string} entity - the entity name (e.g, 'user', 'order', 'product')
339
- * @param {string} workflowId - the workflow/job id
340
- * @param {string} [namespace='memflow'] - the namespace for the client
341
- * @returns {Promise<string>}
342
- * @example
343
- * // mint a key
344
- * const key = await meshData.mintKey('greeting', 'jsmith123');
345
- *
346
- * // returns 'hmsh:memflow:j:greeting-jsmith123'
347
- */
348
- async mintKey(entity, workflowId, namespace) {
349
- const handle = await this.getClient().workflow.getHandle(entity, entity, workflowId, namespace);
350
- const store = handle.hotMesh.engine?.store;
351
- return store?.mintKey(hotmesh_2.KeyType.JOB_STATE, {
352
- jobId: workflowId,
353
- appId: handle.hotMesh.engine?.appId,
354
- });
355
- }
356
- /**
357
- * Connects a function to the operational data layer.
358
- *
359
- * @template T The expected return type of the target function.
360
- *
361
- * @param {object} connection - The options for connecting a function.
362
- * @param {string} connection.entity - The global entity identifier for the function (e.g, 'user', 'order', 'product').
363
- * @param {(...args: any[]) => T} connection.target - Function to connect, returns type T.
364
- * @param {ConnectOptions} connection.options={} - Extended connection options (e.g., ttl, taskQueue). A
365
- * ttl of 'infinity' will cache the function indefinitely.
366
- *
367
- * @returns {Promise<boolean>} True if connection is successfully established.
368
- *
369
- * @example
370
- * // Instantiate MeshData with Redis configuration.
371
- * const meshData = new MeshData({
372
- * class: Redis,
373
- * options: { host: 'localhost', port: 6379 }
374
- * });
375
- *
376
- * // Define and connect a function with the 'greeting' entity.
377
- * // The function will be cached indefinitely (infinite TTL).
378
- * await meshData.connect({
379
- * entity: 'greeting',
380
- * target: (email, user) => `Hello, ${user.first}.`,
381
- * options: { ttl: 'infinity' }
382
- * });
383
- */
384
- async connect({ entity, target, options = {}, }) {
385
- this.validate(entity);
386
- this.connectionSignatures[entity] = target.toString();
387
- const targetFunction = {
388
- [entity]: async (...args) => {
389
- const { callOptions } = this.bindCallOptions(args);
390
- const result = (await target.apply(target, args));
391
- //increase status by 1, set 'done' flag and emit the 'job done' signal
392
- await this.pauseForTTL(result, callOptions);
393
- return result;
394
- },
395
- };
396
- await memflow_1.MemFlow.Worker.create({
397
- namespace: options.namespace,
398
- options: options.options,
399
- connection: await this.getConnection(),
400
- taskQueue: options.taskQueue ?? entity,
401
- workflow: targetFunction,
402
- search: options.search,
403
- });
404
- return true;
405
- }
406
- /**
407
- * During remote execution, an argument is injected (the last argument)
408
- * this is then used by the 'connect' function to determine if the call
409
- * is a hook or a exec call. If it is an exec, the connected function has
410
- * precedence and can say that all calls are cached indefinitely.
411
- *
412
- * @param {any[]} args
413
- * @param {StringAnyType} callOptions
414
- * @returns {StringAnyType}
415
- * @private
416
- */
417
- bindCallOptions(args, callOptions = {}) {
418
- if (args.length) {
419
- const lastArg = args[args.length - 1];
420
- if (lastArg instanceof Object && lastArg?.$type === 'exec') {
421
- //override the caller and force indefinite caching
422
- callOptions = args.pop();
423
- }
424
- else if (lastArg instanceof Object && lastArg.$type === 'hook') {
425
- callOptions = args.pop();
426
- //hooks may not affect `ttl` (it is set at invocation)
427
- delete callOptions.ttl;
428
- }
429
- }
430
- return { callOptions };
431
- }
432
- /**
433
- * Sleeps/WaitsForSignal to keep the function open
434
- * and remain part of the operational data layer
435
- *
436
- * @template T The expected return type of the remote function
437
- *
438
- * @param {string} result - the result to emit before going to sleep
439
- * @param {CallOptions} options - call options
440
- * @private
441
- */
442
- async pauseForTTL(result, options) {
443
- if (options?.ttl && options.$type === 'exec') {
444
- //exit early if the outer function wrapper has already run
445
- const { counter, replay, workflowDimension, workflowId } = MeshData.workflow.getContext();
446
- const prefix = options.ttl === 'infinity' ? 'wait' : 'sleep';
447
- if (`-${prefix}${workflowDimension}-${counter + 1}-` in replay) {
448
- return;
449
- }
450
- //manually set job state since leaving open/running
451
- await new Promise((resolve) => setImmediate(resolve));
452
- options.$guid = options.$guid ?? workflowId;
453
- const hotMesh = await MeshData.workflow.getHotMesh();
454
- const jobKey = hotMesh.engine?.store?.mintKey(hotmesh_2.KeyType.JOB_STATE, {
455
- jobId: options.$guid,
456
- appId: hotMesh.engine?.appId,
457
- });
458
- const jobResponse = { aAa: '/t', aBa: this.toString(result) };
459
- await hotMesh.engine?.search.setFields(jobKey, jobResponse);
460
- }
461
- }
462
- /**
463
- * Publishes the job result, because pausing the job (in support of
464
- * the 'ttl' option) interrupts the response.
465
- *
466
- * @template T The expected return type of the remote function
467
- *
468
- * @param {string} result - the result to emit before going to sleep
469
- * @param {HotMesh} hotMesh - call options
470
- * @param {CallOptions} options - call options
471
- *
472
- * @returns {Promise<void>}
473
- * @private
474
- */
475
- async publishDone(result, hotMesh, options) {
476
- await hotMesh.engine?.subscribe?.publish(hotmesh_2.KeyType.QUORUM, {
477
- type: 'job',
478
- topic: `${hotMesh.engine.appId}.executed`,
479
- job: {
480
- metadata: {
481
- tpc: `${hotMesh.engine.appId}.execute`,
482
- app: hotMesh.engine.appId,
483
- vrs: '1',
484
- jid: options.$guid,
485
- aid: 't1',
486
- ts: '0',
487
- js: 0,
488
- },
489
- data: {
490
- done: true,
491
- response: result,
492
- workflowId: options.$guid, //aCa
493
- },
494
- },
495
- }, hotMesh.engine.appId, `${hotMesh.engine.appId}.executed.${options.$guid}`);
496
- }
497
- /**
498
- * Flushes a function with a `ttl` of 'infinity'. These entities were
499
- * created by a connect method that was configured with a
500
- * `ttl` of 'infinity'. It can take several seconds for the function
501
- * to be removed from the cache as it might be actively orchestrating
502
- * sub-workflows.
503
- *
504
- * @param {string} entity - the entity name (e.g, 'user', 'order', 'product')
505
- * @param {string} id - The workflow/job id
506
- * @param {string} [namespace='memflow'] - the namespace for the client
507
- *
508
- * @example
509
- * // Flush a function
510
- * await meshData.flush('greeting', 'jsmith123');
511
- */
512
- async flush(entity, id, namespace) {
513
- const workflowId = MeshData.mintGuid(entity, id);
514
- //resolve the system signal (this forces the main wrapper function to end)
515
- await this.getClient().workflow.signal(`flush-${workflowId}`, {}, namespace);
516
- await (0, utils_1.sleepFor)(1000);
517
- //other activities may still be running; call `interrupt` to stop all threads
518
- await this.interrupt(entity, id, {
519
- descend: true,
520
- suppress: true,
521
- expire: 1,
522
- }, namespace);
523
- }
524
- /**
525
- * Interrupts a job by its entity and id. It is best not to call this
526
- * method directly for entries with a ttl of `infinity` (call `flush` instead).
527
- * For those entities that are cached for a specified duration (e.g., '15 minutes'),
528
- * this method will interrupt the job and start the cascaded cleanup/expire/delete.
529
- * As jobs are asynchronous, there is no way to stop descendant flows immediately.
530
- * Use an `expire` option to keep the interrupted job in the cache for a specified
531
- * duration before it is fully removed.
532
- *
533
- * @param {string} entity - the entity name (e.g, 'user', 'order', 'product')
534
- * @param {string} id - The workflow/job id
535
- * @param {JobInterruptOptions} [options={}] - call options
536
- * @param {string} [namespace='memflow'] - the namespace for the client
537
- *
538
- * @example
539
- * // Interrupt a function
540
- * await meshData.interrupt('greeting', 'jsmith123');
541
- */
542
- async interrupt(entity, id, options = {}, namespace) {
543
- const workflowId = MeshData.mintGuid(entity, id);
544
- try {
545
- const handle = await this.getClient().workflow.getHandle(entity, entity, workflowId, namespace);
546
- const hotMesh = handle.hotMesh;
547
- await hotMesh.interrupt(`${hotMesh.appId}.execute`, workflowId, options);
548
- }
549
- catch (e) {
550
- //no-op; interrup throws an error
551
- }
552
- }
553
- /**
554
- * Signals a Hook Function or Main Function to awaken that
555
- * is paused and registered to awaken upon receiving the signal
556
- * matching @guid.
557
- *
558
- * @param {string} guid - The global identifier for the signal
559
- * @param {StringAnyType} payload - The payload to send with the signal
560
- * @param {string} [namespace='memflow'] - the namespace for the client
561
- * @returns {Promise<string>} - the signal id
562
- * @example
563
- * // Signal a function with a payload
564
- * await meshData.signal('signal123', { message: 'hi!' });
565
- *
566
- * // returns '123456732345-0' (stream message receipt)
567
- */
568
- async signal(guid, payload, namespace) {
569
- return await this.getClient().workflow.signal(guid, payload, namespace);
570
- }
571
- /**
572
- * Sends a signal to the backend Service Mesh (workers and engines)
573
- * to announce their presence, including message counts, target
574
- * functions, topics, etc. This is useful for establishing
575
- * the network profile and overall message throughput
576
- * of the operational data layer as a unified quorum.
577
- * @param {RollCallOptions} options
578
- * @returns {Promise<QuorumProfile[]>}
579
- */
580
- async rollCall(options = {}) {
581
- return (await this.getHotMesh(options.namespace || 'memflow')).rollCall(options.delay || 1000);
582
- }
583
- /**
584
- * Throttles a worker or engine in the backend Service Mesh, using either
585
- * a 'guid' to target a specific worker or engine, or a 'topic' to target
586
- * a group of worker(s) connected to that topic. The throttle value is
587
- * specified in milliseconds and will cause the target(s) to delay consuming
588
- * the next message by this amount. By default, the value is set to `0`.
589
- * @param {ThrottleOptions} options
590
- * @returns {Promise<boolean>}
591
- *
592
- * @example
593
- * // Throttle a worker or engine
594
- * await meshData.throttle({ guid: '1234567890', throttle: 10_000 });
595
- */
596
- async throttle(options) {
597
- return (await this.getHotMesh(options.namespace || 'memflow')).throttle(options);
598
- }
599
- /**
600
- * Similar to `exec`, except it augments the workflow state without creating a new job.
601
- *
602
- * @param {object} input - The input parameters for hooking a function.
603
- * @param {string} input.entity - The target entity name (e.g., 'user', 'order', 'product').
604
- * @param {string} input.id - The target execution/workflow/job id.
605
- * @param {string} input.hookEntity - The hook entity name (e.g, 'user.notification').
606
- * @param {any[]} input.hookArgs - The arguments for the hook function; must be JSON serializable.
607
- * @param {HookOptions} input.options={} - Extended hook options (taskQueue, namespace, etc).
608
- * @returns {Promise<string>} The signal id.
609
- *
610
- * @example
611
- * // Hook a function
612
- * const signalId = await meshData.hook({
613
- * entity: 'greeting',
614
- * id: 'jsmith123',
615
- * hookEntity: 'greeting.newsletter',
616
- * hookArgs: ['xxxx@xxxxx'],
617
- * options: {}
618
- * });
619
- */
620
- async hook({ entity, id, hookEntity, hookArgs, options = {}, }) {
621
- const workflowId = MeshData.mintGuid(entity, id);
622
- this.validate(workflowId);
623
- const args = [
624
- ...hookArgs,
625
- { ...options, $guid: workflowId, $type: 'hook' },
626
- ];
627
- return await this.getClient().workflow.hook({
628
- namespace: options.namespace,
629
- args,
630
- taskQueue: options.taskQueue ?? hookEntity,
631
- workflowName: hookEntity,
632
- workflowId: options.workflowId ?? workflowId,
633
- config: options.config ?? undefined,
634
- });
635
- }
636
- /**
637
- * Executes a remote function by its global entity identifier with specified arguments.
638
- * If options.ttl is infinity, the function will be cached indefinitely and can only be
639
- * removed by calling `flush`. During this time, the function will remain active and
640
- * its state can be augmented by calling `set`, `incr`, `del`, etc OR by calling a
641
- * transactional 'hook' function.
642
- *
643
- * @template T The expected return type of the remote function.
644
- *
645
- * @param {object} input - The execution parameters.
646
- * @param {string} input.entity - The function entity name (e.g., 'user', 'order', 'user.bill').
647
- * @param {any[]} input.args - The arguments for the remote function.
648
- * @param {CallOptions} input.options={} - Extended configuration options for execution (e.g, taskQueue).
649
- *
650
- * @returns {Promise<T>} A promise that resolves with the result of the remote function execution. If
651
- * the input options include `await: false`, the promise will resolve with the
652
- * workflow ID (string) instead of the result. Make sure to pass string as the
653
- * return type if you are using `await: false`.
654
- *
655
- * @example
656
- * // Invoke a remote function with arguments and options
657
- * const response = await meshData.exec({
658
- * entity: 'greeting',
659
- * args: ['jsmith@hotmesh', { first: 'Jan' }],
660
- * options: { ttl: '15 minutes', id: 'jsmith123' }
661
- * });
662
- */
663
- async exec({ entity, args = [], options = {} }) {
664
- const workflowId = MeshData.mintGuid(options.prefix ?? entity, options.id);
665
- this.validate(workflowId);
666
- const client = this.getClient();
667
- try {
668
- //check the cache
669
- const handle = await client.workflow.getHandle(entity, entity, workflowId, options.namespace);
670
- const state = await handle.hotMesh.getState(`${handle.hotMesh.appId}.execute`, handle.workflowId);
671
- if (state?.data?.done) {
672
- return state.data.response;
673
- }
674
- //202 `pending`; await the result
675
- return (await handle.result());
676
- }
677
- catch (e) {
678
- //create, since not found; then await the result
679
- const optionsClone = { ...options };
680
- let seconds;
681
- if (optionsClone.ttl) {
682
- //setting ttl requires the workflow to remain open
683
- if (optionsClone.signalIn !== false) {
684
- optionsClone.signalIn = true; //explicit 'true' forces open
685
- }
686
- if (optionsClone.ttl === 'infinity') {
687
- delete optionsClone.ttl;
688
- seconds = enums_1.MAX_DELAY;
689
- }
690
- else {
691
- seconds = (0, utils_1.s)(optionsClone.ttl);
692
- }
693
- }
694
- delete optionsClone.search;
695
- delete optionsClone.config;
696
- const handle = await client.workflow.start({
697
- args: [...args, { ...optionsClone, $guid: workflowId, $type: 'exec' }],
698
- taskQueue: options.taskQueue ?? entity,
699
- workflowName: entity,
700
- workflowId: options.workflowId ?? workflowId,
701
- config: options.config ?? undefined,
702
- search: options.search,
703
- workflowTrace: options.workflowTrace,
704
- workflowSpan: options.workflowSpan,
705
- namespace: options.namespace,
706
- await: options.await,
707
- marker: options.marker,
708
- pending: options.pending,
709
- expire: seconds ?? options.expire,
710
- //`persistent` flag keeps the job alive and accepting signals
711
- persistent: options.signalIn == false ? undefined : seconds && true,
712
- signalIn: optionsClone.signalIn,
713
- });
714
- if (options.await === false) {
715
- return handle.workflowId;
716
- }
717
- return (await handle.result());
718
- }
719
- }
720
- /**
721
- * Retrieves the job profile for the function execution, including metadata such as
722
- * execution status and result.
723
- *
724
- * @param {string} entity - the entity name (e.g, 'user', 'order', 'product')
725
- * @param {string} id - identifier for the job
726
- * @param {CallOptions} [options={}] - Configuration options for the execution,
727
- * including custom IDs, time-to-live (TTL) settings, etc.
728
- * Defaults to an empty object if not provided.
729
- *
730
- * @returns {Promise<JobOutput>} A promise that resolves with the job's output, which
731
- * includes metadata about the job's execution status. The
732
- * structure of `JobOutput` should contain all relevant
733
- * information such as execution result, status, and any
734
- * error messages if the job failed.
735
- *
736
- * @example
737
- * // Retrieve information about a remote function's execution by job ID
738
- * const jobInfoById = await meshData.info('greeting', 'job-12345');
739
- *
740
- * // Response: JobOutput
741
- * {
742
- * metadata: {
743
- * tpc: 'memflow.execute',
744
- * app: 'memflow',
745
- * vrs: '1',
746
- * jid: 'greeting-jsmith123',
747
- * aid: 't1',
748
- * ts: '0',
749
- * jc: '20240208014803.980',
750
- * ju: '20240208065017.762',
751
- * js: 0
752
- * },
753
- * data: {
754
- * done: true,
755
- * response: 'Hello, Jan. Your email is [jsmith@hotmesh.com].',
756
- * workflowId: 'greeting-jsmith123'
757
- * }
758
- * }
759
- */
760
- async info(entity, id, options = {}) {
761
- const workflowId = MeshData.mintGuid(options.prefix ?? entity, id);
762
- this.validate(workflowId);
763
- const handle = await this.getClient().workflow.getHandle(options.taskQueue ?? entity, entity, workflowId, options.namespace);
764
- return await handle.hotMesh.getState(`${handle.hotMesh.appId}.execute`, handle.workflowId);
765
- }
766
- /**
767
- * Exports the job profile for the function execution, including
768
- * all state, process, and timeline data. The information in the export
769
- * is sufficient to capture the full state of the function in the moment
770
- * and over time.
771
- *
772
- * @param {string} entity - the entity name (e.g, 'user', 'order', 'product')
773
- * @param {string} id - The workflow/job id
774
- * @param {ExportOptions} [options={}] - Configuration options for the export
775
- * @param {string} [namespace='memflow'] - the namespace for the client
776
- *
777
- * @example
778
- * // Export a function
779
- * await meshData.export('greeting', 'jsmith123');
780
- */
781
- async export(entity, id, options, namespace) {
782
- const workflowId = MeshData.mintGuid(entity, id);
783
- const handle = await this.getClient().workflow.getHandle(entity, entity, workflowId, namespace);
784
- return await handle.export(options);
785
- }
786
- /**
787
- * Returns the remote function state. this is different than the function response
788
- * returned by the `exec` method which represents the return value from the
789
- * function at the moment it completed. Instead, function state represents
790
- * mutable shared state that can be set:
791
- * 1) when the record is first created (provide `options.search.data` to `exec`)
792
- * 2) during function execution ((await MeshData.workflow.search()).set(...))
793
- * 3) during hook execution ((await MeshData.workflow.search()).set(...))
794
- * 4) via the meshData SDK (`meshData.set(...)`)
795
- *
796
- * @param {string} entity - the entity name (e.g, 'user', 'order', 'product')
797
- * @param {string} id - the job id
798
- * @param {CallOptions} [options={}] - call options
799
- *
800
- * @returns {Promise<StringAnyType>} - the function state
801
- *
802
- * @example
803
- * // get the state of a function
804
- * const state = await meshData.get('greeting', 'jsmith123', { fields: ['fred', 'barney'] });
805
- *
806
- * // returns { fred: 'flintstone', barney: 'rubble' }
807
- */
808
- async get(entity, id, options = {}) {
809
- const workflowId = MeshData.mintGuid(options.prefix ?? entity, id);
810
- this.validate(workflowId);
811
- let prefixedFields = [];
812
- if (Array.isArray(options.fields)) {
813
- prefixedFields = options.fields.map((field) => `_${field}`);
814
- }
815
- else if (this.search?.schema) {
816
- prefixedFields = Object.entries(this.search.schema).map(([key, value]) => {
817
- return 'fieldName' in value ? value.fieldName.toString() : `_${key}`;
818
- });
819
- }
820
- else {
821
- return await this.all(entity, id, options);
822
- }
823
- const handle = await this.getClient().workflow.getHandle(entity, entity, workflowId, options.namespace);
824
- const search = handle.hotMesh.engine?.search;
825
- const jobKey = await this.mintKey(entity, workflowId, options.namespace);
826
- const vals = await search?.getFields(jobKey, prefixedFields);
827
- const result = prefixedFields.reduce((obj, field, index) => {
828
- obj[field.substring(1)] = vals?.[index];
829
- return obj;
830
- }, {});
831
- return result;
832
- }
833
- /**
834
- * Returns the remote function state for all fields. NOTE:
835
- * `all` can be less efficient than calling `get` as it returns all
836
- * fields (HGETALL), not just the ones requested (HMGET). Depending
837
- * upon the duration of the workflow, this could represent a large
838
- * amount of process/history data.
839
- *
840
- * @param {string} entity - the entity name (e.g, 'user', 'order', 'product')
841
- * @param {string} id - the workflow/job id
842
- * @param {CallOptions} [options={}] - call options
843
- *
844
- * @returns {Promise<StringAnyType>} - the function state
845
- *
846
- * @example
847
- * // get the state of the job (this is not the response...this is job state)
848
- * const state = await meshData.all('greeting', 'jsmith123');
849
- *
850
- * // returns { fred: 'flintstone', barney: 'rubble', ... }
851
- */
852
- async all(entity, id, options = {}) {
853
- const rawResponse = await this.raw(entity, id, options);
854
- const responseObj = {};
855
- for (const key in rawResponse) {
856
- if (key.startsWith('_')) {
857
- responseObj[key.substring(1)] = rawResponse[key];
858
- }
859
- }
860
- return responseObj;
861
- }
862
- /**
863
- * Returns all fields in the HASH record:
864
- *
865
- * 1) `:`: workflow status (a semaphore where `0` is complete)
866
- * 2) `_*`: function state (name/value pairs are prefixed with `_`)
867
- * 3) `-*`: workflow cycle state (cycles are prefixed with `-`)
868
- * 4) `[a-zA-Z]{3}`: mutable workflow job state
869
- * 5) `[a-zA-Z]{3}[,\d]+`: immutable workflow activity state
870
- *
871
- * @param {string} entity - the entity name (e.g, 'user', 'order', 'product')
872
- * @param {string} id - the workflow/job id
873
- * @param {CallOptions} [options={}] - call options
874
- *
875
- * @returns {Promise<StringAnyType>} - the function state
876
- *
877
- * @example
878
- * // get the state of a function
879
- * const state = await meshData.raw('greeting', 'jsmith123');
880
- *
881
- * // returns { : '0', _barney: 'rubble', aBa: 'Hello, John Doe. Your email is [jsmith@hotmesh].', ... }
882
- */
883
- async raw(entity, id, options = {}) {
884
- const workflowId = MeshData.mintGuid(options.prefix ?? entity, id);
885
- this.validate(workflowId);
886
- const handle = await this.getClient().workflow.getHandle(entity, entity, workflowId, options.namespace);
887
- const search = handle.hotMesh.engine?.search;
888
- const jobKey = await this.mintKey(entity, workflowId, options.namespace);
889
- return await search?.getAllFields(jobKey);
890
- }
891
- /**
892
- * Sets the remote function state. this is different than the function response
893
- * returned by the exec method which represents the return value from the
894
- * function at the moment it completed. Instead, function state represents
895
- * mutable shared state that can be set
896
- *
897
- * @param {string} entity - the entity name (e.g, 'user', 'order', 'product')
898
- * @param {string} id - the job id
899
- * @param {CallOptions} [options={}] - call options
900
- *
901
- * @returns {Promise<number>} - count. The number inserted (Postgres) / updated(Redis)
902
- * @example
903
- * // set the state of a function
904
- * const count = await meshData.set('greeting', 'jsmith123', { search: { data: { fred: 'flintstone', barney: 'rubble' } } });
905
- */
906
- async set(entity, id, options = {}) {
907
- const workflowId = MeshData.mintGuid(options.prefix ?? entity, id);
908
- this.validate(workflowId);
909
- const handle = await this.getClient().workflow.getHandle(entity, entity, workflowId, options.namespace);
910
- const search = handle.hotMesh.engine?.search;
911
- const jobId = await this.mintKey(entity, workflowId, options.namespace);
912
- const safeArgs = {};
913
- for (const key in options.search?.data) {
914
- safeArgs[this.safeKey(key)] = options.search?.data[key].toString();
915
- }
916
- return await search?.setFields(jobId, safeArgs);
917
- }
918
- /**
919
- * Increments a field in the remote function state.
920
- * @param {string} entity - the entity name (e.g, 'user', 'order', 'product')
921
- * @param {string} id - the job id
922
- * @param {string} field - the field name
923
- * @param {number} amount - the amount to increment
924
- * @param {CallOptions} [options={}] - call options
925
- *
926
- * @returns {Promise<number>} - the new value
927
- * @example
928
- * // increment a field in the function state
929
- * const count = await meshData.incr('greeting', 'jsmith123', 'counter', 1);
930
- */
931
- async incr(entity, id, field, amount, options = {}) {
932
- const workflowId = MeshData.mintGuid(options.prefix ?? entity, id);
933
- this.validate(workflowId);
934
- const handle = await this.getClient().workflow.getHandle(entity, entity, workflowId, options.namespace);
935
- const search = handle.hotMesh.engine?.search;
936
- const jobId = await this.mintKey(entity, workflowId, options.namespace);
937
- return await search?.incrementFieldByFloat(jobId, this.safeKey(field), amount);
938
- }
939
- /**
940
- * Deletes one or more fields from the remote function state.
941
- * @param {string} entity - the entity name (e.g, 'user', 'order', 'product')
942
- * @param {string} id - the job id
943
- * @param {CallOptions} [options={}] - call options
944
- *
945
- * @returns {Promise<number>} - the count of fields deleted
946
- * @example
947
- * // remove two hash fields from the function state
948
- * const count = await meshData.del('greeting', 'jsmith123', { fields: ['fred', 'barney'] });
949
- */
950
- async del(entity, id, options) {
951
- const workflowId = MeshData.mintGuid(options.prefix ?? entity, id);
952
- this.validate(workflowId);
953
- if (!Array.isArray(options.fields)) {
954
- throw 'Invalid arguments [options.fields is not an array]';
955
- }
956
- const prefixedFields = options.fields.map((field) => `_${field}`);
957
- const handle = await this.getClient().workflow.getHandle(entity, entity, workflowId, options.namespace);
958
- const search = handle.hotMesh.engine?.search;
959
- const jobKey = await this.mintKey(entity, workflowId, options.namespace);
960
- const count = await search?.deleteFields(jobKey, prefixedFields);
961
- return Number(count);
962
- }
963
- /**
964
- * For those implementations without a search backend, this quasi-equivalent
965
- * method is provided with a cursor for rudimentary pagination.
966
- * @param {FindJobsOptions} [options]
967
- * @returns {Promise<[string, string[]]>}
968
- * @example
969
- * // find jobs
970
- * const [cursor, jobs] = await meshData.findJobs({ match: 'greeting*' });
971
- *
972
- * // returns [ '0', [ 'hmsh:memflow:j:greeting-jsmith123', 'hmsh:memflow:j:greeting-jdoe456' ] ]
973
- */
974
- async findJobs(options = {}) {
975
- const hotMesh = await this.getHotMesh(options.namespace);
976
- return (await hotMesh.engine?.store?.findJobs(options.match, options.limit, options.batch, options.cursor));
977
- }
978
- /**
979
- * Executes the search query; optionally specify other commands
980
- * @example '@_quantity:[89 89]'
981
- * @example '@_quantity:[89 89] @_name:"John"'
982
- * @example 'FT.search my-index @_quantity:[89 89]'
983
- * @param {FindOptions} options
984
- * @param {any[]} args
985
- * @returns {Promise<string[] | [number] | Array<number, string | number | string[]>>}
986
- */
987
- async find(entity, options, ...args) {
988
- return await this.getClient().workflow.search(options.taskQueue ?? entity, options.workflowName ?? entity, options.namespace || 'memflow', options.index ?? options.search?.index ?? this.search.index ?? '', ...args); //[count, [id, fields[]], [id, fields[]], [id, fields[]], ...]]
989
- }
990
- /**
991
- * Provides a JSON abstraction for the backend search engine
992
- * (e.g, `count`, `query`, `return`, `limit`)
993
- * NOTE: If the type is TAG for an entity, `.`, `@`, and `-` must be escaped.
994
- *
995
- * @param {string} entity - the entity name (e.g, 'user', 'order', 'product')
996
- * @param {FindWhereOptions} options - find options (the query). A custom search schema may be provided.
997
- * @returns {Promise<SearchResults | number>} Returns a number if `count` is true, otherwise a SearchResults object.
998
- * @example
999
- * const results = await meshData.findWhere('greeting', {
1000
- * query: [
1001
- * { field: 'name', is: '=', value: 'John' },
1002
- * { field: 'age', is: '>', value: 2 },
1003
- * { field: 'quantity', is: '[]', value: [89, 89] }
1004
- * ],
1005
- * count: false,
1006
- * limit: { start: 0, size: 10 },
1007
- * return: ['name', 'quantity']
1008
- * });
1009
- *
1010
- * // returns { count: 1, query: 'FT.SEARCH my-index @_name:"John" @_age:[2 +inf] @_quantity:[89 89] LIMIT 0 10', data: [ { name: 'John', quantity: '89' } ] }
1011
- */
1012
- async findWhere(entity, options) {
1013
- const targetSearch = options.options?.search ?? this.search;
1014
- const args = [
1015
- this.generateSearchQuery(options.query, targetSearch),
1016
- ];
1017
- if (options.count) {
1018
- args.push('LIMIT', '0', '0');
1019
- }
1020
- else {
1021
- //limit which hash fields to return
1022
- args.push('RETURN');
1023
- args.push(((options.return?.length ?? 0) + 1).toString());
1024
- args.push('$');
1025
- options.return?.forEach((returnField) => {
1026
- if (returnField.startsWith('"')) {
1027
- //request a literal hash value
1028
- args.push(returnField.slice(1, -1));
1029
- }
1030
- else {
1031
- //request a search hash value
1032
- args.push(`_${returnField}`);
1033
- }
1034
- });
1035
- //paginate
1036
- if (options.limit) {
1037
- args.push('LIMIT', options.limit.start.toString(), options.limit.size.toString());
1038
- }
1039
- }
1040
- const FTResults = await this.find(entity, options.options ?? {}, ...args);
1041
- const count = FTResults[0];
1042
- const sargs = `FT.SEARCH ${options.options?.index ?? targetSearch?.index} ${args.join(' ')}`;
1043
- if (options.count) {
1044
- //always return number format if count is requested
1045
- return !isNaN(count) || count > 0 ? count : 0;
1046
- }
1047
- else if (count === 0) {
1048
- return { count, query: sargs, data: [] };
1049
- }
1050
- const hashes = this.arrayToHash(FTResults);
1051
- return { count, query: sargs, data: hashes };
1052
- }
1053
- /**
1054
- * Generates a search query from a FindWhereQuery array
1055
- * @param {FindWhereQuery[] | string} [query]
1056
- * @param {WorkflowSearchOptions} [search]
1057
- * @returns {string}
1058
- * @private
1059
- */
1060
- generateSearchQuery(query, search) {
1061
- if (!Array.isArray(query) || query.length === 0) {
1062
- return typeof query === 'string' ? query : '*';
1063
- }
1064
- const queryString = query
1065
- .map((q) => {
1066
- const { field, is, value, type } = q;
1067
- let prefixedFieldName;
1068
- //insert the underscore prefix if requested field in query is not a literal
1069
- if (search?.schema && field in search.schema) {
1070
- if ('fieldName' in search.schema[field]) {
1071
- prefixedFieldName = `@${search.schema[field].fieldName}`;
1072
- }
1073
- else {
1074
- prefixedFieldName = `@_${field}`;
1075
- }
1076
- }
1077
- else {
1078
- prefixedFieldName = `@${field}`;
1079
- }
1080
- const fieldType = search?.schema?.[field]?.type ?? type ?? 'TEXT';
1081
- switch (fieldType) {
1082
- case 'TAG':
1083
- return `${prefixedFieldName}:{${value}}`;
1084
- case 'TEXT':
1085
- return `${prefixedFieldName}:"${value}"`;
1086
- case 'NUMERIC':
1087
- let range = '';
1088
- if (is.startsWith('=')) {
1089
- //equal
1090
- range = `[${value} ${value}]`;
1091
- }
1092
- else if (is.startsWith('<')) {
1093
- //less than or equal
1094
- range = `[-inf ${value}]`;
1095
- }
1096
- else if (is.startsWith('>')) {
1097
- //greater than or equal
1098
- range = `[${value} +inf]`;
1099
- }
1100
- else if (is === '[]') {
1101
- //between
1102
- range = `[${value[0]} ${value[1]}]`;
1103
- }
1104
- return `${prefixedFieldName}:${range}`;
1105
- default:
1106
- return '';
1107
- }
1108
- })
1109
- .join(' ');
1110
- return queryString;
1111
- }
1112
- /**
1113
- * Creates a search index for the specified search backend (e.g., FT.search).
1114
- * @param {string} entity - the entity name (e.g, 'user', 'order', 'product')
1115
- * @param {CallOptions} [options={}] - call options
1116
- * @param {WorkflowSearchOptions} [searchOptions] - search options
1117
- * @returns {Promise<string>} - the search index name
1118
- * @example
1119
- * // create a search index for the 'greeting' entity. pass in search options.
1120
- * const index = await meshData.createSearchIndex('greeting', {}, { prefix: 'greeting', ... });
1121
- *
1122
- * // creates a search index for the 'greeting' entity, using the default search options.
1123
- * const index = await meshData.createSearchIndex('greeting');
1124
- */
1125
- async createSearchIndex(entity, options = {}, searchOptions) {
1126
- const workflowTopic = `${options.taskQueue ?? entity}-${entity}`;
1127
- const hotMeshClient = await this.getClient().getHotMeshClient(workflowTopic, options.namespace);
1128
- return await memflow_1.MemFlow.Search.configureSearchIndex(hotMeshClient, searchOptions ?? this.search);
1129
- }
1130
- /**
1131
- * Lists all search indexes in the operational data layer when the
1132
- * targeted search backend is configured/enabled.
1133
- * @returns {Promise<string[]>}
1134
- * @example
1135
- * // list all search indexes
1136
- * const indexes = await meshData.listSearchIndexes();
1137
- *
1138
- * // returns ['greeting', 'user', 'order', 'product']
1139
- */
1140
- async listSearchIndexes() {
1141
- const hotMeshClient = await this.getHotMesh();
1142
- return await memflow_1.MemFlow.Search.listSearchIndexes(hotMeshClient);
1143
- }
1144
- /**
1145
- * shut down MeshData (typically on sigint or sigterm)
1146
- */
1147
- static async shutdown() {
1148
- await memflow_1.MemFlow.shutdown();
1149
- }
1150
- }
1151
- exports.MeshData = MeshData;
1152
- _a = MeshData;
1153
- /**
1154
- * Provides a set of static extensions that can be invoked by
1155
- * your linked workflow functions during their execution.
1156
- * @example
1157
- *
1158
- * function greet (email: string, user: { first: string}) {
1159
- * //persist the user's email and newsletter preferences
1160
- * const search = await MeshData.workflow.search();
1161
- * await search.set('email', email, 'newsletter', 'yes');
1162
- *
1163
- * //hook a function to send a newsletter
1164
- * await MeshData.workflow.hook({
1165
- * entity: 'user.newsletter',
1166
- * args: [email]
1167
- * });
1168
- *
1169
- * return `Hello, ${user.first}. Your email is [${email}].`;
1170
- * }
1171
- */
1172
- MeshData.workflow = {
1173
- sleep: memflow_1.MemFlow.workflow.sleepFor,
1174
- sleepFor: memflow_1.MemFlow.workflow.sleepFor,
1175
- signal: memflow_1.MemFlow.workflow.signal,
1176
- hook: memflow_1.MemFlow.workflow.hook,
1177
- waitForSignal: memflow_1.MemFlow.workflow.waitFor,
1178
- waitFor: memflow_1.MemFlow.workflow.waitFor,
1179
- getHotMesh: memflow_1.MemFlow.workflow.getHotMesh,
1180
- random: memflow_1.MemFlow.workflow.random,
1181
- search: memflow_1.MemFlow.workflow.search,
1182
- getContext: memflow_1.MemFlow.workflow.getContext,
1183
- /**
1184
- * Interrupts a job by its entity and id.
1185
- */
1186
- interrupt: async (entity, id, options = {}) => {
1187
- const jobId = MeshData.mintGuid(entity, id);
1188
- await memflow_1.MemFlow.workflow.interrupt(jobId, options);
1189
- },
1190
- /**
1191
- * Starts a new, subordinated workflow/job execution. NOTE: The child workflow's
1192
- * lifecycle is bound to the parent workflow, and it will be terminated/scrubbed
1193
- * when the parent workflow is terminated/scrubbed.
1194
- *
1195
- * @template T The expected return type of the target function.
1196
- */
1197
- execChild: async (options = {}) => {
1198
- const pluckOptions = {
1199
- ...options,
1200
- args: [...options.args, { $type: 'exec' }],
1201
- };
1202
- return memflow_1.MemFlow.workflow.execChild(pluckOptions);
1203
- },
1204
- /**
1205
- * Starts a new, subordinated workflow/job execution. NOTE: The child workflow's
1206
- * lifecycle is bound to the parent workflow, and it will be terminated/scrubbed
1207
- * when the parent workflow is terminated/scrubbed.
1208
- *
1209
- * @template T The expected return type of the target function.
1210
- */
1211
- executeChild: async (options = {}) => {
1212
- const pluckOptions = {
1213
- ...options,
1214
- args: [...options.args, { $type: 'exec' }],
1215
- };
1216
- return memflow_1.MemFlow.workflow.execChild(pluckOptions);
1217
- },
1218
- /**
1219
- * Starts a new, subordinated workflow/job execution, awaiting only the jobId, namely,
1220
- * the confirmation that the suboridinated job has begun. NOTE: The child workflow's
1221
- * lifecycle is bound to the parent workflow, and it will be terminated/scrubbed
1222
- * when the parent workflow is terminated/scrubbed.
1223
- */
1224
- startChild: async (options = {}) => {
1225
- const pluckOptions = {
1226
- ...options,
1227
- args: [...options.args, { $type: 'exec' }],
1228
- };
1229
- return memflow_1.MemFlow.workflow.startChild(pluckOptions);
1230
- },
1231
- };
1232
- /**
1233
- * Wrap activities in a proxy that will durably run them, once.
1234
- */
1235
- MeshData.proxyActivities = memflow_1.MemFlow.workflow.proxyActivities;