@hotmeshio/hotmesh 0.0.51 → 0.0.53

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 (126) hide show
  1. package/README.md +13 -9
  2. package/build/index.d.ts +1 -2
  3. package/build/index.js +1 -3
  4. package/build/modules/enums.d.ts +8 -3
  5. package/build/modules/enums.js +16 -8
  6. package/build/modules/errors.d.ts +98 -18
  7. package/build/modules/errors.js +90 -33
  8. package/build/package.json +7 -2
  9. package/build/services/activities/activity.d.ts +8 -0
  10. package/build/services/activities/activity.js +65 -16
  11. package/build/services/activities/await.js +6 -6
  12. package/build/services/activities/cycle.d.ts +2 -2
  13. package/build/services/activities/cycle.js +5 -5
  14. package/build/services/activities/hook.js +4 -4
  15. package/build/services/activities/interrupt.d.ts +3 -3
  16. package/build/services/activities/interrupt.js +15 -6
  17. package/build/services/activities/signal.d.ts +2 -2
  18. package/build/services/activities/signal.js +4 -4
  19. package/build/services/activities/trigger.js +12 -3
  20. package/build/services/activities/worker.js +6 -6
  21. package/build/services/compiler/deployer.js +33 -5
  22. package/build/services/compiler/validator.d.ts +2 -0
  23. package/build/services/compiler/validator.js +5 -1
  24. package/build/services/durable/client.d.ts +7 -1
  25. package/build/services/durable/client.js +56 -30
  26. package/build/services/durable/exporter.d.ts +7 -72
  27. package/build/services/durable/exporter.js +105 -295
  28. package/build/services/durable/handle.d.ts +11 -6
  29. package/build/services/durable/handle.js +59 -46
  30. package/build/services/durable/index.d.ts +0 -2
  31. package/build/services/durable/index.js +0 -2
  32. package/build/services/durable/schemas/factory.d.ts +33 -0
  33. package/build/services/durable/schemas/factory.js +2356 -0
  34. package/build/services/durable/search.js +8 -8
  35. package/build/services/durable/worker.js +117 -25
  36. package/build/services/durable/workflow.d.ts +46 -43
  37. package/build/services/durable/workflow.js +273 -277
  38. package/build/services/engine/index.js +3 -0
  39. package/build/services/exporter/index.d.ts +2 -4
  40. package/build/services/exporter/index.js +4 -5
  41. package/build/services/mapper/index.d.ts +6 -2
  42. package/build/services/mapper/index.js +6 -2
  43. package/build/services/pipe/functions/array.d.ts +2 -10
  44. package/build/services/pipe/functions/array.js +30 -28
  45. package/build/services/pipe/functions/conditional.d.ts +1 -0
  46. package/build/services/pipe/functions/conditional.js +3 -0
  47. package/build/services/pipe/functions/date.d.ts +1 -0
  48. package/build/services/pipe/functions/date.js +4 -0
  49. package/build/services/pipe/functions/index.d.ts +2 -0
  50. package/build/services/pipe/functions/index.js +2 -0
  51. package/build/services/pipe/functions/logical.d.ts +5 -0
  52. package/build/services/pipe/functions/logical.js +12 -0
  53. package/build/services/pipe/functions/object.d.ts +3 -0
  54. package/build/services/pipe/functions/object.js +25 -7
  55. package/build/services/pipe/index.d.ts +20 -3
  56. package/build/services/pipe/index.js +82 -16
  57. package/build/services/router/index.js +14 -3
  58. package/build/services/serializer/index.d.ts +3 -2
  59. package/build/services/serializer/index.js +11 -4
  60. package/build/services/store/clients/ioredis.js +6 -6
  61. package/build/services/store/clients/redis.js +7 -7
  62. package/build/services/store/index.d.ts +2 -0
  63. package/build/services/store/index.js +4 -1
  64. package/build/services/stream/clients/ioredis.js +8 -8
  65. package/build/services/stream/clients/redis.js +1 -1
  66. package/build/types/activity.d.ts +60 -5
  67. package/build/types/durable.d.ts +168 -33
  68. package/build/types/exporter.d.ts +26 -4
  69. package/build/types/index.d.ts +2 -2
  70. package/build/types/job.d.ts +69 -5
  71. package/build/types/pipe.d.ts +81 -3
  72. package/build/types/stream.d.ts +61 -1
  73. package/build/types/stream.js +4 -0
  74. package/index.ts +1 -2
  75. package/modules/enums.ts +16 -8
  76. package/modules/errors.ts +174 -32
  77. package/package.json +7 -2
  78. package/services/activities/activity.ts +67 -18
  79. package/services/activities/await.ts +6 -6
  80. package/services/activities/cycle.ts +7 -6
  81. package/services/activities/hook.ts +4 -4
  82. package/services/activities/interrupt.ts +19 -9
  83. package/services/activities/signal.ts +6 -5
  84. package/services/activities/trigger.ts +16 -4
  85. package/services/activities/worker.ts +7 -7
  86. package/services/compiler/deployer.ts +33 -6
  87. package/services/compiler/validator.ts +7 -3
  88. package/services/durable/client.ts +47 -14
  89. package/services/durable/exporter.ts +110 -318
  90. package/services/durable/handle.ts +63 -50
  91. package/services/durable/index.ts +0 -2
  92. package/services/durable/schemas/factory.ts +2358 -0
  93. package/services/durable/search.ts +8 -8
  94. package/services/durable/worker.ts +128 -29
  95. package/services/durable/workflow.ts +304 -288
  96. package/services/engine/index.ts +4 -0
  97. package/services/exporter/index.ts +10 -12
  98. package/services/mapper/index.ts +6 -2
  99. package/services/pipe/functions/array.ts +24 -37
  100. package/services/pipe/functions/conditional.ts +4 -0
  101. package/services/pipe/functions/date.ts +6 -0
  102. package/services/pipe/functions/index.ts +7 -5
  103. package/services/pipe/functions/logical.ts +11 -0
  104. package/services/pipe/functions/object.ts +26 -7
  105. package/services/pipe/index.ts +99 -21
  106. package/services/quorum/index.ts +1 -3
  107. package/services/router/index.ts +14 -3
  108. package/services/serializer/index.ts +12 -5
  109. package/services/store/clients/ioredis.ts +6 -6
  110. package/services/store/clients/redis.ts +7 -7
  111. package/services/store/index.ts +4 -1
  112. package/services/stream/clients/ioredis.ts +8 -8
  113. package/services/stream/clients/redis.ts +1 -1
  114. package/types/activity.ts +87 -15
  115. package/types/durable.ts +246 -73
  116. package/types/exporter.ts +31 -5
  117. package/types/index.ts +6 -7
  118. package/types/job.ts +130 -36
  119. package/types/pipe.ts +84 -3
  120. package/types/stream.ts +82 -23
  121. package/build/services/durable/factory.d.ts +0 -17
  122. package/build/services/durable/factory.js +0 -817
  123. package/build/services/durable/meshos.d.ts +0 -127
  124. package/build/services/durable/meshos.js +0 -380
  125. package/services/durable/factory.ts +0 -818
  126. package/services/durable/meshos.ts +0 -441
