@hotmeshio/hotmesh 0.0.23 → 0.0.24

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 (37) hide show
  1. package/README.md +66 -67
  2. package/build/index.d.ts +2 -1
  3. package/build/index.js +3 -1
  4. package/build/package.json +2 -2
  5. package/build/services/durable/factory.js +6 -6
  6. package/build/services/durable/handle.js +2 -4
  7. package/build/services/durable/index.d.ts +2 -2
  8. package/build/services/durable/index.js +2 -2
  9. package/build/services/durable/meshos.d.ts +108 -0
  10. package/build/services/durable/meshos.js +289 -0
  11. package/build/services/durable/search.js +0 -1
  12. package/build/services/durable/worker.d.ts +1 -1
  13. package/build/services/durable/worker.js +8 -4
  14. package/build/services/durable/workflow.d.ts +4 -0
  15. package/build/services/durable/workflow.js +21 -9
  16. package/build/services/signaler/stream.js +1 -2
  17. package/build/services/store/clients/ioredis.js +2 -2
  18. package/build/services/store/clients/redis.js +1 -1
  19. package/build/types/durable.d.ts +19 -5
  20. package/build/types/index.d.ts +1 -1
  21. package/index.ts +2 -1
  22. package/package.json +2 -2
  23. package/services/durable/factory.ts +6 -6
  24. package/services/durable/handle.ts +2 -4
  25. package/services/durable/index.ts +2 -2
  26. package/services/durable/meshos.ts +344 -0
  27. package/services/durable/search.ts +0 -1
  28. package/services/durable/worker.ts +8 -5
  29. package/services/durable/workflow.ts +23 -10
  30. package/services/signaler/stream.ts +1 -2
  31. package/services/store/clients/ioredis.ts +2 -3
  32. package/services/store/clients/redis.ts +1 -1
  33. package/types/durable.ts +26 -6
  34. package/types/index.ts +6 -2
  35. package/build/services/durable/meshdb.d.ts +0 -113
  36. package/build/services/durable/meshdb.js +0 -211
  37. package/services/durable/meshdb.ts +0 -254
