@hotmeshio/hotmesh 0.4.2 → 0.4.3

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 (30) hide show
  1. package/README.md +82 -44
  2. package/build/index.d.ts +2 -1
  3. package/build/index.js +3 -1
  4. package/build/package.json +2 -1
  5. package/build/services/activities/trigger.d.ts +1 -1
  6. package/build/services/activities/trigger.js +3 -3
  7. package/build/services/memflow/client.js +2 -1
  8. package/build/services/memflow/{context.d.ts → entity.d.ts} +34 -34
  9. package/build/services/memflow/{context.js → entity.js} +40 -40
  10. package/build/services/memflow/index.d.ts +2 -2
  11. package/build/services/memflow/index.js +2 -2
  12. package/build/services/memflow/workflow/common.d.ts +2 -2
  13. package/build/services/memflow/workflow/common.js +3 -3
  14. package/build/services/memflow/workflow/entityMethods.d.ts +14 -0
  15. package/build/services/memflow/workflow/{contextMethods.js → entityMethods.js} +11 -11
  16. package/build/services/memflow/workflow/index.d.ts +2 -2
  17. package/build/services/memflow/workflow/index.js +2 -2
  18. package/build/services/store/index.d.ts +1 -1
  19. package/build/services/store/providers/postgres/kvsql.d.ts +1 -1
  20. package/build/services/store/providers/postgres/kvtypes/hash.d.ts +1 -1
  21. package/build/services/store/providers/postgres/kvtypes/hash.js +8 -8
  22. package/build/services/store/providers/postgres/postgres.d.ts +1 -1
  23. package/build/services/store/providers/postgres/postgres.js +3 -2
  24. package/build/services/store/providers/redis/_base.d.ts +1 -1
  25. package/build/services/store/providers/redis/_base.js +1 -1
  26. package/build/types/job.d.ts +7 -0
  27. package/build/types/provider.d.ts +1 -0
  28. package/index.ts +2 -2
  29. package/package.json +4 -3
  30. package/build/services/memflow/workflow/contextMethods.d.ts +0 -14
package/README.md CHANGED
@@ -4,12 +4,12 @@
4
4
 