@@ -1,441 +0,0 @@
1
- import { Durable } from '.';
2
- import { asyncLocalStorage } from '../../modules/storage';
3
- import { ClientService as Client } from './client';
4
- import { WorkflowHandleService } from './handle';
5
- import { Search } from './search';
6
- import { WorkerService as Worker } from './worker';
7
- import { WorkflowService } from './workflow';
8
- import {
9
- FindOptions,
10
- FindWhereOptions,
11
- FindWhereQuery,
12
- MeshOSActivityOptions,
13
- MeshOSConfig,
14
- MeshOSOptions,
15
- MeshOSWorkerOptions,
16
- WorkflowSearchOptions } from '../../types/durable';
17
- import { RedisOptions, RedisClass } from '../../types/redis';
18
- import { StringAnyType } from '../../types/serializer';
19
- import { guid } from '../../modules/utils';
20
-
21
- /**
22
- * The base class for running MeshOS workflows.
23
- * Extend this class, add your Redis config, and add functions to
24
- * execute as durable `hooks`, `workflows`, and `activities`.
25
- */
26
-
27
- export class MeshOSService {
28
-
29
- /**
30
- * The top-level Redis isolation. All workflow data is
31
- * isolated within this namespace. Values should be
32
- * lower-case with no spaces (e.g, 'staging', 'prod', 'test',
33
- * 'routing-stagig', 'reporting-prod', etc.).
34
- * 1) only url-safe values are allowed;
35
- * 2) the 'a' symbol is reserved by HotMesh for indexing apps
36
- */
37
- namespace = 'durable';
38
-
39
- /**
40
- * Data is routed to workers that specify this task queue.
41
- * Setting the task queue when the worker is created will
42
- * ensure that the worker only receives messages destined
43
- * for the queue. Callers can specify the taskQue to when
44
- * starting a job to call those workers.
45
- */
46
- taskQueue = 'default';
47
-
48
- /**
49
- * These methods run as durable workflows
50
- */
51
- workflowFunctions: Array<MeshOSOptions | string> = [];
52
-
53
- /**
54
- * These methods run as hooks (hook into a running workflow)
55
- */
56
- hookFunctions: Array<MeshOSOptions | string> = [];
57
-
58
- /**
59
- * These methods run as proxied activities (and are safely memoized)
60
- */
61
- proxyFunctions: Array<MeshOSActivityOptions | string> = [];
62
-
63
- /**
64
- * The workflow GUID. Workflows will be persisted to
65
- * Redis using the pattern hmsh:<namespace>:j:<id>.
66
- */
67
- id: string;
68
-
69
- /**
70
- * The Redis connection options. NOTE: Redis and IORedis
71
- * use different formats for their connection config.
72
- */
73
- redisOptions: RedisOptions = {
74
- host: 'localhost',
75
- port: 6379,
76
- password: '',
77
- db: 0,
78
- };
79
-
80
- /**
81
- * The Redis connection class.
82
- *
83
- * @example
84
- * import Redis from 'ioredis';
85
- * import * as Redis from 'redis';
86
- */
87
- redisClass: RedisClass;
88
-
89
- /**
90
- * Optional model declaration (custom workflow state)
91
- */
92
- model: StringAnyType;
93
-
94
- /**
95
- * Optional configuration for Redis FT search
96
- */
97
- search: WorkflowSearchOptions;
98
-
99
- static MeshOS = WorkflowService
100
-
101
- static async getHotMeshClient (redisClass: RedisClass, redisOptions: RedisOptions, namespace: string, taskQueue: string) {
102
- const client = new Client({
103
- connection: {
104
- class: redisClass,
105
- options: redisOptions,
106
- }
107
- });
108
- return await client.getHotMeshClient(taskQueue, namespace);
109
- }
110
-
111
- /**
112
- * mints a workflow ID, using the search prefix.
113
- * NOTE: The prefix is necesary when indexing
114
- * HASHes when FT search is enabled.
115
- * @returns {string}
116
- */
117
- static mintGuid(): string {
118
- const my = new this();
119
- return `${my.search?.prefix?.[0]}${guid()}`;
120
- }
121
-
122
- /**
123
- * Creates an FT search index
124
- */
125
- static async createIndex() {
126
- const my = new this();
127
- const hmClient = await MeshOSService.getHotMeshClient(my.redisClass, my.redisOptions, my.namespace, my.taskQueue);
128
- await Search.configureSearchIndex(hmClient, my.search);
129
- }
130
-
131
- /**
132
- * Lists FT search indexes
133
- */
134
- static async listIndexes() {
135
- const my = new this();
136
- const hmClient = await MeshOSService.getHotMeshClient(my.redisClass, my.redisOptions, my.namespace, my.taskQueue);
137
- return await Search.listSearchIndexes(hmClient)
138
- }
139
-
140
- /**
141
- * stop the workers
142
- * @returns {Promise<void>}
143
- */
144
- static async stopWorkers(): Promise<void> {
145
- await Durable.shutdown();
146
- }
147
-
148
- /**
149
- * Initializes the worker(s). This is a static
150
- * method that allows for optional task Queue targeting.
151
- * An `allowList` may be optionally provided to start
152
- * specific `worker` and `hook` methods.
153
- * @param {MeshOSWorkerOptions} [options]
154
- */
155
- static async startWorkers(options?: MeshOSWorkerOptions) {
156
- const taskQueue = options && options.taskQueue;
157
- const allowList = options && options.allowList || [];
158
- const my = new this();
159
-
160
- //helper functions
161
- const resolveFunctionNames = (arr: any[]) => arr.map(item => typeof item === 'string' ? item : item.name);
162
- const belongsTo = (name: string, target: Array<MeshOSOptions | MeshOSActivityOptions | string>): boolean => {
163
- const isWorkflow = target.find((item: MeshOSOptions | string) => {
164
- return typeof item === 'string' ? item === name : item.name === name;
165
- });
166
- return isWorkflow !== undefined;
167
- };
168
-
169
- // proxy registered activities
170
- const proxyFunctionNames = resolveFunctionNames([...my.proxyFunctions]);
171
- if (proxyFunctionNames.length) {
172
- const proxyActivities = proxyFunctionNames.reduce((acc, funcName) => {
173
- let originalMethod = my[funcName];
174
- if (typeof originalMethod === 'function') {
175
- acc[funcName] = async (...args: any[]) => {
176
- return await originalMethod.apply(my, args);
177
- }
178
- }
179
- return acc;
180
- }, {});
181
- const proxiedActivities = Durable.workflow.proxyActivities({
182
- activities: proxyActivities
183
- });
184
- Object.assign(my, proxiedActivities);
185
- }
186
-
187
- const functionsToIterate = allowList.length ? resolveFunctionNames(allowList) : resolveFunctionNames([...my.workflowFunctions, ...my.hookFunctions]);
188
-
189
- // Iterating through the functions sequentially
190
- for (const funcName of functionsToIterate) {
191
- const originalMethod = my[funcName];
192
- if (typeof originalMethod === 'function') {
193
-
194
- //wrap the function to return
195
- const wrappedFunction = {
196
- [funcName]: async (...args: any[]) => {
197
- const store = asyncLocalStorage.getStore();
198
- const workflowId = store.get('workflowId');
199
-
200
- //use a Proxy to wrap hook methods
201
- const context = new Proxy(my, {
202
- get: (target, prop, receiver) => {
203
- if (prop === 'id') {
204
- return workflowId;
205
- } else if (typeof target[prop] === 'function') {
206
- return (...args: any[]) => {
207
- return new Promise(async (resolve, reject) => {
208
- if (belongsTo(prop as string, my.hookFunctions)) {
209
- return WorkflowService.hook({
210
- namespace: my.namespace,
211
- taskQueue: my.taskQueue,
212
- workflowName: prop as string,
213
- workflowId,
214
- args,
215
- }).then(resolve).catch(reject);
216
- }
217
- //otherwise, call the method as a standard instance method.
218
- target[prop].apply(this, args).then(resolve).catch(reject);
219
- });
220
- }
221
- }
222
- return Reflect.get(target, prop, receiver);
223
- },
224
- });
225
- return await originalMethod.apply(context, args);
226
- }
227
- };
228
-
229
- //start the worker
230
- await Worker.create({
231
- namespace: my.namespace,
232
- connection: {
233
- class: my.redisClass,
234
- options: my.redisOptions,
235
- },
236
- taskQueue: taskQueue ?? my.taskQueue,
237
- workflow: wrappedFunction,
238
- });
239
- }
240
- }
241
- }
242
-
243
- /**
244
- * executes the redis FT search query; optionally specify other commands
245
- * @example '@_quantity:[89 89]'
246
- * @example '@_quantity:[89 89] @_name:"John"'
247
- * @example 'FT.search my-index @_quantity:[89 89]'
248
- * @param {FindOptions} options
249
- * @param {any[]} args
250
- * @returns {Promise<string[] | [number] | Array<number, string | number | string[]>>}
251
- */
252
- static async find(options: FindOptions, ...args: string[]): Promise<string[] | [number] | Array<string | number | string[]>> {
253
- const my = new this();
254
- const client = new Client({ connection: {
255
- class: my.redisClass,
256
- options: my.redisOptions
257
- }});
258
-
259
- let workflowName: string;
260
- if (options.workflowName) {
261
- workflowName = options?.workflowName
262
- } else if(my.workflowFunctions?.length) {
263
- let target = my.workflowFunctions[0];
264
- if (typeof target === 'string') {
265
- workflowName = target;
266
- } else {
267
- workflowName = target.name;
268
- }
269
- }
270
- return await client.workflow.search(
271
- options.taskQueue ?? my.taskQueue,
272
- workflowName,
273
- options.namespace ?? my.namespace,
274
- options.index ?? my.search.index,
275
- ...args,
276
- ); //[count, [id, fields[]], [id, fields[]], [id, fields[]], ...]]
277
- }
278
-
279
- /**
280
- * Provides a JSON abstraction for the Redis FT.search command
281
- * (e.g, `count`, `query`, `return`, `limit`)
282
- * @param {FindWhereOptions} options
283
- * @returns {Promise<string[] | [number] | Array<string | number | string[]>>}
284
- */
285
- static async findWhere(options: FindWhereOptions): Promise<string[] | [number] | Array<string | number | string[]>> {
286
- const args: string[] = [this.generateSearchQuery(options.query)];
287
- if (options.count) {
288
- args.push('LIMIT', '0', '0');
289
- } else {
290
- //limit which hash fields to return
291
- args.push('RETURN');
292
- args.push(((options.return?.length ?? 0) + 1).toString());
293
- args.push('$');
294
- options.return?.forEach(returnField => {
295
- if (returnField.startsWith('"')) {
296
- //allow literal values to be requested
297
- args.push(returnField.slice(1, -1));
298
- } else {
299
- args.push(`_${returnField}`);
300
- }
301
- });
302
- //paginate
303
- if (options.limit) {
304
- args.push('LIMIT', options.limit.start.toString(), options.limit.size.toString());
305
- }
306
- }
307
- return await this.find(options.options ?? {}, ...args);
308
- }
309
-
310
- static generateSearchQuery(query: FindWhereQuery[]) {
311
- if (!Array.isArray(query) || query.length === 0) {
312
- return '*';
313
- }
314
- const my = new this();
315
- let queryString = query.map(q => {
316
- const { field, is, value, type } = q;
317
- const prefixedFieldName = my.search?.schema && field in my.search.schema ? `@_${field}` : `@${field}`;
318
- const fieldType = my.search?.schema[field]?.type ?? type ?? 'TEXT';
319
-
320
- switch (fieldType) {
321
- case 'TAG':
322
- return `${prefixedFieldName}:{${value}}`;
323
- case 'TEXT':
324
- return `${prefixedFieldName}:"${value}"`;
325
- case 'NUMERIC':
326
- let range = '';
327
- if (is.startsWith('=')) { //equal
328
- range = `[${value} ${value}]`;
329
- } else if (is.startsWith('<')) { //less than or equal
330
- range = `[-inf ${value}]`;
331
- } else if (is.startsWith('>')) { //greater than or equal
332
- range = `[${value} +inf]`;
333
- } else if (is === '[]') { //between
334
- range = `[${value[0]} ${value[1]}]`
335
- }
336
- return `${prefixedFieldName}:${range}`;
337
- default:
338
- return '';
339
- }
340
- }).join(' ');
341
- return queryString;
342
- }
343
-
344
- /**
345
- * returns the workflow handle. The handle can then be
346
- * used to query for status, state, custom state, etc.
347
- * @param {string} id
348
- * @returns {Promise<WorkflowHandleService>}
349
- */
350
- static async get(id: string): Promise<WorkflowHandleService> {
351
- const my = new this();
352
- const client = new Client({ connection: {
353
- class: my.redisClass,
354
- options: my.redisOptions
355
- }});
356
- let workflowName: string;
357
- let target = my.workflowFunctions[0];
358
- if (typeof target === 'string') {
359
- workflowName = target;
360
- } else {
361
- workflowName = target.name;
362
- }
363
- return await client.workflow.getHandle(
364
- my.taskQueue,
365
- workflowName,
366
- id,
367
- my.namespace,
368
- );
369
- }
370
-
371
- /**
372
- * Optionally include a target taskQueue to exec the
373
- * workflow's call on a specific worker queue.
374
- */
375
- constructor(id?: string | MeshOSConfig, options?: MeshOSConfig) {
376
- if (typeof(id) === 'string') {
377
- this.id = id;
378
- } else if (id?.id) {
379
- this.id = id.id;
380
- options = id;
381
- id = undefined;
382
- };
383
- if (options?.taskQueue) {
384
- this.taskQueue = options.taskQueue;
385
- } else if (!id && !options?.taskQueue) {
386
- return this;
387
- }
388
-
389
- function belongsTo(name: string, target: Array<MeshOSOptions | MeshOSActivityOptions | string>): boolean {
390
- const isWorkflow = target.find((item: MeshOSOptions | string) => {
391
- return typeof item === 'string' ? item === name : item.name === name;
392
- });
393
- return isWorkflow !== undefined;
394
- }
395
-
396
- return new Proxy(this, {
397
- get: (target, prop, receiver) => {
398
- if (typeof target[prop] === 'function') {
399
- return (...args: any[]) => {
400
- return new Promise(async (resolve, reject) => {
401
- const client = new Client({ connection: {
402
- class: this.redisClass,
403
- options: this.redisOptions
404
- }});
405
- if (belongsTo(prop as string, this.workflowFunctions)) {
406
- //start a new workflow
407
- const handle = await client.workflow.start({
408
- namespace: this.namespace,
409
- args,
410
- taskQueue: this.taskQueue,
411
- workflowName: prop as string,
412
- workflowId: this.id,
413
- });
414
- if (options?.await) {
415
- //wait for the workflow to complete
416
- const result = await handle.result();
417
- return resolve(result);
418
- } else {
419
- //return the workflow handle
420
- return resolve(handle);
421
- }
422
- } else if (belongsTo(prop as string, this.hookFunctions)) {
423
- //hook into a running workflow
424
- return client.workflow.hook({
425
- namespace: this.namespace,
426
- taskQueue: this.taskQueue,
427
- workflowName: prop as string,
428
- workflowId: this.id,
429
- args,
430
- }).then(resolve).catch(reject);
431
- }
432
- //otherwise, call the method as a standard instance method.
433
- target[prop].apply(this, args).then(resolve).catch(reject);
434
- });
435
- };
436
- }
437
- return Reflect.get(target, prop, receiver);
438
- },
439
- });
440
- }
441
- }