@@ -1,113 +0,0 @@
1
- import { WorkflowHandleService } from './handle';
2
- import { WorkflowSearchOptions } from '../../types/durable';
3
- import { RedisOptions, RedisClass } from '../../types/redis';
4
- /**
5
- * A base class for configuration and setup of
6
- * a reentrant process database. Entities modeled as
7
- * subclasses of this class will execute as reentrant
8
- * processes with a 'main' execution thread and 'n'
9
- * parallel hook threads.
10
- *
11
- * @example
12
- * //RUN (start a workflow)
13
- * const myInstance = new MeshDB('someIdempotentGuid');
14
- * const handle = await myInstance.create(100);
15
- * await handle.result(); //100
16
- *
17
- * //UPDATE (update a workflow)
18
- * const result = await myInstance.decrement(11);
19
- */
20
- export declare class MeshDBService {
21
- /**
22
- * The name of the main method. When this method
23
- * is invoked/proxied, it is assumed that a new
24
- * workflow instance is being created. In all other
25
- * cases, the call is assumed to be a hook/update
26
- */
27
- main: string;
28
- /**
29
- * The GUID for the workflow (assigned when created). This
30
- * value should be idempotent and will be rejected if an
31
- * instance is already running with the same id.
32
- */
33
- id: string;
34
- /**
35
- * test value
36
- */
37
- value: number;
38
- /**
39
- * The top-level Redis isolation. All workflow data is
40
- * isolated within this namespace. Values should be
41
- * lower-case with no spaces (e.g, 'staging', 'prod', 'test',
42
- * 'routing-stagig', 'reporting-prod', etc.).
43
- * 1) only url-safe values are allowed;
44
- * 2) the 'a' symbol is reserved by HotMesh for indexing apps
45
- */
46
- namespace: string;
47
- /**
48
- * The second-level isolation. Data is routed to workers
49
- * that specify this task queue. Setting the task queue
50
- * when the worker is created will ensure that the worker
51
- * only receives messages destined for the queue. This
52
- * allows callers to specify specific workers/containers
53
- * for specific tasks. Only url-safe values are allowed.
54
- */
55
- taskQueue: string;
56
- /**
57
- * The Redis connection options. NOTE: Redis and IORedis
58
- * use different formats for their connection config.
59
- */
60
- redisOptions: RedisOptions;
61
- /**
62
- * The Redis connection class. Import as follows
63
- * within the base subclass as follows:
64
- *
65
- * @example
66
- * import Redis from 'ioredis';
67
- * import * as Redis from 'redis';
68
- */
69
- redisClass: RedisClass | null;
70
- /**
71
- * Configuration for the the Redis FT search index.
72
- */
73
- search: WorkflowSearchOptions;
74
- static getHotMeshClient(redisClass: RedisClass, redisOptions: RedisOptions, namespace: string, taskQueue: string): Promise<import("../hotmesh").HotMeshService>;
75
- /**
76
- * mints a new key, using the provided search prefix, ensuring
77
- * new workflows are properly indexed
78
- * @returns {string}
79
- */
80
- static mintGuid(): string;
81
- /**
82
- * Creates an FT search index
83
- */
84
- static createIndex(): Promise<void>;
85
- /**
86
- * Initialize the worker(s) for the entity. This is a static
87
- * method that allows for optional task Queue targeting.
88
- * NOTE: Allow List may be optionally used
89
- * @param {string} taskQueue
90
- * @param {string[]} allowList
91
- */
92
- static doWork(taskQueue?: string, allowList?: string[]): Promise<void>;
93
- /**
94
- * executes the redis FT search query
95
- * @example '@_quantity:[89 89]'
96
- * @param {any[]} args
97
- * @returns {string}
98
- */
99
- static find(...args: string[]): Promise<string[] | [number]>;
100
- /**
101
- * returns the workflow handle (use the handle to call:
102
- * `state`, `status`, `queryStatus`, and `result`)
103
- * @param {string} id
104
- * @returns {Promise<WorkflowHandleService>}
105
- */
106
- static get(id: string): Promise<WorkflowHandleService>;
107
- /**
108
- * Initialize with an idempotent workflow identifier.
109
- * Optionally include a target taskQueue to send
110
- * events to a specific worker.
111
- */
112
- constructor(id?: string, taskQueue?: string);
113
- }
@@ -1,211 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MeshDBService = void 0;
4
- const nanoid_1 = require("nanoid");
5
- const client_1 = require("./client");
6
- const search_1 = require("./search");
7
- const worker_1 = require("./worker");
8
- /**
9
- * A base class for configuration and setup of
10
- * a reentrant process database. Entities modeled as
11
- * subclasses of this class will execute as reentrant
12
- * processes with a 'main' execution thread and 'n'
13
- * parallel hook threads.
14
- *
15
- * @example
16
- * //RUN (start a workflow)
17
- * const myInstance = new MeshDB('someIdempotentGuid');
18
- * const handle = await myInstance.create(100);
19
- * await handle.result(); //100
20
- *
21
- * //UPDATE (update a workflow)
22
- * const result = await myInstance.decrement(11);
23
- */
24
- class MeshDBService {
25
- static async getHotMeshClient(redisClass, redisOptions, namespace, taskQueue) {
26
- const client = new client_1.ClientService({
27
- connection: {
28
- class: redisClass,
29
- options: redisOptions,
30
- }
31
- });
32
- return await client.getHotMeshClient(taskQueue, namespace);
33
- }
34
- /**
35
- * mints a new key, using the provided search prefix, ensuring
36
- * new workflows are properly indexed
37
- * @returns {string}
38
- */
39
- static mintGuid() {
40
- const my = new this();
41
- return `${my.search?.prefix?.[0]}${(0, nanoid_1.nanoid)()}}`;
42
- }
43
- /**
44
- * Creates an FT search index
45
- */
46
- static async createIndex() {
47
- const my = new this();
48
- const hmClient = await MeshDBService.getHotMeshClient(my.redisClass, my.redisOptions, my.namespace, my.taskQueue);
49
- search_1.Search.configureSearchIndex(hmClient, my.search);
50
- }
51
- /**
52
- * Initialize the worker(s) for the entity. This is a static
53
- * method that allows for optional task Queue targeting.
54
- * NOTE: Allow List may be optionally used
55
- * @param {string} taskQueue
56
- * @param {string[]} allowList
57
- */
58
- static async doWork(taskQueue, allowList) {
59
- const my = new this();
60
- let prototype = Object.getPrototypeOf(my);
61
- const durablePromises = [];
62
- const found = [];
63
- while (prototype !== null && !Object.getOwnPropertyNames(prototype).includes('__proto__')) {
64
- const promises = Object.getOwnPropertyNames(prototype).map((prop) => {
65
- if (found.includes(prop) || ['constructor'].includes(prop) || (allowList && !allowList.includes(prop))) {
66
- return;
67
- }
68
- const originalMethod = my[prop];
69
- if (typeof originalMethod === 'function') {
70
- found.push(prop);
71
- return worker_1.WorkerService.create({
72
- namespace: my.namespace,
73
- connection: {
74
- class: my.redisClass,
75
- options: my.redisOptions,
76
- },
77
- taskQueue: taskQueue ?? my.taskQueue,
78
- workflow: originalMethod,
79
- });
80
- }
81
- }).filter(p => p !== undefined); // filter out undefined values
82
- durablePromises.push(...promises);
83
- prototype = Object.getPrototypeOf(prototype);
84
- }
85
- await Promise.all(durablePromises);
86
- }
87
- /**
88
- * executes the redis FT search query
89
- * @example '@_quantity:[89 89]'
90
- * @param {any[]} args
91
- * @returns {string}
92
- */
93
- static async find(...args) {
94
- const my = new this();
95
- const client = new client_1.ClientService({ connection: {
96
- class: my.redisClass,
97
- options: my.redisOptions
98
- } });
99
- return await client.workflow.search(my.taskQueue, my.main, my.namespace, my.search.index, ...args);
100
- //[count, [id, fields[], id, fields[], id, fields[], ...]]
101
- }
102
- /**
103
- * returns the workflow handle (use the handle to call:
104
- * `state`, `status`, `queryStatus`, and `result`)
105
- * @param {string} id
106
- * @returns {Promise<WorkflowHandleService>}
107
- */
108
- static async get(id) {
109
- const my = new this();
110
- const client = new client_1.ClientService({ connection: {
111
- class: my.redisClass,
112
- options: my.redisOptions
113
- } });
114
- return await client.workflow.getHandle(my.taskQueue, my.main, id, my.namespace);
115
- }
116
- /**
117
- * Initialize with an idempotent workflow identifier.
118
- * Optionally include a target taskQueue to send
119
- * events to a specific worker.
120
- */
121
- constructor(id, taskQueue) {
122
- /**
123
- * The name of the main method. When this method
124
- * is invoked/proxied, it is assumed that a new
125
- * workflow instance is being created. In all other
126
- * cases, the call is assumed to be a hook/update
127
- */
128
- this.main = 'create';
129
- /**
130
- * The top-level Redis isolation. All workflow data is
131
- * isolated within this namespace. Values should be
132
- * lower-case with no spaces (e.g, 'staging', 'prod', 'test',
133
- * 'routing-stagig', 'reporting-prod', etc.).
134
- * 1) only url-safe values are allowed;
135
- * 2) the 'a' symbol is reserved by HotMesh for indexing apps
136
- */
137
- this.namespace = 'durable';
138
- /**
139
- * The second-level isolation. Data is routed to workers
140
- * that specify this task queue. Setting the task queue
141
- * when the worker is created will ensure that the worker
142
- * only receives messages destined for the queue. This
143
- * allows callers to specify specific workers/containers
144
- * for specific tasks. Only url-safe values are allowed.
145
- */
146
- this.taskQueue = 'default';
147
- /**
148
- * The Redis connection options. NOTE: Redis and IORedis
149
- * use different formats for their connection config.
150
- */
151
- this.redisOptions = {
152
- host: 'localhost',
153
- port: 6379,
154
- password: '',
155
- db: 0,
156
- };
157
- /**
158
- * The Redis connection class. Import as follows
159
- * within the base subclass as follows:
160
- *
161
- * @example
162
- * import Redis from 'ioredis';
163
- * import * as Redis from 'redis';
164
- */
165
- this.redisClass = null;
166
- this.id = id;
167
- if (taskQueue) {
168
- this.taskQueue = taskQueue;
169
- }
170
- else if (!id && !taskQueue) {
171
- return this;
172
- }
173
- return new Proxy(this, {
174
- get: (target, prop, receiver) => {
175
- if (typeof target[prop] === 'function') {
176
- return (...args) => {
177
- return new Promise(async (resolve, reject) => {
178
- const client = new client_1.ClientService({ connection: {
179
- class: this.redisClass,
180
- options: this.redisOptions
181
- } });
182
- if (prop === this.main) {
183
- //start a new workflow (main method was called)
184
- return client.workflow.start({
185
- namespace: this.namespace,
186
- args,
187
- taskQueue: this.taskQueue,
188
- workflowName: prop,
189
- workflowId: this.id,
190
- }).then(resolve).catch(reject);
191
- }
192
- else if (prop !== 'constructor') {
193
- //update an existing workflow (hook/signal-in)
194
- return client.workflow.hook({
195
- namespace: this.namespace,
196
- taskQueue: this.taskQueue,
197
- workflowName: prop,
198
- workflowId: this.id,
199
- args,
200
- }).then(resolve).catch(reject);
201
- }
202
- target[prop].apply(this, args).then(resolve).catch(reject);
203
- });
204
- };
205
- }
206
- return Reflect.get(target, prop, receiver);
207
- },
208
- });
209
- }
210
- }
211
- exports.MeshDBService = MeshDBService;
@@ -1,254 +0,0 @@
1
- import { nanoid } from 'nanoid';
2
-
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 { WorkflowSearchOptions } from '../../types/durable';
8
- import { RedisOptions, RedisClass } from '../../types/redis';
9
-
10
- /**
11
- * A base class for configuration and setup of
12
- * a reentrant process database. Entities modeled as
13
- * subclasses of this class will execute as reentrant
14
- * processes with a 'main' execution thread and 'n'
15
- * parallel hook threads.
16
- *
17
- * @example
18
- * //RUN (start a workflow)
19
- * const myInstance = new MeshDB('someIdempotentGuid');
20
- * const handle = await myInstance.create(100);
21
- * await handle.result(); //100
22
- *
23
- * //UPDATE (update a workflow)
24
- * const result = await myInstance.decrement(11);
25
- */
26
-
27
- export class MeshDBService {
28
-
29
- /**
30
- * The name of the main method. When this method
31
- * is invoked/proxied, it is assumed that a new
32
- * workflow instance is being created. In all other
33
- * cases, the call is assumed to be a hook/update
34
- */
35
- main = 'create';
36
-
37
- /**
38
- * The GUID for the workflow (assigned when created). This
39
- * value should be idempotent and will be rejected if an
40
- * instance is already running with the same id.
41
- */
42
- id: string;
43
-
44
- /**
45
- * test value
46
- */
47
- value: number;
48
-
49
- /**
50
- * The top-level Redis isolation. All workflow data is
51
- * isolated within this namespace. Values should be
52
- * lower-case with no spaces (e.g, 'staging', 'prod', 'test',
53
- * 'routing-stagig', 'reporting-prod', etc.).
54
- * 1) only url-safe values are allowed;
55
- * 2) the 'a' symbol is reserved by HotMesh for indexing apps
56
- */
57
- namespace = 'durable';
58
-
59
- /**
60
- * The second-level isolation. Data is routed to workers
61
- * that specify this task queue. Setting the task queue
62
- * when the worker is created will ensure that the worker
63
- * only receives messages destined for the queue. This
64
- * allows callers to specify specific workers/containers
65
- * for specific tasks. Only url-safe values are allowed.
66
- */
67
- taskQueue = 'default';
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. Import as follows
82
- * within the base subclass as follows:
83
- *
84
- * @example
85
- * import Redis from 'ioredis';
86
- * import * as Redis from 'redis';
87
- */
88
- redisClass: RedisClass | null = null;
89
-
90
- /**
91
- * Configuration for the the Redis FT search index.
92
- */
93
- search: WorkflowSearchOptions;
94
-
95
- static async getHotMeshClient (redisClass: RedisClass, redisOptions: RedisOptions, namespace: string, taskQueue: string) {
96
- const client = new Client({
97
- connection: {
98
- class: redisClass,
99
- options: redisOptions,
100
- }
101
- });
102
- return await client.getHotMeshClient(taskQueue, namespace);
103
- }
104
-
105
- /**
106
- * mints a new key, using the provided search prefix, ensuring
107
- * new workflows are properly indexed
108
- * @returns {string}
109
- */
110
- static mintGuid(): string {
111
- const my = new this();
112
- return `${my.search?.prefix?.[0]}${nanoid()}}`;
113
- }
114
-
115
- /**
116
- * Creates an FT search index
117
- */
118
- static async createIndex() {
119
- const my = new this();
120
- const hmClient = await MeshDBService.getHotMeshClient(my.redisClass, my.redisOptions, my.namespace, my.taskQueue);
121
- Search.configureSearchIndex(hmClient, my.search)
122
- }
123
-
124
- /**
125
- * Initialize the worker(s) for the entity. This is a static
126
- * method that allows for optional task Queue targeting.
127
- * NOTE: Allow List may be optionally used
128
- * @param {string} taskQueue
129
- * @param {string[]} allowList
130
- */
131
- static async doWork(taskQueue?: string, allowList?: string[]) {
132
- const my = new this();
133
- let prototype = Object.getPrototypeOf(my);
134
- const durablePromises = [];
135
- const found = [];
136
-
137
- while (prototype !== null && !Object.getOwnPropertyNames(prototype).includes('__proto__')) {
138
- const promises = Object.getOwnPropertyNames(prototype).map((prop) => {
139
- if (found.includes(prop) || ['constructor'].includes(prop) || (allowList && !allowList.includes(prop))) {
140
- return;
141
- }
142
- const originalMethod = my[prop];
143
- if (typeof originalMethod === 'function') {
144
- found.push(prop);
145
- return Worker.create({
146
- namespace: my.namespace,
147
- connection: {
148
- class: my.redisClass,
149
- options: my.redisOptions,
150
- },
151
- taskQueue: taskQueue ?? my.taskQueue,
152
- workflow: originalMethod,
153
- });
154
- }
155
- }).filter(p => p !== undefined); // filter out undefined values
156
- durablePromises.push(...promises);
157
- prototype = Object.getPrototypeOf(prototype);
158
- }
159
- await Promise.all(durablePromises);
160
- }
161
-
162
- /**
163
- * executes the redis FT search query
164
- * @example '@_quantity:[89 89]'
165
- * @param {any[]} args
166
- * @returns {string}
167
- */
168
- static async find(...args: string[]): Promise<string[] | [number]> {
169
- const my = new this();
170
- const client = new Client({ connection: {
171
- class: my.redisClass,
172
- options: my.redisOptions
173
- }});
174
- return await client.workflow.search(
175
- my.taskQueue,
176
- my.main,
177
- my.namespace,
178
- my.search.index,
179
- ...args,
180
- );
181
- //[count, [id, fields[], id, fields[], id, fields[], ...]]
182
- }
183
-
184
- /**
185
- * returns the workflow handle (use the handle to call:
186
- * `state`, `status`, `queryStatus`, and `result`)
187
- * @param {string} id
188
- * @returns {Promise<WorkflowHandleService>}
189
- */
190
- static async get(id: string): Promise<WorkflowHandleService> {
191
- const my = new this();
192
- const client = new Client({ connection: {
193
- class: my.redisClass,
194
- options: my.redisOptions
195
- }});
196
- return await client.workflow.getHandle(
197
- my.taskQueue,
198
- my.main,
199
- id,
200
- my.namespace,
201
- );
202
- }
203
-
204
- /**
205
- * Initialize with an idempotent workflow identifier.
206
- * Optionally include a target taskQueue to send
207
- * events to a specific worker.
208
- */
209
- constructor(id?: string, taskQueue?: string) {
210
- this.id = id;
211
- if (taskQueue) {
212
- this.taskQueue = taskQueue;
213
- } else if (!id && !taskQueue) {
214
- return this;
215
- }
216
-
217
- return new Proxy(this, {
218
- get: (target, prop, receiver) => {
219
- if (typeof target[prop] === 'function') {
220
- return (...args: any[]) => {
221
-
222
- return new Promise(async (resolve, reject) => {
223
- const client = new Client({ connection: {
224
- class: this.redisClass,
225
- options: this.redisOptions
226
- }});
227
- if (prop === this.main) {
228
- //start a new workflow (main method was called)
229
- return client.workflow.start({
230
- namespace: this.namespace,
231
- args,
232
- taskQueue: this.taskQueue,
233
- workflowName: prop,
234
- workflowId: this.id,
235
- }).then(resolve).catch(reject);
236
- } else if (prop !== 'constructor') {
237
- //update an existing workflow (hook/signal-in)
238
- return client.workflow.hook({
239
- namespace: this.namespace,
240
- taskQueue: this.taskQueue,
241
- workflowName: prop as string,
242
- workflowId: this.id,
243
- args,
244
- }).then(resolve).catch(reject);
245
- }
246
- target[prop].apply(this, args).then(resolve).catch(reject);
247
- });
248
- };
249
- }
250
- return Reflect.get(target, prop, receiver);
251
- },
252
- });
253
- }
254
- }