5
5
  ![beta release](https://img.shields.io/badge/release-beta-blue.svg) ![made with typescript](https://img.shields.io/badge/built%20with-typescript-lightblue.svg)
6
6
 
7
- **HotMesh** is a Temporal-style workflow engine that runs natively on PostgreSQL — with a powerful twist: every workflow maintains a permanent, JSON-backed context that persists independently of the workflow itself.
7
+ **HotMesh** is a Temporal-style workflow engine that runs natively on PostgreSQL — with a powerful twist: every workflow maintains permanent, state that persists independently of the workflow itself.
8
8
 
9
9
  This means:
10
10
 
11
11
  * Any number of lightweight, thread-safe **hook workers** can attach to the same workflow record at any time.
12
- * These hooks can safely **read and incrementally write** to shared state.
12
+ * These hooks can safely **read and write** to shared state.
13
13
  * The result is a **durable execution model** with **evolving memory**, ideal for **human-in-the-loop processes** and **AI agents that learn over time**.
14
14
 
15
15
  ---
@@ -18,7 +18,7 @@ This means:
18
18
 
19
19
  1. 🚀 Quick Start
20
20
  2. 🧠 How Permanent Memory Works
21
- 3. 🔌 Hooks & Context API
21
+ 3. 🔌 Hooks & Entity API
22
22
  4. 🤖 Building Durable AI Agents
23
23
  5. 🔬 Advanced Patterns & Recipes
24
24
  6. 📚 Documentation & Links
@@ -51,9 +51,11 @@ async function main() {
51
51
 
52
52
  // Kick off a workflow
53
53
  const handle = await mf.workflow.start({
54
- workflowName: 'example',
54
+ entity: 'user',
55
+ workflowName: 'userExample',
56
+ workflowId: 'jane@hotmesh.com',
55
57
  args: ['Jane'],
56
- taskQueue: 'contextual'
58
+ taskQueue: 'entityqueue'
57
59
  });
58
60
 
59
61
  console.log('Result:', await handle.result());
@@ -66,17 +68,16 @@ main().catch(console.error);
66
68
 
67
69
  ## 🧠 How Permanent Memory Works
68
70
 
69
- * **Context = persistent JSON record** – each workflow's memory is stored as a JSONB row in your Postgres database
71
+ * **Entity = persistent JSON record** – each workflow's memory is stored as a JSONB row in your Postgres database
70
72
  * **Atomic operations** (`set`, `merge`, `append`, `increment`, `toggle`, `delete`, …)
71
73
  * **Transactional** – every update participates in the workflow/DB transaction
72
74
  * **Time-travel-safe** – full replay compatibility; side-effect detector guarantees determinism
73
75
  * **Hook-friendly** – any worker with the record ID can attach and mutate its slice of the JSON
74
-
75
- * Context data is stored as JSONB; add partial indexes for improved query analysis.
76
+ * **Index-friendly** - entity data is stored as JSONB; add partial indexes for improved query analysis.
76
77
 
77
78
  **Example: Adding a Partial Index for Specific Entity Types**
78
79
  ```sql
79
- -- Create a partial index for 'user' entities with specific context values
80
+ -- Create a partial index for 'user' entities with specific entity values
80
81
  CREATE INDEX idx_user_premium ON your_app.jobs (id)
81
82
  WHERE entity = 'user' AND (context->>'isPremium')::boolean = true;
82
83
  ```
@@ -84,42 +85,44 @@ This index will only be used for queries that match both conditions, making look
84
85
 
85
86
  ---
86
87
 
87
- ## 🔌 Hooks & Context API – Full Example
88
+ ## 🔌 Hooks & Entity API – Full Example
89
+
90
+ HotMesh hooks are powerful because they can be called both internally (from within a workflow) and externally (from outside, even after the workflow completes). This means you can:
91
+
92
+ * Start a workflow that sets up initial state
93
+ * Have the workflow call some hooks internally
94
+ * Let the workflow complete
95
+ * Continue to update the workflow's entity state from the outside via hooks
96
+ * Build long-running processes that evolve over time
97
+
98
+ Here's a complete example showing both internal and external hook usage:
88
99
 
89
100
  ```typescript
90
101
  import { MemFlow } from '@hotmeshio/hotmesh';
91
102
 
92
103
  /* ------------ Main workflow ------------ */
93
- export async function example(name: string): Promise<any> {
94
- //the context method provides transactional, replayable access to shared job state
95
- const ctx = await MemFlow.workflow.context();
104
+ export async function userExample(name: string): Promise<any> {
105
+ //the entity method provides transactional, replayable access to shared job state
106
+ const entity = await MemFlow.workflow.entity();
96
107
 
97
- //create the initial context (even arrays are supported)
98
- await ctx.set({
108
+ //create the initial entity (even arrays are supported)
109
+ await entity.set({
99
110
  user: { name },
100
111
  hooks: {},
101
112
  metrics: { count: 0 }
102
113
  });
103
114
 
104
- // Call two hooks in parallel to updaet the same shared context
105
- const [r1, r2] = await Promise.all([
106
- MemFlow.workflow.execHook({
107
- taskQueue: 'contextual',
108
- workflowName: 'hook1',
109
- args: [name, 'hook1'],
110
- signalId: 'hook1-complete',
111
- }),
112
- MemFlow.workflow.execHook({
113
- taskQueue: 'contextual',
114
- workflowName: 'hook2',
115
- args: [name, 'hook2'],
116
- signalId: 'hook2-complete',
117
- })
118
- ]);
119
-
120
- // merge here (or have the hooks merge in...everyone can access context)
121
- await ctx.merge({ hooks: { r1, r2 } });
122
- await ctx.increment('metrics.count', 2);
115
+ // Call one hook internally
116
+ const result1 = await MemFlow.workflow.execHook({
117
+ taskQueue: 'entityqueue',
118
+ workflowName: 'hook1',
119
+ args: [name, 'hook1'],
120
+ signalId: 'hook1-complete'
121
+ });
122
+
123
+ // merge the result
124
+ await entity.merge({ hooks: { r1: result1 } });
125
+ await entity.increment('metrics.count', 1);
123
126
 
124
127
  return "The main has completed; the db record persists and can be hydrated; hook in from the outside!";
125
128
  }
@@ -131,10 +134,10 @@ export async function hook1(name: string, kind: string): Promise<any> {
131
134
  await MemFlow.workflow.signal('hook1-complete', res);
132
135
  }
133
136
 
134
- /* ------------ Hook 2 (hooks can access shared job context) ------------ */
137
+ /* ------------ Hook 2 (hooks can access shared job entity) ------------ */
135
138
  export async function hook2(name: string, kind: string): Promise<void> {
136
- const ctx = await MemFlow.workflow.context();
137
- await ctx.merge({ user: { lastSeen: new Date().toISOString() } });
139
+ const entity = await MemFlow.workflow.entity();
140
+ await entity.merge({ user: { lastSeen: new Date().toISOString() } });
138
141
  await MemFlow.workflow.signal('hook2-complete', { ok: true });
139
142
  }
140
143
 
@@ -151,17 +154,17 @@ async function startWorker() {
151
154
  });
152
155
 
153
156
  const worker = await mf.worker.create({
154
- taskQueue: 'contextual',
157
+ taskQueue: 'entityqueue',
155
158
  workflow: example
156
159
  });
157
160
 
158
161
  await mf.worker.create({
159
- taskQueue: 'contextual',
162
+ taskQueue: 'entityqueue',
160
163
  workflow: hook1
161
164
  });
162
165
 
163
166
  await mf.worker.create({
164
- taskQueue: 'contextual',
167
+ taskQueue: 'entityqueue',
165
168
  workflow: hook2
166
169
  });
167
170
 
@@ -169,16 +172,51 @@ async function startWorker() {
169
172
  }
170
173
  ```
171
174
 
175
+ ### The Power of External Hooks
176
+
177
+ One of HotMesh's most powerful features is that workflow entities remain accessible even after the main workflow completes. By providing the original workflow ID, any authorized client can:
178
+
179
+ * Hook into existing workflow entities
180
+ * Update state and trigger new processing
181
+ * Build evolving, long-running processes
182
+ * Enable human-in-the-loop workflows
183
+ * Create AI agents that learn over time
184
+
185
+ Here's how to hook into an existing workflow from the outside:
186
+
187
+ ```typescript
188
+ /* ------------ External Hook Example ------------ */
189
+ async function externalHookExample() {
190
+ const client = new MemFlow.Client({
191
+ appId: 'my-app',
192
+ engine: {
193
+ connection: {
194
+ class: Postgres,
195
+ options: { connectionString: process.env.DATABASE_URL }
196
+ }
197
+ }
198
+ });
199
+
200
+ // Start hook2 externally by providing the original workflow ID
201
+ await client.workflow.hook({
202
+ workflowId: 'jane@hotmesh.com', //id of the target workflow
203
+ taskQueue: 'entityqueue',
204
+ workflowName: 'hook2',
205
+ args: [name, 'external-hook']
206
+ });
207
+ }
208
+ ```
209
+
172
210
  ---
173
211
 
174
212
  ## 🤖 Building Durable AI Agents
175
213
 
176
214
  Permanent memory unlocks a straightforward pattern for agentic systems:
177
215
 
178
- 1. **Planner workflow** – sketches a task list, seeds context.
179
- 2. **Tool hooks** – execute individual tasks, feeding intermediate results back into context.
180
- 3. **Reflector hook** – periodically summarises context into long-term memory embeddings.
181
- 4. **Supervisor workflow** – monitors metrics stored in context and decides when to finish.
216
+ 1. **Planner workflow** – sketches a task list, seeds entity state.
217
+ 2. **Tool hooks** – execute individual tasks, feeding intermediate results back into state.
218
+ 3. **Reflector hook** – periodically summarizes state into long-term memory embeddings.
219
+ 4. **Supervisor workflow** – monitors metrics stored in state and decides when to finish.
182
220
 
183
221
  Because every step is durable *and* shares the same knowledge object, agents can pause,
184
222
  restart, scale horizontally, and keep evolving their world-model indefinitely.
package/build/index.d.ts CHANGED
@@ -5,6 +5,7 @@ import { MemFlow } from './services/memflow';
5
5
  import { ClientService as Client } from './services/memflow/client';
6
6
  import { ConnectionService as Connection } from './services/memflow/connection';
7
7
  import { Search } from './services/memflow/search';
8
+ import { Entity } from './services/memflow/entity';
8
9
  import { WorkerService as Worker } from './services/memflow/worker';
9
10
  import { WorkflowService as workflow } from './services/memflow/workflow';
10
11
  import { WorkflowHandleService as WorkflowHandle } from './services/memflow/handle';
@@ -21,5 +22,5 @@ import { RedisConnection as ConnectorIORedis } from './services/connector/provid
21
22
  import { RedisConnection as ConnectorRedis } from './services/connector/providers/redis';
22
23
  import { NatsConnection as ConnectorNATS } from './services/connector/providers/nats';
23
24
  export { Connector, //factory
24
- ConnectorIORedis, ConnectorNATS, ConnectorPostgres, ConnectorRedis, HotMesh, HotMeshConfig, MeshCall, MeshData, MemFlow, MeshOS, Client, Connection, proxyActivities, Search, Worker, workflow, WorkflowHandle, Enums, Errors, Utils, KeyStore, };
25
+ ConnectorIORedis, ConnectorNATS, ConnectorPostgres, ConnectorRedis, HotMesh, HotMeshConfig, MeshCall, MeshData, MemFlow, MeshOS, Client, Connection, proxyActivities, Search, Entity, Worker, workflow, WorkflowHandle, Enums, Errors, Utils, KeyStore, };
25
26
  export * as Types from './types';
package/build/index.js CHANGED
@@ -23,7 +23,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
23
23
  return result;
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.Types = exports.KeyStore = exports.Utils = exports.Errors = exports.Enums = exports.WorkflowHandle = exports.workflow = exports.Worker = exports.Search = exports.proxyActivities = exports.Connection = exports.Client = exports.MeshOS = exports.MemFlow = exports.MeshData = exports.MeshCall = exports.HotMesh = exports.ConnectorRedis = exports.ConnectorPostgres = exports.ConnectorNATS = exports.ConnectorIORedis = exports.Connector = void 0;
26
+ exports.Types = exports.KeyStore = exports.Utils = exports.Errors = exports.Enums = exports.WorkflowHandle = exports.workflow = exports.Worker = exports.Entity = exports.Search = exports.proxyActivities = exports.Connection = exports.Client = exports.MeshOS = exports.MemFlow = exports.MeshData = exports.MeshCall = exports.HotMesh = exports.ConnectorRedis = exports.ConnectorPostgres = exports.ConnectorNATS = exports.ConnectorIORedis = exports.Connector = void 0;
27
27
  const hotmesh_1 = require("./services/hotmesh");
28
28
  Object.defineProperty(exports, "HotMesh", { enumerable: true, get: function () { return hotmesh_1.HotMesh; } });
29
29
  const meshcall_1 = require("./services/meshcall");
@@ -36,6 +36,8 @@ const connection_1 = require("./services/memflow/connection");
36
36
  Object.defineProperty(exports, "Connection", { enumerable: true, get: function () { return connection_1.ConnectionService; } });
37
37
  const search_1 = require("./services/memflow/search");
38
38
  Object.defineProperty(exports, "Search", { enumerable: true, get: function () { return search_1.Search; } });
39
+ const entity_1 = require("./services/memflow/entity");
40
+ Object.defineProperty(exports, "Entity", { enumerable: true, get: function () { return entity_1.Entity; } });
39
41
  const worker_1 = require("./services/memflow/worker");
40
42
  Object.defineProperty(exports, "Worker", { enumerable: true, get: function () { return worker_1.WorkerService; } });
41
43
  const workflow_1 = require("./services/memflow/workflow");
@@ -33,7 +33,8 @@
33
33
  "test:memflow:collision": "NODE_ENV=test jest ./tests/memflow/collision/*.test.ts --detectOpenHandles --forceExit --verbose",
34
34
  "test:memflow:fatal": "NODE_ENV=test jest ./tests/memflow/fatal/*.test.ts --detectOpenHandles --forceExit --verbose",
35
35
  "test:memflow:goodbye": "NODE_ENV=test jest ./tests/memflow/goodbye/*.test.ts --detectOpenHandles --forceExit --verbose",
36
- "test:memflow:context": "NODE_ENV=test jest ./tests/memflow/context/postgres.test.ts --detectOpenHandles --forceExit --verbose",
36
+ "test:memflow:entity": "NODE_ENV=test HMSH_LOGLEVEL=debug jest ./tests/memflow/entity/postgres.test.ts --detectOpenHandles --forceExit --verbose",
37
+ "test:memflow:agent": "NODE_ENV=test HMSH_LOGLEVEL=debug jest ./tests/memflow/agent/postgres.test.ts --detectOpenHandles --forceExit --verbose",
37
38
  "test:memflow:hello": "HMSH_TELEMETRY=debug HMSH_LOGLEVEL=debug HMSH_IS_CLUSTER=true NODE_ENV=test jest ./tests/memflow/helloworld/*.test.ts --detectOpenHandles --forceExit --verbose",
38
39
  "test:memflow:hook": "NODE_ENV=test jest ./tests/memflow/hook/*.test.ts --detectOpenHandles --forceExit --verbose",
39
40
  "test:memflow:interrupt": "NODE_ENV=test jest ./tests/memflow/interrupt/*.test.ts --detectOpenHandles --forceExit --verbose",
@@ -31,7 +31,7 @@ declare class Trigger extends Activity {
31
31
  getJobStatus(): number;
32
32
  resolveJobId(context: Partial<JobState>): string;
33
33
  resolveJobKey(context: Partial<JobState>): string;
34
- setStateNX(status?: number): Promise<void>;
34
+ setStateNX(status?: number, entity?: string): Promise<void>;
35
35
  setStats(transaction?: ProviderTransaction): Promise<void>;
36
36
  }
37
37
  export { Trigger };
@@ -27,7 +27,7 @@ class Trigger extends activity_1.Activity {
27
27
  this.mapJobData();
28
28
  this.adjacencyList = await this.filterAdjacent();
29
29
  const initialStatus = this.initStatus(options, this.adjacencyList.length);
30
- await this.setStateNX(initialStatus);
30
+ await this.setStateNX(initialStatus, options?.entity);
31
31
  await this.setStatus(initialStatus);
32
32
  this.bindSearchData(options);
33
33
  this.bindMarkerData(options);
@@ -228,9 +228,9 @@ class Trigger extends activity_1.Activity {
228
228
  const jobKey = this.config.stats?.key;
229
229
  return jobKey ? pipe_1.Pipe.resolve(jobKey, context) : '';
230
230
  }
231
- async setStateNX(status) {
231
+ async setStateNX(status, entity) {
232
232
  const jobId = this.context.metadata.jid;
233
- if (!await this.store.setStateNX(jobId, this.engine.appId, status)) {
233
+ if (!await this.store.setStateNX(jobId, this.engine.appId, status, entity)) {
234
234
  throw new errors_1.DuplicateJobError(jobId);
235
235
  }
236
236
  }
@@ -125,7 +125,7 @@ class ClientService {
125
125
  */
126
126
  start: async (options) => {
127
127
  const taskQueueName = options.taskQueue ?? options.entity;
128
- const workflowName = options.entity ?? options.workflowName;
128
+ const workflowName = options.taskQueue ? options.workflowName : (options.entity ?? options.workflowName);
129
129
  const trc = options.workflowTrace;
130
130
  const spn = options.workflowSpan;
131
131
  //hotmesh `topic` is equivalent to `queue+workflowname` pattern in other systems
@@ -151,6 +151,7 @@ class ClientService {
151
151
  search: options?.search?.data,
152
152
  marker: options?.marker,
153
153
  pending: options?.pending,
154
+ entity: options?.entity,
154
155
  });
155
156
  return new handle_1.WorkflowHandleService(hotMeshClient, workflowTopic, jobId);
156
157
  },
@@ -1,26 +1,26 @@
1
1
  import { HotMesh } from '../hotmesh';
2
2
  import { SearchService } from '../search';
3
3
  /**
4
- * The Context module provides methods for reading and writing
5
- * JSONB data to a workflow's context. The instance methods
4
+ * The Entity module provides methods for reading and writing
5
+ * JSONB data to a workflow's entity. The instance methods
6
6
  * exposed by this class are available for use from within
7
7
  * a running workflow.
8
8
  *
9
9
  * @example
10
10
  * ```typescript
11
- * //contextWorkflow.ts
11
+ * //entityWorkflow.ts
12
12
  * import { workflow } from '@hotmeshio/hotmesh';
13
13
  *
14
- * export async function contextExample(): Promise<void> {
15
- * const context = await workflow.context();
16
- * await context.set({ user: { id: 123 } });
17
- * await context.merge({ user: { name: "John" } });
18
- * const user = await context.get("user");
14
+ * export async function entityExample(): Promise<void> {
15
+ * const entity = await workflow.entity();
16
+ * await entity.set({ user: { id: 123 } });
17
+ * await entity.merge({ user: { name: "John" } });
18
+ * const user = await entity.get("user");
19
19
  * // user = { id: 123, name: "John" }
20
20
  * }
21
21
  * ```
22
22
  */
23
- export declare class Context {
23
+ export declare class Entity {
24
24
  /**
25
25
  * @private
26
26
  */
@@ -56,84 +56,84 @@ export declare class Context {
56
56
  */
57
57
  getSearchSessionGuid(): string;
58
58
  /**
59
- * Sets the entire context object. This replaces any existing context.
59
+ * Sets the entire entity object. This replaces any existing entity.
60
60
  *
61
61
  * @example
62
- * const context = await workflow.context();
63
- * await context.set({ user: { id: 123, name: "John" } });
62
+ * const entity = await workflow.entity();
63
+ * await entity.set({ user: { id: 123, name: "John" } });
64
64
  */
65
65
  set(value: any): Promise<any>;
66
66
  /**
67
- * Deep merges the provided object with the existing context
67
+ * Deep merges the provided object with the existing entity
68
68
  *
69
69
  * @example
70
- * const context = await workflow.context();
71
- * await context.merge({ user: { email: "john@example.com" } });
70
+ * const entity = await workflow.entity();
71
+ * await entity.merge({ user: { email: "john@example.com" } });
72
72
  */
73
73
  merge<T>(value: T): Promise<T>;
74
74
  /**
75
- * Gets a value from the context by path
75
+ * Gets a value from the entity by path
76
76
  *
77
77
  * @example
78
- * const context = await workflow.context();
79
- * const user = await context.get("user");
80
- * const email = await context.get("user.email");
78
+ * const entity = await workflow.entity();
79
+ * const user = await entity.get("user");
80
+ * const email = await entity.get("user.email");
81
81
  */
82
82
  get(path?: string): Promise<any>;
83
83
  /**
84
- * Deletes a value from the context by path
84
+ * Deletes a value from the entity by path
85
85
  *
86
86
  * @example
87
- * const context = await workflow.context();
88
- * await context.delete("user.email");
87
+ * const entity = await workflow.entity();
88
+ * await entity.delete("user.email");
89
89
  */
90
90
  delete(path: string): Promise<any>;
91
91
  /**
92
92
  * Appends a value to an array at the specified path
93
93
  *
94
94
  * @example
95
- * const context = await workflow.context();
96
- * await context.append("items", { id: 1, name: "New Item" });
95
+ * const entity = await workflow.entity();
96
+ * await entity.append("items", { id: 1, name: "New Item" });
97
97
  */
98
98
  append(path: string, value: any): Promise<any[]>;
99
99
  /**
100
100
  * Prepends a value to an array at the specified path
101
101
  *
102
102
  * @example
103
- * const context = await workflow.context();
104
- * await context.prepend("items", { id: 0, name: "First Item" });
103
+ * const entity = await workflow.entity();
104
+ * await entity.prepend("items", { id: 0, name: "First Item" });
105
105
  */
106
106
  prepend(path: string, value: any): Promise<any[]>;
107
107
  /**
108
108
  * Removes an item from an array at the specified path and index
109
109
  *
110
110
  * @example
111
- * const context = await workflow.context();
112
- * await context.remove("items", 0); // Remove first item
111
+ * const entity = await workflow.entity();
112
+ * await entity.remove("items", 0); // Remove first item
113
113
  */
114
114
  remove(path: string, index: number): Promise<any[]>;
115
115
  /**
116
116
  * Increments a numeric value at the specified path
117
117
  *
118
118
  * @example
119
- * const context = await workflow.context();
120
- * await context.increment("counter", 5);
119
+ * const entity = await workflow.entity();
120
+ * await entity.increment("counter", 5);
121
121
  */
122
122
  increment(path: string, value?: number): Promise<number>;
123
123
  /**
124
124
  * Toggles a boolean value at the specified path
125
125
  *
126
126
  * @example
127
- * const context = await workflow.context();
128
- * await context.toggle("settings.enabled");
127
+ * const entity = await workflow.entity();
128
+ * await entity.toggle("settings.enabled");
129
129
  */
130
130
  toggle(path: string): Promise<boolean>;
131
131
  /**
132
132
  * Sets a value at the specified path only if it doesn't already exist
133
133
  *
134
134
  * @example
135
- * const context = await workflow.context();
136
- * await context.setIfNotExists("user.id", 123);
135
+ * const entity = await workflow.entity();
136
+ * await entity.setIfNotExists("user.id", 123);
137
137
  */
138
138
  setIfNotExists(path: string, value: any): Promise<any>;
139
139
  /**
@@ -1,29 +1,29 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Context = void 0;
3
+ exports.Entity = void 0;
4
4
  const key_1 = require("../../modules/key");
5
5
  const storage_1 = require("../../modules/storage");
6
6
  /**
7
- * The Context module provides methods for reading and writing
8
- * JSONB data to a workflow's context. The instance methods
7
+ * The Entity module provides methods for reading and writing
8
+ * JSONB data to a workflow's entity. The instance methods
9
9
  * exposed by this class are available for use from within
10
10
  * a running workflow.
11
11
  *
12
12
  * @example
13
13
  * ```typescript
14
- * //contextWorkflow.ts
14
+ * //entityWorkflow.ts
15
15
  * import { workflow } from '@hotmeshio/hotmesh';
16
16
  *
17
- * export async function contextExample(): Promise<void> {
18
- * const context = await workflow.context();
19
- * await context.set({ user: { id: 123 } });
20
- * await context.merge({ user: { name: "John" } });
21
- * const user = await context.get("user");
17
+ * export async function entityExample(): Promise<void> {
18
+ * const entity = await workflow.entity();
19
+ * await entity.set({ user: { id: 123 } });
20
+ * await entity.merge({ user: { name: "John" } });
21
+ * const user = await entity.get("user");
22
22
  * // user = { id: 123, name: "John" }
23
23
  * }
24
24
  * ```
25
25
  */
26
- class Context {
26
+ class Entity {
27
27
  /**
28
28
  * @private
29
29
  */
@@ -53,11 +53,11 @@ class Context {
53
53
  return `${this.searchSessionId}-${this.searchSessionIndex++}-`;
54
54
  }
55
55
  /**
56
- * Sets the entire context object. This replaces any existing context.
56
+ * Sets the entire entity object. This replaces any existing entity.
57
57
  *
58
58
  * @example
59
- * const context = await workflow.context();
60
- * await context.set({ user: { id: 123, name: "John" } });
59
+ * const entity = await workflow.entity();
60
+ * await entity.set({ user: { id: 123, name: "John" } });
61
61
  */
62
62
  async set(value) {
63
63
  const ssGuid = this.getSearchSessionGuid();
@@ -66,7 +66,7 @@ class Context {
66
66
  if (ssGuid in replay) {
67
67
  return JSON.parse(replay[ssGuid]);
68
68
  }
69
- // Use single transactional call to update context and store replay value
69
+ // Use single transactional call to update entity and store replay value
70
70
  const result = await this.search.updateContext(this.jobId, {
71
71
  '@context': JSON.stringify(value),
72
72
  [ssGuid]: '', // Pass replay ID to hash module for transactional replay storage
@@ -74,11 +74,11 @@ class Context {
74
74
  return result || value;
75
75
  }
76
76
  /**
77
- * Deep merges the provided object with the existing context
77
+ * Deep merges the provided object with the existing entity
78
78
  *
79
79
  * @example
80
- * const context = await workflow.context();
81
- * await context.merge({ user: { email: "john@example.com" } });
80
+ * const entity = await workflow.entity();
81
+ * await entity.merge({ user: { email: "john@example.com" } });
82
82
  */
83
83
  async merge(value) {
84
84
  const ssGuid = this.getSearchSessionGuid();
@@ -95,29 +95,29 @@ class Context {
95
95
  return newContext;
96
96
  }
97
97
  /**
98
- * Gets a value from the context by path
98
+ * Gets a value from the entity by path
99
99
  *
100
100
  * @example
101
- * const context = await workflow.context();
102
- * const user = await context.get("user");
103
- * const email = await context.get("user.email");
101
+ * const entity = await workflow.entity();
102
+ * const user = await entity.get("user");
103
+ * const email = await entity.get("user.email");
104
104
  */
105
105
  async get(path) {
106
106
  const ssGuid = this.getSearchSessionGuid();
107
107
  const store = storage_1.asyncLocalStorage.getStore();
108
108
  const replay = store?.get('replay') ?? {};
109
109
  if (ssGuid in replay) {
110
- // Replay cache stores the already-extracted value, not full context
110
+ // Replay cache stores the already-extracted value, not full entity
111
111
  return JSON.parse(replay[ssGuid]);
112
112
  }
113
113
  let value;
114
114
  if (!path) {
115
- // No path - fetch entire context with replay storage
115
+ // No path - fetch entire entity with replay storage
116
116
  const result = await this.search.updateContext(this.jobId, {
117
117
  '@context:get': '',
118
118
  [ssGuid]: '', // Pass replay ID to hash module
119
119
  });
120
- // setFields returns the actual context value for @context:get operations
120
+ // setFields returns the actual entity value for @context:get operations
121
121
  value = result || {};
122
122
  }
123
123
  else {
@@ -132,11 +132,11 @@ class Context {
132
132
  return value;
133
133
  }
134
134
  /**
135
- * Deletes a value from the context by path
135
+ * Deletes a value from the entity by path
136
136
  *
137
137
  * @example
138
- * const context = await workflow.context();
139
- * await context.delete("user.email");
138
+ * const entity = await workflow.entity();
139
+ * await entity.delete("user.email");
140
140
  */
141
141
  async delete(path) {
142
142
  const ssGuid = this.getSearchSessionGuid();
@@ -156,8 +156,8 @@ class Context {
156
156
  * Appends a value to an array at the specified path
157
157
  *
158
158
  * @example
159
- * const context = await workflow.context();
160
- * await context.append("items", { id: 1, name: "New Item" });
159
+ * const entity = await workflow.entity();
160
+ * await entity.append("items", { id: 1, name: "New Item" });
161
161
  */
162
162
  async append(path, value) {
163
163
  const ssGuid = this.getSearchSessionGuid();
@@ -177,8 +177,8 @@ class Context {
177
177
  * Prepends a value to an array at the specified path
178
178
  *
179
179
  * @example
180
- * const context = await workflow.context();
181
- * await context.prepend("items", { id: 0, name: "First Item" });
180
+ * const entity = await workflow.entity();
181
+ * await entity.prepend("items", { id: 0, name: "First Item" });
182
182
  */
183
183
  async prepend(path, value) {
184
184
  const ssGuid = this.getSearchSessionGuid();
@@ -198,8 +198,8 @@ class Context {
198
198
  * Removes an item from an array at the specified path and index
199
199
  *
200
200
  * @example
201
- * const context = await workflow.context();
202
- * await context.remove("items", 0); // Remove first item
201
+ * const entity = await workflow.entity();
202
+ * await entity.remove("items", 0); // Remove first item
203
203
  */
204
204
  async remove(path, index) {
205
205
  const ssGuid = this.getSearchSessionGuid();
@@ -219,8 +219,8 @@ class Context {
219
219
  * Increments a numeric value at the specified path
220
220
  *
221
221
  * @example
222
- * const context = await workflow.context();
223
- * await context.increment("counter", 5);
222
+ * const entity = await workflow.entity();
223
+ * await entity.increment("counter", 5);
224
224
  */
225
225
  async increment(path, value = 1) {
226
226
  const ssGuid = this.getSearchSessionGuid();
@@ -240,8 +240,8 @@ class Context {
240
240
  * Toggles a boolean value at the specified path
241
241
  *
242
242
  * @example
243
- * const context = await workflow.context();
244
- * await context.toggle("settings.enabled");
243
+ * const entity = await workflow.entity();
244
+ * await entity.toggle("settings.enabled");
245
245
  */
246
246
  async toggle(path) {
247
247
  const ssGuid = this.getSearchSessionGuid();
@@ -261,8 +261,8 @@ class Context {
261
261
  * Sets a value at the specified path only if it doesn't already exist
262
262
  *
263
263
  * @example
264
- * const context = await workflow.context();
265
- * await context.setIfNotExists("user.id", 123);
264
+ * const entity = await workflow.entity();
265
+ * await entity.setIfNotExists("user.id", 123);
266
266
  */
267
267
  async setIfNotExists(path, value) {
268
268
  const ssGuid = this.getSearchSessionGuid();
@@ -296,4 +296,4 @@ class Context {
296
296
  return output;
297
297
  }
298
298
  }
299
- exports.Context = Context;
299
+ exports.Entity = Entity;
@@ -2,7 +2,7 @@ import { ContextType } from '../../types/memflow';
2
2
  import { ClientService } from './client';
3
3
  import { ConnectionService } from './connection';
4
4
  import { Search } from './search';
5
- import { Context } from './context';
5
+ import { Entity } from './entity';
6
6
  import { WorkerService } from './worker';
7
7
  import { WorkflowService } from './workflow';
8
8
  import { WorkflowHandleService } from './handle';
@@ -86,7 +86,7 @@ declare class MemFlowClass {
86
86
  /**
87
87
  * @private
88
88
  */
89
- static Context: typeof Context;
89
+ static Entity: typeof Entity;
90
90
  /**
91
91
  * The Handle provides methods to interact with a running
92
92
  * workflow. This includes exporting the workflow, sending signals, and
@@ -5,7 +5,7 @@ const hotmesh_1 = require("../hotmesh");
5
5
  const client_1 = require("./client");
6
6
  const connection_1 = require("./connection");
7
7
  const search_1 = require("./search");
8
- const context_1 = require("./context");
8
+ const entity_1 = require("./entity");
9
9
  const worker_1 = require("./worker");
10
10
  const workflow_1 = require("./workflow");
11
11
  const handle_1 = require("./handle");
@@ -100,7 +100,7 @@ MemFlowClass.Search = search_1.Search;
100
100
  /**
101
101
  * @private
102
102
  */
103
- MemFlowClass.Context = context_1.Context;
103
+ MemFlowClass.Entity = entity_1.Entity;
104
104
  /**
105
105
  * The Handle provides methods to interact with a running
106
106
  * workflow. This includes exporting the workflow, sending signals, and
@@ -15,6 +15,6 @@ import { QuorumMessage } from '../../../types';
15
15
  import { UserMessage } from '../../../types/quorum';
16
16
  import { Search } from '../search';
17
17
  import { WorkerService } from '../worker';
18
- import { Context } from '../context';
18
+ import { Entity } from '../entity';
19
19
  import { ExecHookOptions } from './execHook';
20
- export { MemFlowChildError, MemFlowFatalError, MemFlowMaxedError, MemFlowProxyError, MemFlowRetryError, MemFlowSleepError, MemFlowTimeoutError, MemFlowWaitForError, KeyService, KeyType, asyncLocalStorage, deterministicRandom, guid, s, sleepImmediate, HotMesh, SerializerService, ActivityConfig, ChildResponseType, HookOptions, ExecHookOptions, ProxyResponseType, ProxyType, WorkflowContext, WorkflowOptions, JobInterruptOptions, StreamCode, StreamStatus, StringAnyType, StringScalarType, StringStringType, HMSH_CODE_MEMFLOW_CHILD, HMSH_CODE_MEMFLOW_FATAL, HMSH_CODE_MEMFLOW_MAXED, HMSH_CODE_MEMFLOW_PROXY, HMSH_CODE_MEMFLOW_SLEEP, HMSH_CODE_MEMFLOW_TIMEOUT, HMSH_CODE_MEMFLOW_WAIT, HMSH_MEMFLOW_EXP_BACKOFF, HMSH_MEMFLOW_MAX_ATTEMPTS, HMSH_MEMFLOW_MAX_INTERVAL, MemFlowChildErrorType, MemFlowProxyErrorType, TelemetryService, QuorumMessage, UserMessage, Search, WorkerService, Context, };
20
+ export { MemFlowChildError, MemFlowFatalError, MemFlowMaxedError, MemFlowProxyError, MemFlowRetryError, MemFlowSleepError, MemFlowTimeoutError, MemFlowWaitForError, KeyService, KeyType, asyncLocalStorage, deterministicRandom, guid, s, sleepImmediate, HotMesh, SerializerService, ActivityConfig, ChildResponseType, HookOptions, ExecHookOptions, ProxyResponseType, ProxyType, WorkflowContext, WorkflowOptions, JobInterruptOptions, StreamCode, StreamStatus, StringAnyType, StringScalarType, StringStringType, HMSH_CODE_MEMFLOW_CHILD, HMSH_CODE_MEMFLOW_FATAL, HMSH_CODE_MEMFLOW_MAXED, HMSH_CODE_MEMFLOW_PROXY, HMSH_CODE_MEMFLOW_SLEEP, HMSH_CODE_MEMFLOW_TIMEOUT, HMSH_CODE_MEMFLOW_WAIT, HMSH_MEMFLOW_EXP_BACKOFF, HMSH_MEMFLOW_MAX_ATTEMPTS, HMSH_MEMFLOW_MAX_INTERVAL, MemFlowChildErrorType, MemFlowProxyErrorType, TelemetryService, QuorumMessage, UserMessage, Search, WorkerService, Entity, };
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Context = exports.WorkerService = exports.Search = exports.TelemetryService = exports.HMSH_MEMFLOW_MAX_INTERVAL = exports.HMSH_MEMFLOW_MAX_ATTEMPTS = exports.HMSH_MEMFLOW_EXP_BACKOFF = exports.HMSH_CODE_MEMFLOW_WAIT = exports.HMSH_CODE_MEMFLOW_TIMEOUT = exports.HMSH_CODE_MEMFLOW_SLEEP = exports.HMSH_CODE_MEMFLOW_PROXY = exports.HMSH_CODE_MEMFLOW_MAXED = exports.HMSH_CODE_MEMFLOW_FATAL = exports.HMSH_CODE_MEMFLOW_CHILD = exports.StreamStatus = exports.SerializerService = exports.HotMesh = exports.sleepImmediate = exports.s = exports.guid = exports.deterministicRandom = exports.asyncLocalStorage = exports.KeyType = exports.KeyService = exports.MemFlowWaitForError = exports.MemFlowTimeoutError = exports.MemFlowSleepError = exports.MemFlowRetryError = exports.MemFlowProxyError = exports.MemFlowMaxedError = exports.MemFlowFatalError = exports.MemFlowChildError = void 0;
3
+ exports.Entity = exports.WorkerService = exports.Search = exports.TelemetryService = exports.HMSH_MEMFLOW_MAX_INTERVAL = exports.HMSH_MEMFLOW_MAX_ATTEMPTS = exports.HMSH_MEMFLOW_EXP_BACKOFF = exports.HMSH_CODE_MEMFLOW_WAIT = exports.HMSH_CODE_MEMFLOW_TIMEOUT = exports.HMSH_CODE_MEMFLOW_SLEEP = exports.HMSH_CODE_MEMFLOW_PROXY = exports.HMSH_CODE_MEMFLOW_MAXED = exports.HMSH_CODE_MEMFLOW_FATAL = exports.HMSH_CODE_MEMFLOW_CHILD = exports.StreamStatus = exports.SerializerService = exports.HotMesh = exports.sleepImmediate = exports.s = exports.guid = exports.deterministicRandom = exports.asyncLocalStorage = exports.KeyType = exports.KeyService = exports.MemFlowWaitForError = exports.MemFlowTimeoutError = exports.MemFlowSleepError = exports.MemFlowRetryError = exports.MemFlowProxyError = exports.MemFlowMaxedError = exports.MemFlowFatalError = exports.MemFlowChildError = void 0;
4
4
  const errors_1 = require("../../../modules/errors");
5
5
  Object.defineProperty(exports, "MemFlowChildError", { enumerable: true, get: function () { return errors_1.MemFlowChildError; } });
6
6
  Object.defineProperty(exports, "MemFlowFatalError", { enumerable: true, get: function () { return errors_1.MemFlowFatalError; } });
@@ -43,5 +43,5 @@ const search_1 = require("../search");
43
43
  Object.defineProperty(exports, "Search", { enumerable: true, get: function () { return search_1.Search; } });
44
44
  const worker_1 = require("../worker");
45
45
  Object.defineProperty(exports, "WorkerService", { enumerable: true, get: function () { return worker_1.WorkerService; } });
46
- const context_1 = require("../context");
47
- Object.defineProperty(exports, "Context", { enumerable: true, get: function () { return context_1.Context; } });
46
+ const entity_1 = require("../entity");
47
+ Object.defineProperty(exports, "Entity", { enumerable: true, get: function () { return entity_1.Entity; } });
@@ -0,0 +1,14 @@
1
+ import { Entity } from './common';
2
+ /**
3
+ * Returns an entity session handle for interacting with the workflow's JSONB entity storage.
4
+ * @returns {Promise<Entity>} An entity session for workflow data.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * const entity = await workflow.entity();
9
+ * await entity.set({ user: { id: 123 } });
10
+ * await entity.merge({ user: { name: "John" } });
11
+ * const user = await entity.get("user");
12
+ * ```
13
+ */
14
+ export declare function entity(): Promise<Entity>;
@@ -1,20 +1,20 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.context = void 0;
3
+ exports.entity = void 0;
4
4
  const common_1 = require("./common");
5
5
  /**
6
- * Returns a context session handle for interacting with the workflow's JSONB context storage.
7
- * @returns {Promise<Context>} A context session for workflow data.
6
+ * Returns an entity session handle for interacting with the workflow's JSONB entity storage.
7
+ * @returns {Promise<Entity>} An entity session for workflow data.
8
8
  *
9
9
  * @example
10
10
  * ```typescript
11
- * const context = await workflow.context();
12
- * await context.set({ user: { id: 123 } });
13
- * await context.merge({ user: { name: "John" } });
14
- * const user = await context.get("user");
11
+ * const entity = await workflow.entity();
12
+ * await entity.set({ user: { id: 123 } });
13
+ * await entity.merge({ user: { name: "John" } });
14
+ * const user = await entity.get("user");
15
15
  * ```
16
16
  */
17
- async function context() {
17
+ async function entity() {
18
18
  const store = common_1.asyncLocalStorage.getStore();
19
19
  const workflowId = store.get('workflowId');
20
20
  const workflowDimension = store.get('workflowDimension') ?? '';
@@ -27,7 +27,7 @@ async function context() {
27
27
  connection,
28
28
  namespace,
29
29
  });
30
- const contextSessionId = `-context${workflowDimension}-${execIndex}`;
31
- return new common_1.Context(workflowId, hotMeshClient, contextSessionId);
30
+ const entitySessionId = `-entity${workflowDimension}-${execIndex}`;
31
+ return new common_1.Entity(workflowId, hotMeshClient, entitySessionId);
32
32
  }
33
- exports.context = context;
33
+ exports.entity = entity;
@@ -16,7 +16,7 @@ import { all } from './all';
16
16
  import { sleepFor } from './sleepFor';
17
17
  import { waitFor } from './waitFor';
18
18
  import { HotMesh } from './common';
19
- import { context } from './contextMethods';
19
+ import { entity } from './entityMethods';
20
20
  /**
21
21
  * The WorkflowService class provides a set of static methods to be used within a workflow function.
22
22
  * These methods ensure deterministic replay, persistence of state, and error handling across
@@ -58,7 +58,7 @@ export declare class WorkflowService {
58
58
  static execHook: typeof execHook;
59
59
  static proxyActivities: typeof proxyActivities;
60
60
  static search: typeof search;
61
- static context: typeof context;
61
+ static entity: typeof entity;
62
62
  static random: typeof random;
63
63
  static signal: typeof signal;
64
64
  static hook: typeof hook;
@@ -19,7 +19,7 @@ const all_1 = require("./all");
19
19
  const sleepFor_1 = require("./sleepFor");
20
20
  const waitFor_1 = require("./waitFor");
21
21
  const common_1 = require("./common");
22
- const contextMethods_1 = require("./contextMethods");
22
+ const entityMethods_1 = require("./entityMethods");
23
23
  /**
24
24
  * The WorkflowService class provides a set of static methods to be used within a workflow function.
25
25
  * These methods ensure deterministic replay, persistence of state, and error handling across
@@ -76,7 +76,7 @@ WorkflowService.startChild = execChild_1.startChild;
76
76
  WorkflowService.execHook = execHook_1.execHook;
77
77
  WorkflowService.proxyActivities = proxyActivities_1.proxyActivities;
78
78
  WorkflowService.search = searchMethods_1.search;
79
- WorkflowService.context = contextMethods_1.context;
79
+ WorkflowService.entity = entityMethods_1.entity;
80
80
  WorkflowService.random = random_1.random;
81
81
  WorkflowService.signal = signal_1.signal;
82
82
  WorkflowService.hook = hook_1.hook;
@@ -40,7 +40,7 @@ declare abstract class StoreService<Provider extends ProviderClient, Transaction
40
40
  abstract getJobIds(indexKeys: string[], idRange: [number, number]): Promise<IdsData>;
41
41
  abstract setStatus(collationKeyStatus: number, jobId: string, appId: string, transaction?: TransactionProvider): Promise<any>;
42
42
  abstract getStatus(jobId: string, appId: string): Promise<number>;
43
- abstract setStateNX(jobId: string, appId: string, status?: number): Promise<boolean>;
43
+ abstract setStateNX(jobId: string, appId: string, status?: number, entity?: string): Promise<boolean>;
44
44
  abstract setState(state: StringAnyType, status: number | null, jobId: string, symbolNames: string[], dIds: StringStringType, transaction?: TransactionProvider): Promise<string>;
45
45
  abstract getQueryState(jobId: string, fields: string[]): Promise<StringAnyType>;
46
46
  abstract getState(jobId: string, consumes: Consumes, dIds: StringStringType): Promise<[StringAnyType, number] | undefined>;
@@ -52,7 +52,7 @@ export declare class KVSQL {
52
52
  sql: string;
53
53
  params: any[];
54
54
  };
55
- hsetnx: (key: string, field: string, value: string, multi?: ProviderTransaction) => Promise<number>;
55
+ hsetnx: (key: string, field: string, value: string, multi?: ProviderTransaction, entity?: string) => Promise<number>;
56
56
  hget: (key: string, field: string, multi?: ProviderTransaction) => Promise<string>;
57
57
  _hget: (key: string, field: string) => {
58
58
  sql: string;
@@ -2,7 +2,7 @@ import { PostgresJobEnumType } from '../../../../../types/postgres';
2
2
  import { HScanResult, HSetOptions, ProviderTransaction } from '../../../../../types/provider';
3
3
  import type { KVSQL } from '../kvsql';
4
4
  export declare const hashModule: (context: KVSQL) => {
5
- hsetnx(key: string, field: string, value: string, multi?: ProviderTransaction): Promise<number>;
5
+ hsetnx(key: string, field: string, value: string, multi?: ProviderTransaction, entity?: string): Promise<number>;
6
6
  hset(key: string, fields: Record<string, string>, options?: HSetOptions, multi?: ProviderTransaction): Promise<number | any>;
7
7
  /**
8
8
  * Derives the enumerated `type` value based on the field name when
@@ -2,8 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.hashModule = void 0;
4
4
  const hashModule = (context) => ({
5
- async hsetnx(key, field, value, multi) {
6
- const { sql, params } = this._hset(key, { [field]: value }, { nx: true });
5
+ async hsetnx(key, field, value, multi, entity) {
6
+ const { sql, params } = this._hset(key, { [field]: value }, { nx: true, entity });
7
7
  if (multi) {
8
8
  multi.addCommand(sql, params, 'number');
9
9
  return Promise.resolve(0);
@@ -97,25 +97,25 @@ const hashModule = (context) => ({
97
97
  if (options?.nx) {
98
98
  // Use WHERE NOT EXISTS to enforce nx
99
99
  sql = `
100
- INSERT INTO ${targetTable} (id, key, status)
101
- SELECT gen_random_uuid(), $1, $2
100
+ INSERT INTO ${targetTable} (id, key, status, entity)
101
+ SELECT gen_random_uuid(), $1, $2, $3
102
102
  WHERE NOT EXISTS (
103
103
  SELECT 1 FROM ${targetTable}
104
104
  WHERE key = $1 AND is_live
105
105
  )
106
106
  RETURNING 1 as count
107
107
  `;
108
- params.push(key, fields[':']);
108
+ params.push(key, fields[':'], options?.entity ?? null);
109
109
  }
110
110
  else {
111
111
  // Update existing job or insert new one
112
112
  sql = `
113
- INSERT INTO ${targetTable} (id, key, status)
114
- VALUES (gen_random_uuid(), $1, $2)
113
+ INSERT INTO ${targetTable} (id, key, status, entity)
114
+ VALUES (gen_random_uuid(), $1, $2, $3)
115
115
  ON CONFLICT (key) WHERE is_live DO UPDATE SET status = EXCLUDED.status
116
116
  RETURNING 1 as count
117
117
  `;
118
- params.push(key, fields[':']);
118
+ params.push(key, fields[':'], options?.entity ?? null);
119
119
  }
120
120
  }
121
121
  else if (isJobsTable && '@context' in fields) {
@@ -83,7 +83,7 @@ declare class PostgresStoreService extends StoreService<ProviderClient, Provider
83
83
  * `purposeful re-entry`.
84
84
  */
85
85
  collateSynthetic(jobId: string, guid: string, amount: number, transaction?: ProviderTransaction): Promise<number>;
86
- setStateNX(jobId: string, appId: string, status?: number): Promise<boolean>;
86
+ setStateNX(jobId: string, appId: string, status?: number, entity?: string): Promise<boolean>;
87
87
  getSchema(activityId: string, appVersion: AppVID): Promise<ActivityType>;
88
88
  getSchemas(appVersion: AppVID): Promise<Record<string, ActivityType>>;
89
89
  setSchemas(schemas: Record<string, ActivityType>, appVersion: AppVID): Promise<any>;
@@ -567,9 +567,10 @@ class PostgresStoreService extends __1.StoreService {
567
567
  });
568
568
  return await this.kvsql(transaction).hincrbyfloat(jobKey, guid, amount);
569
569
  }
570
- async setStateNX(jobId, appId, status) {
570
+ async setStateNX(jobId, appId, status, entity) {
571
571
  const hashKey = this.mintKey(key_1.KeyType.JOB_STATE, { appId, jobId });
572
- const result = await this.kvsql().hsetnx(hashKey, ':', status?.toString() ?? '1');
572
+ const result = await this.kvsql().hsetnx(hashKey, ':', status?.toString() ?? '1', undefined, //multi
573
+ entity);
573
574
  return this.isSuccessful(result);
574
575
  }
575
576
  async getSchema(activityId, appVersion) {
@@ -75,7 +75,7 @@ declare abstract class RedisStoreBase<ClientProvider extends ProviderClient, Tra
75
75
  * `purposeful re-entry`.
76
76
  */
77
77
  collateSynthetic(jobId: string, guid: string, amount: number, transaction?: TransactionProvider): Promise<number>;
78
- setStateNX(jobId: string, appId: string, status?: number): Promise<boolean>;
78
+ setStateNX(jobId: string, appId: string, status?: number, entity?: string): Promise<boolean>;
79
79
  getSchema(activityId: string, appVersion: AppVID): Promise<ActivityType>;
80
80
  getSchemas(appVersion: AppVID): Promise<Record<string, ActivityType>>;
81
81
  setSchemas(schemas: Record<string, ActivityType>, appVersion: AppVID): Promise<any>;
@@ -539,7 +539,7 @@ class RedisStoreBase extends __1.StoreService {
539
539
  });
540
540
  return await (transaction || this.storeClient)[this.commands.hincrbyfloat](jobKey, guid, amount.toString());
541
541
  }
542
- async setStateNX(jobId, appId, status) {
542
+ async setStateNX(jobId, appId, status, entity) {
543
543
  const hashKey = this.mintKey(key_1.KeyType.JOB_STATE, { appId, jobId });
544
544
  const result = await this.storeClient[this.commands.hsetnx](hashKey, ':', status?.toString() ?? '1');
545
545
  return this.isSuccessful(result);
@@ -95,6 +95,13 @@ type ExtensionType = {
95
95
  * will be added after the job is resumed if relevant.
96
96
  */
97
97
  pending?: number;
98
+ /**
99
+ * Entity to organize the job in the database by type.
100
+ * This is useful for querying and filtering jobs by type
101
+ * as the <your-app-id>.jobs table is organized by entity.
102
+ * Add partial indexes to the entity column to speed up queries.
103
+ */
104
+ entity?: string;
98
105
  };
99
106
  /**
100
107
  * job_status semaphore
@@ -84,6 +84,7 @@ export interface SetOptions {
84
84
  export interface HSetOptions {
85
85
  nx?: boolean;
86
86
  ex?: number;
87
+ entity?: string;
87
88
  }
88
89
  export interface ZAddOptions {
89
90
  nx?: boolean;
package/index.ts CHANGED
@@ -5,6 +5,7 @@ import { MemFlow } from './services/memflow';
5
5
  import { ClientService as Client } from './services/memflow/client';
6
6
  import { ConnectionService as Connection } from './services/memflow/connection';
7
7
  import { Search } from './services/memflow/search';
8
+ import { Entity } from './services/memflow/entity';
8
9
  import { WorkerService as Worker } from './services/memflow/worker';
9
10
  import { WorkflowService as workflow } from './services/memflow/workflow';
10
11
  import { WorkflowHandleService as WorkflowHandle } from './services/memflow/handle';
@@ -21,8 +22,6 @@ import { RedisConnection as ConnectorIORedis } from './services/connector/provid
21
22
  import { RedisConnection as ConnectorRedis } from './services/connector/providers/redis';
22
23
  import { NatsConnection as ConnectorNATS } from './services/connector/providers/nats';
23
24
 
24
- //const { Client, Connection, Search, Worker, workflow } = MemFlow;
25
-
26
25
  export {
27
26
  //Provider Connectors
28
27
  Connector, //factory
@@ -44,6 +43,7 @@ export {
44
43
  Connection,
45
44
  proxyActivities,
46
45
  Search,
46
+ Entity,
47
47
  Worker,
48
48
  workflow,
49
49
  WorkflowHandle,
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@hotmeshio/hotmesh",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "Permanent-Memory Workflows & AI Agents",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",
7
- "homepage": "https://hotmesh.io/",
7
+ "homepage": "https://github.com/hotmeshio/sdk-typescript/",
8
8
  "publishConfig": {
9
9
  "access": "public"
10
10
  },
@@ -33,7 +33,8 @@
33
33
  "test:memflow:collision": "NODE_ENV=test jest ./tests/memflow/collision/*.test.ts --detectOpenHandles --forceExit --verbose",
34
34
  "test:memflow:fatal": "NODE_ENV=test jest ./tests/memflow/fatal/*.test.ts --detectOpenHandles --forceExit --verbose",
35
35
  "test:memflow:goodbye": "NODE_ENV=test jest ./tests/memflow/goodbye/*.test.ts --detectOpenHandles --forceExit --verbose",
36
- "test:memflow:context": "NODE_ENV=test jest ./tests/memflow/context/postgres.test.ts --detectOpenHandles --forceExit --verbose",
36
+ "test:memflow:entity": "NODE_ENV=test HMSH_LOGLEVEL=debug jest ./tests/memflow/entity/postgres.test.ts --detectOpenHandles --forceExit --verbose",
37
+ "test:memflow:agent": "NODE_ENV=test HMSH_LOGLEVEL=debug jest ./tests/memflow/agent/postgres.test.ts --detectOpenHandles --forceExit --verbose",
37
38
  "test:memflow:hello": "HMSH_TELEMETRY=debug HMSH_LOGLEVEL=debug HMSH_IS_CLUSTER=true NODE_ENV=test jest ./tests/memflow/helloworld/*.test.ts --detectOpenHandles --forceExit --verbose",
38
39
  "test:memflow:hook": "NODE_ENV=test jest ./tests/memflow/hook/*.test.ts --detectOpenHandles --forceExit --verbose",
39
40
  "test:memflow:interrupt": "NODE_ENV=test jest ./tests/memflow/interrupt/*.test.ts --detectOpenHandles --forceExit --verbose",
@@ -1,14 +0,0 @@
1
- import { Context } from './common';
2
- /**
3
- * Returns a context session handle for interacting with the workflow's JSONB context storage.
4
- * @returns {Promise<Context>} A context session for workflow data.
5
- *
6
- * @example
7
- * ```typescript
8
- * const context = await workflow.context();
9
- * await context.set({ user: { id: 123 } });
10
- * await context.merge({ user: { name: "John" } });
11
- * const user = await context.get("user");
12
- * ```
13
- */
14
- export declare function context(): Promise<Context>;