@hotmeshio/hotmesh 0.6.1 → 0.8.0
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.
- package/.claude/settings.local.json +7 -0
- package/README.md +179 -142
- package/build/index.d.ts +1 -3
- package/build/index.js +1 -5
- package/build/modules/enums.d.ts +7 -0
- package/build/modules/enums.js +16 -1
- package/build/modules/utils.d.ts +27 -0
- package/build/modules/utils.js +55 -32
- package/build/package.json +18 -27
- package/build/services/activities/activity.d.ts +43 -6
- package/build/services/activities/activity.js +262 -54
- package/build/services/activities/await.js +2 -2
- package/build/services/activities/cycle.js +1 -1
- package/build/services/activities/hook.d.ts +5 -0
- package/build/services/activities/hook.js +22 -19
- package/build/services/activities/interrupt.js +17 -25
- package/build/services/activities/signal.d.ts +4 -2
- package/build/services/activities/signal.js +27 -24
- package/build/services/activities/worker.js +2 -2
- package/build/services/collator/index.d.ts +123 -25
- package/build/services/collator/index.js +224 -101
- package/build/services/connector/factory.d.ts +1 -1
- package/build/services/connector/factory.js +1 -11
- package/build/services/connector/providers/postgres.js +3 -0
- package/build/services/engine/index.d.ts +5 -5
- package/build/services/engine/index.js +36 -15
- package/build/services/hotmesh/index.d.ts +66 -15
- package/build/services/hotmesh/index.js +84 -15
- package/build/services/memflow/index.d.ts +100 -14
- package/build/services/memflow/index.js +100 -14
- package/build/services/memflow/worker.d.ts +97 -0
- package/build/services/memflow/worker.js +217 -0
- package/build/services/memflow/workflow/proxyActivities.d.ts +74 -3
- package/build/services/memflow/workflow/proxyActivities.js +81 -4
- package/build/services/router/consumption/index.d.ts +2 -1
- package/build/services/router/consumption/index.js +39 -3
- package/build/services/router/error-handling/index.d.ts +3 -3
- package/build/services/router/error-handling/index.js +48 -13
- package/build/services/router/index.d.ts +1 -0
- package/build/services/router/index.js +2 -1
- package/build/services/search/factory.js +1 -9
- package/build/services/store/factory.js +1 -9
- package/build/services/store/index.d.ts +8 -2
- package/build/services/store/providers/postgres/kvsql.d.ts +4 -0
- package/build/services/store/providers/postgres/kvsql.js +4 -0
- package/build/services/store/providers/postgres/kvtransaction.d.ts +2 -0
- package/build/services/store/providers/postgres/kvtransaction.js +23 -0
- package/build/services/store/providers/postgres/kvtypes/hash/basic.d.ts +51 -0
- package/build/services/store/providers/postgres/kvtypes/hash/basic.js +229 -7
- package/build/services/store/providers/postgres/kvtypes/hash/expire.js +12 -2
- package/build/services/store/providers/postgres/kvtypes/hash/index.d.ts +4 -0
- package/build/services/store/providers/postgres/kvtypes/hash/index.js +6 -0
- package/build/services/store/providers/postgres/kvtypes/hash/scan.js +30 -10
- package/build/services/store/providers/postgres/kvtypes/list.js +68 -10
- package/build/services/store/providers/postgres/kvtypes/string.js +60 -10
- package/build/services/store/providers/postgres/kvtypes/zset.js +92 -22
- package/build/services/store/providers/postgres/postgres.d.ts +23 -3
- package/build/services/store/providers/postgres/postgres.js +38 -1
- package/build/services/stream/factory.js +1 -17
- package/build/services/stream/providers/postgres/kvtables.js +76 -23
- package/build/services/stream/providers/postgres/lifecycle.d.ts +19 -0
- package/build/services/stream/providers/postgres/lifecycle.js +54 -0
- package/build/services/stream/providers/postgres/messages.d.ts +56 -0
- package/build/services/stream/providers/postgres/messages.js +253 -0
- package/build/services/stream/providers/postgres/notifications.d.ts +59 -0
- package/build/services/stream/providers/postgres/notifications.js +357 -0
- package/build/services/stream/providers/postgres/postgres.d.ts +110 -11
- package/build/services/stream/providers/postgres/postgres.js +196 -488
- package/build/services/stream/providers/postgres/scout.d.ts +68 -0
- package/build/services/stream/providers/postgres/scout.js +233 -0
- package/build/services/stream/providers/postgres/stats.d.ts +49 -0
- package/build/services/stream/providers/postgres/stats.js +113 -0
- package/build/services/sub/factory.js +1 -9
- package/build/services/sub/index.d.ts +1 -1
- package/build/services/sub/providers/postgres/postgres.d.ts +1 -1
- package/build/services/sub/providers/postgres/postgres.js +53 -6
- package/build/services/task/index.d.ts +1 -1
- package/build/services/task/index.js +2 -6
- package/build/services/worker/index.d.ts +1 -0
- package/build/services/worker/index.js +2 -0
- package/build/types/hotmesh.d.ts +42 -2
- package/build/types/index.d.ts +3 -4
- package/build/types/index.js +1 -4
- package/build/types/memflow.d.ts +32 -0
- package/build/types/provider.d.ts +17 -1
- package/build/types/stream.d.ts +92 -1
- package/index.ts +0 -4
- package/package.json +18 -27
- package/build/services/connector/providers/ioredis.d.ts +0 -9
- package/build/services/connector/providers/ioredis.js +0 -26
- package/build/services/connector/providers/redis.d.ts +0 -9
- package/build/services/connector/providers/redis.js +0 -38
- package/build/services/search/providers/redis/ioredis.d.ts +0 -23
- package/build/services/search/providers/redis/ioredis.js +0 -189
- package/build/services/search/providers/redis/redis.d.ts +0 -23
- package/build/services/search/providers/redis/redis.js +0 -202
- package/build/services/store/providers/redis/_base.d.ts +0 -137
- package/build/services/store/providers/redis/_base.js +0 -980
- package/build/services/store/providers/redis/ioredis.d.ts +0 -20
- package/build/services/store/providers/redis/ioredis.js +0 -180
- package/build/services/store/providers/redis/redis.d.ts +0 -18
- package/build/services/store/providers/redis/redis.js +0 -199
- package/build/services/stream/providers/redis/ioredis.d.ts +0 -61
- package/build/services/stream/providers/redis/ioredis.js +0 -272
- package/build/services/stream/providers/redis/redis.d.ts +0 -61
- package/build/services/stream/providers/redis/redis.js +0 -305
- package/build/services/sub/providers/redis/ioredis.d.ts +0 -20
- package/build/services/sub/providers/redis/ioredis.js +0 -150
- package/build/services/sub/providers/redis/redis.d.ts +0 -18
- package/build/services/sub/providers/redis/redis.js +0 -137
- package/build/types/redis.d.ts +0 -258
- package/build/types/redis.js +0 -11
package/build/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hotmeshio/hotmesh",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Permanent-Memory Workflows & AI Agents",
|
|
5
5
|
"main": "./build/index.js",
|
|
6
6
|
"types": "./build/index.d.ts",
|
|
@@ -23,16 +23,15 @@
|
|
|
23
23
|
"test:await": "NODE_ENV=test jest ./tests/functional/awaiter/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
24
24
|
"test:compile": "NODE_ENV=test jest ./tests/functional/compile/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
25
25
|
"test:connect": "NODE_ENV=test jest ./tests/unit/services/connector/* --detectOpenHandles --forceExit --verbose",
|
|
26
|
-
"test:connect:ioredis": "NODE_ENV=test jest ./tests/unit/services/connector/providers/ioredis.test.ts --detectOpenHandles --forceExit --verbose",
|
|
27
26
|
"test:connect:postgres": "NODE_ENV=test jest ./tests/unit/services/connector/providers/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
28
|
-
"test:connect:redis": "NODE_ENV=test jest ./tests/unit/services/connector/providers/ioredis.test.ts --detectOpenHandles --forceExit --verbose",
|
|
29
27
|
"test:connect:nats": "NODE_ENV=test jest ./tests/unit/services/connector/providers/nats.test.ts --detectOpenHandles --forceExit --verbose",
|
|
30
28
|
"test:memflow": "NODE_ENV=test jest ./tests/memflow/*/*.test.ts --detectOpenHandles --forceExit --verbose",
|
|
29
|
+
"test:memflow:postgres": "HMSH_LOGLEVEL=info NODE_ENV=test jest ./tests/memflow/*/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
31
30
|
"test:memflow:basic": "HMSH_LOGLEVEL=info NODE_ENV=test jest ./tests/memflow/basic/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
32
|
-
"test:memflow:collision": "NODE_ENV=test jest ./tests/memflow/collision
|
|
31
|
+
"test:memflow:collision": "NODE_ENV=test jest ./tests/memflow/collision/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
33
32
|
"test:memflow:fatal": "NODE_ENV=test jest ./tests/memflow/fatal/*.test.ts --detectOpenHandles --forceExit --verbose",
|
|
34
33
|
"test:memflow:goodbye": "NODE_ENV=test HMSH_LOGLEVEL=debug jest ./tests/memflow/goodbye/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
35
|
-
"test:memflow:interceptor": "NODE_ENV=test HMSH_LOGLEVEL=
|
|
34
|
+
"test:memflow:interceptor": "NODE_ENV=test HMSH_LOGLEVEL=info jest ./tests/memflow/interceptor/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
36
35
|
"test:memflow:entity": "NODE_ENV=test HMSH_LOGLEVEL=debug jest ./tests/memflow/entity/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
37
36
|
"test:memflow:agent": "NODE_ENV=test HMSH_LOGLEVEL=debug jest ./tests/memflow/agent/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
38
37
|
"test:memflow:hello": "HMSH_TELEMETRY=debug HMSH_LOGLEVEL=debug NODE_ENV=test jest ./tests/memflow/helloworld/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
@@ -42,37 +41,33 @@
|
|
|
42
41
|
"test:memflow:nested": "NODE_ENV=test jest ./tests/memflow/nested/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
43
42
|
"test:memflow:pipeline": "NODE_ENV=test jest ./tests/memflow/pipeline/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
44
43
|
"test:memflow:retry": "NODE_ENV=test jest ./tests/memflow/retry/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
44
|
+
"test:memflow:retrypolicy": "NODE_ENV=test jest ./tests/memflow/retry-policy/*.test.ts --detectOpenHandles --forceExit --verbose",
|
|
45
45
|
"test:memflow:sleep": "NODE_ENV=test jest ./tests/memflow/sleep/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
46
46
|
"test:memflow:signal": "NODE_ENV=test jest ./tests/memflow/signal/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
47
47
|
"test:memflow:unknown": "NODE_ENV=test jest ./tests/memflow/unknown/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
48
48
|
"test:cycle": "NODE_ENV=test jest ./tests/functional/cycle/*.test.ts --detectOpenHandles --forceExit --verbose",
|
|
49
|
-
"test:functional": "NODE_ENV=test jest ./tests/functional
|
|
49
|
+
"test:functional": "NODE_ENV=test jest ./tests/functional/**/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
50
50
|
"test:emit": "NODE_ENV=test jest ./tests/functional/emit/*.test.ts --detectOpenHandles --forceExit --verbose",
|
|
51
51
|
"test:pending": "NODE_ENV=test jest ./tests/functional/pending/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
52
|
-
"test:hmsh": "NODE_ENV=test jest ./tests/functional
|
|
53
|
-
"test:hook": "NODE_ENV=test jest ./tests/functional/hook/
|
|
54
|
-
"test:interrupt": "NODE_ENV=test jest ./tests/functional/interrupt
|
|
52
|
+
"test:hmsh": "NODE_ENV=test jest ./tests/functional/postgres.test.ts --detectOpenHandles --verbose --forceExit",
|
|
53
|
+
"test:hook": "NODE_ENV=test jest ./tests/functional/hook/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
54
|
+
"test:interrupt": "NODE_ENV=test jest ./tests/functional/interrupt/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
55
55
|
"test:parallel": "NODE_ENV=test jest ./tests/functional/parallel/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
56
56
|
"test:pipe": "NODE_ENV=test jest ./tests/unit/services/pipe/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
57
|
-
"test:quorum": "NODE_ENV=test jest ./tests/functional/quorum
|
|
58
|
-
"test:reclaim": "NODE_ENV=test jest ./tests/functional/reclaim
|
|
59
|
-
"test:redeploy": "NODE_ENV=test jest ./tests/functional/redeploy
|
|
57
|
+
"test:quorum": "NODE_ENV=test jest ./tests/functional/quorum/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
58
|
+
"test:reclaim": "NODE_ENV=test jest ./tests/functional/reclaim/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
59
|
+
"test:redeploy": "NODE_ENV=test jest ./tests/functional/redeploy/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
60
60
|
"test:reporter": "NODE_ENV=test jest ./tests/unit/services/reporter/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
61
|
-
"test:reentrant": "NODE_ENV=test jest ./tests/functional/reentrant
|
|
62
|
-
"test:retry": "NODE_ENV=test jest ./tests/functional/retry
|
|
63
|
-
"test:
|
|
64
|
-
"test:
|
|
61
|
+
"test:reentrant": "NODE_ENV=test jest ./tests/functional/reentrant/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
62
|
+
"test:retry": "NODE_ENV=test jest ./tests/functional/retry/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
63
|
+
"test:retrypolicy": "NODE_ENV=test jest ./tests/functional/retry-policy/*.test.ts --detectOpenHandles --forceExit --verbose",
|
|
64
|
+
"test:sequence": "NODE_ENV=test HMSH_LOGLEVEL=info jest ./tests/functional/sequence/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
65
|
+
"test:signal": "NODE_ENV=test jest ./tests/functional/signal/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
65
66
|
"test:status": "NODE_ENV=test jest ./tests/functional/status/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
66
67
|
"test:providers": "NODE_ENV=test jest ./tests/functional/*/providers/*/*.test.ts --detectOpenHandles --forceExit --verbose",
|
|
67
|
-
"test:store:ioredis": "NODE_ENV=test jest ./tests/functional/store/providers/redis/ioredis.test.ts --detectOpenHandles --forceExit --verbose",
|
|
68
|
-
"test:store:redis": "NODE_ENV=test jest ./tests/functional/store/providers/redis/redis.test.ts --detectOpenHandles --forceExit --verbose",
|
|
69
68
|
"test:store:postgres": "NODE_ENV=test jest ./tests/functional/store/providers/postgres/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
70
|
-
"test:stream:ioredis": "NODE_ENV=test jest ./tests/functional/stream/providers/redis/ioredis.test.ts --detectOpenHandles --forceExit --verbose",
|
|
71
|
-
"test:stream:redis": "NODE_ENV=test jest ./tests/functional/stream/providers/redis/redis.test.ts --detectOpenHandles --forceExit --verbose",
|
|
72
69
|
"test:stream:postgres": "NODE_ENV=test jest ./tests/functional/stream/providers/postgres/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
73
70
|
"test:stream:nats": "NODE_ENV=test jest ./tests/functional/stream/providers/nats/nats.test.ts --detectOpenHandles --forceExit --verbose",
|
|
74
|
-
"test:sub:ioredis": "NODE_ENV=test jest ./tests/functional/sub/providers/redis/ioredis.test.ts --detectOpenHandles --forceExit --verbose",
|
|
75
|
-
"test:sub:redis": "NODE_ENV=test jest ./tests/functional/sub/providers/redis/redis.test.ts --detectOpenHandles --forceExit --verbose",
|
|
76
71
|
"test:sub:postgres": "NODE_ENV=test jest ./tests/functional/sub/providers/postgres/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
77
72
|
"test:sub:nats": "NODE_ENV=test jest ./tests/functional/sub/providers/nats/nats.test.ts --detectOpenHandles --forceExit --verbose",
|
|
78
73
|
"test:trigger": "NODE_ENV=test jest ./tests/unit/services/activities/trigger.test.ts --detectOpenHandles --forceExit --verbose",
|
|
@@ -114,13 +109,11 @@
|
|
|
114
109
|
"eslint-config-prettier": "^9.1.0",
|
|
115
110
|
"eslint-plugin-import": "^2.29.1",
|
|
116
111
|
"eslint-plugin-prettier": "^5.1.3",
|
|
117
|
-
"ioredis": "^5.3.2",
|
|
118
112
|
"javascript-obfuscator": "^0.6.2",
|
|
119
113
|
"jest": "^29.5.0",
|
|
120
114
|
"nats": "^2.28.0",
|
|
121
115
|
"openai": "^5.9.0",
|
|
122
116
|
"pg": "^8.10.0",
|
|
123
|
-
"redis": "^4.6.13",
|
|
124
117
|
"rimraf": "^4.4.1",
|
|
125
118
|
"terser": "^5.37.0",
|
|
126
119
|
"ts-jest": "^29.0.5",
|
|
@@ -130,9 +123,7 @@
|
|
|
130
123
|
"typescript": "^5.0.4"
|
|
131
124
|
},
|
|
132
125
|
"peerDependencies": {
|
|
133
|
-
"ioredis": "^4.0.0 || ^5.0.0",
|
|
134
126
|
"nats": "^2.0.0",
|
|
135
|
-
"pg": "^8.0.0"
|
|
136
|
-
"redis": "^4.0.0"
|
|
127
|
+
"pg": "^8.0.0"
|
|
137
128
|
}
|
|
138
129
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { EngineService } from '../engine';
|
|
2
2
|
import { ILogger } from '../logger';
|
|
3
3
|
import { StoreService } from '../store';
|
|
4
|
-
import { TelemetryService } from '../telemetry';
|
|
5
4
|
import { ActivityData, ActivityLeg, ActivityMetadata, ActivityType } from '../../types/activity';
|
|
6
5
|
import { ProviderClient, ProviderTransaction, TransactionResultList } from '../../types/provider';
|
|
7
6
|
import { JobState, JobStatus } from '../../types/job';
|
|
@@ -24,6 +23,7 @@ declare class Activity {
|
|
|
24
23
|
leg: ActivityLeg;
|
|
25
24
|
adjacencyList: StreamData[];
|
|
26
25
|
adjacentIndex: number;
|
|
26
|
+
guidLedger: number;
|
|
27
27
|
constructor(config: ActivityType, data: ActivityData, metadata: ActivityMetadata, hook: ActivityData | null, engine: EngineService, context?: JobState);
|
|
28
28
|
setLeg(leg: ActivityLeg): void;
|
|
29
29
|
/**
|
|
@@ -37,15 +37,48 @@ declare class Activity {
|
|
|
37
37
|
*/
|
|
38
38
|
verifyEntry(): Promise<void>;
|
|
39
39
|
/**
|
|
40
|
-
* Upon entering leg 2 of a duplexed activity
|
|
40
|
+
* Upon entering leg 2 of a duplexed activity.
|
|
41
|
+
* Increments both the activity ledger (+1) and GUID ledger (+1).
|
|
42
|
+
* Stores the GUID ledger value for step-level resume decisions.
|
|
41
43
|
*/
|
|
42
44
|
verifyReentry(): Promise<number>;
|
|
43
45
|
processEvent(status?: StreamStatus, code?: StreamCode, type?: 'hook' | 'output'): Promise<void>;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Executes the 3-step Leg2 protocol using GUID ledger for
|
|
48
|
+
* crash-safe resume. Each step bundles durable writes with
|
|
49
|
+
* its concluding digit update in a single transaction.
|
|
50
|
+
*
|
|
51
|
+
* @returns true if this transition caused the job to complete
|
|
52
|
+
*/
|
|
53
|
+
executeStepProtocol(delta: number, shouldFinalize: boolean): Promise<boolean>;
|
|
54
|
+
/**
|
|
55
|
+
* Extracts the thresholdHit value from transaction results.
|
|
56
|
+
* The setStatusAndCollateGuid result is the last item.
|
|
57
|
+
*/
|
|
58
|
+
resolveThresholdHit(results: TransactionResultList): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Extracts the job status from the last result of a transaction.
|
|
61
|
+
* Used by subclass Leg1 process methods for telemetry.
|
|
62
|
+
*/
|
|
48
63
|
resolveStatus(multiResponse: TransactionResultList): number;
|
|
64
|
+
/**
|
|
65
|
+
* Leg1 entry verification for Category B activities (Leg1-only with children).
|
|
66
|
+
* Returns true if this is a resume (Leg1 already completed on a prior attempt).
|
|
67
|
+
* On resume, loads the GUID ledger for step-level resume decisions.
|
|
68
|
+
*/
|
|
69
|
+
verifyLeg1Entry(): Promise<boolean>;
|
|
70
|
+
/**
|
|
71
|
+
* Executes the 3-step Leg1 protocol for Category B activities
|
|
72
|
+
* (Leg1-only with children, e.g., Hook passthrough, Signal, Interrupt-another).
|
|
73
|
+
* Uses the incoming Leg1 message GUID as the GUID ledger key.
|
|
74
|
+
*
|
|
75
|
+
* Step A: setState + notarizeLeg1Completion + step1 markers (transaction 1)
|
|
76
|
+
* Step B: publish children + step2 markers + setStatusAndCollateGuid (transaction 2)
|
|
77
|
+
* Step C: if edge → runJobCompletionTasks + step3 markers + finalize (transaction 3)
|
|
78
|
+
*
|
|
79
|
+
* @returns true if this transition caused the job to complete
|
|
80
|
+
*/
|
|
81
|
+
executeLeg1StepProtocol(delta: number): Promise<boolean>;
|
|
49
82
|
mapJobData(): void;
|
|
50
83
|
mapInputData(): void;
|
|
51
84
|
mapOutputData(): void;
|
|
@@ -94,6 +127,10 @@ declare class Activity {
|
|
|
94
127
|
* @private
|
|
95
128
|
*/
|
|
96
129
|
shouldPersistJob(): boolean;
|
|
130
|
+
/**
|
|
131
|
+
* Transition method for Category C (Leg1-only, no children, no semaphore change)
|
|
132
|
+
* and Category D (Trigger) activities. NOT used by the Leg2 step protocol.
|
|
133
|
+
*/
|
|
97
134
|
transition(adjacencyList: StreamData[], jobStatus: JobStatus): Promise<string[]>;
|
|
98
135
|
/**
|
|
99
136
|
* A job with a vale < -100_000_000 is considered interrupted,
|
|
@@ -18,6 +18,7 @@ class Activity {
|
|
|
18
18
|
this.status = stream_1.StreamStatus.SUCCESS;
|
|
19
19
|
this.code = 200;
|
|
20
20
|
this.adjacentIndex = 0;
|
|
21
|
+
this.guidLedger = 0;
|
|
21
22
|
this.config = config;
|
|
22
23
|
this.data = data;
|
|
23
24
|
this.metadata = metadata;
|
|
@@ -56,6 +57,7 @@ class Activity {
|
|
|
56
57
|
}
|
|
57
58
|
catch (error) {
|
|
58
59
|
await collator_1.CollatorService.notarizeEntry(this);
|
|
60
|
+
//todo: confirm this check is still needed; the edge event cleanup should handle fully
|
|
59
61
|
if (threshold > 0) {
|
|
60
62
|
if (this.context.metadata.js === threshold) {
|
|
61
63
|
//conclude job EXACTLY ONCE
|
|
@@ -73,14 +75,19 @@ class Activity {
|
|
|
73
75
|
await collator_1.CollatorService.notarizeEntry(this);
|
|
74
76
|
}
|
|
75
77
|
/**
|
|
76
|
-
* Upon entering leg 2 of a duplexed activity
|
|
78
|
+
* Upon entering leg 2 of a duplexed activity.
|
|
79
|
+
* Increments both the activity ledger (+1) and GUID ledger (+1).
|
|
80
|
+
* Stores the GUID ledger value for step-level resume decisions.
|
|
77
81
|
*/
|
|
78
82
|
async verifyReentry() {
|
|
79
|
-
const
|
|
83
|
+
const msgGuid = this.context.metadata.guid;
|
|
80
84
|
this.setLeg(2);
|
|
81
85
|
await this.getState();
|
|
86
|
+
this.context.metadata.guid = msgGuid;
|
|
82
87
|
collator_1.CollatorService.assertJobActive(this.context.metadata.js, this.context.metadata.jid, this.metadata.aid);
|
|
83
|
-
|
|
88
|
+
const [activityLedger, guidLedger] = await collator_1.CollatorService.notarizeLeg2Entry(this, msgGuid);
|
|
89
|
+
this.guidLedger = guidLedger;
|
|
90
|
+
return activityLedger;
|
|
84
91
|
}
|
|
85
92
|
//******** DUPLEX RE-ENTRY POINT ********//
|
|
86
93
|
async processEvent(status = stream_1.StreamStatus.SUCCESS, code = 200, type = 'output') {
|
|
@@ -108,17 +115,43 @@ class Activity {
|
|
|
108
115
|
this.adjacentIndex = collator_1.CollatorService.getDimensionalIndex(collationKey);
|
|
109
116
|
telemetry = new telemetry_1.TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
|
|
110
117
|
telemetry.startActivitySpan(this.leg);
|
|
111
|
-
|
|
112
|
-
if (status === stream_1.StreamStatus.
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
118
|
+
//bind data per status type
|
|
119
|
+
if (status === stream_1.StreamStatus.ERROR) {
|
|
120
|
+
this.bindActivityError(this.data);
|
|
121
|
+
this.adjacencyList = await this.filterAdjacent();
|
|
122
|
+
if (!this.adjacencyList.length) {
|
|
123
|
+
this.bindJobError(this.data);
|
|
124
|
+
}
|
|
117
125
|
}
|
|
118
126
|
else {
|
|
119
|
-
|
|
127
|
+
this.bindActivityData(type);
|
|
128
|
+
this.adjacencyList = await this.filterAdjacent();
|
|
120
129
|
}
|
|
121
|
-
this.
|
|
130
|
+
this.mapJobData();
|
|
131
|
+
//When an unrecoverable error has no matching transitions
|
|
132
|
+
//(e.g., code 500 from raw errors after retries exhausted),
|
|
133
|
+
//mark the job as terminally errored so the step protocol
|
|
134
|
+
//can force completion via the isErrorTerminal path.
|
|
135
|
+
if (status === stream_1.StreamStatus.ERROR && !this.adjacencyList?.length) {
|
|
136
|
+
if (!this.context.data)
|
|
137
|
+
this.context.data = {};
|
|
138
|
+
this.context.data.done = true;
|
|
139
|
+
this.context.data.$error = {
|
|
140
|
+
message: this.data?.message || 'unknown error',
|
|
141
|
+
code: enums_1.HMSH_CODE_MEMFLOW_MAXED,
|
|
142
|
+
stack: this.data?.stack,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
//determine step parameters
|
|
146
|
+
const delta = status === stream_1.StreamStatus.PENDING
|
|
147
|
+
? this.adjacencyList.length
|
|
148
|
+
: this.adjacencyList.length - 1;
|
|
149
|
+
const shouldFinalize = status !== stream_1.StreamStatus.PENDING;
|
|
150
|
+
//execute 3-step protocol
|
|
151
|
+
const thresholdHit = await this.executeStepProtocol(delta, shouldFinalize);
|
|
152
|
+
//telemetry
|
|
153
|
+
telemetry.mapActivityAttributes();
|
|
154
|
+
telemetry.setActivityAttributes({});
|
|
122
155
|
}
|
|
123
156
|
catch (error) {
|
|
124
157
|
if (error instanceof errors_1.CollationError) {
|
|
@@ -151,50 +184,100 @@ class Activity {
|
|
|
151
184
|
this.logger.debug('activity-process-event-end', { jid, aid });
|
|
152
185
|
}
|
|
153
186
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
196
|
-
|
|
187
|
+
/**
|
|
188
|
+
* Executes the 3-step Leg2 protocol using GUID ledger for
|
|
189
|
+
* crash-safe resume. Each step bundles durable writes with
|
|
190
|
+
* its concluding digit update in a single transaction.
|
|
191
|
+
*
|
|
192
|
+
* @returns true if this transition caused the job to complete
|
|
193
|
+
*/
|
|
194
|
+
async executeStepProtocol(delta, shouldFinalize) {
|
|
195
|
+
const msgGuid = this.context.metadata.guid;
|
|
196
|
+
const threshold = this.mapStatusThreshold();
|
|
197
|
+
const { id: appId } = await this.engine.getVID();
|
|
198
|
+
//Step 1: Save work (skip if GUID 10B already set)
|
|
199
|
+
if (!collator_1.CollatorService.isGuidStep1Done(this.guidLedger)) {
|
|
200
|
+
const txn1 = this.store.transact();
|
|
201
|
+
await this.setState(txn1);
|
|
202
|
+
await collator_1.CollatorService.notarizeStep1(this, msgGuid, txn1);
|
|
203
|
+
await txn1.exec();
|
|
204
|
+
}
|
|
205
|
+
//Step 2: Spawn children + semaphore + edge capture (skip if GUID 1B already set)
|
|
206
|
+
let thresholdHit = false;
|
|
207
|
+
if (!collator_1.CollatorService.isGuidStep2Done(this.guidLedger)) {
|
|
208
|
+
const txn2 = this.store.transact();
|
|
209
|
+
//queue step markers first
|
|
210
|
+
await collator_1.CollatorService.notarizeStep2(this, msgGuid, txn2);
|
|
211
|
+
//queue child publications
|
|
212
|
+
for (const child of this.adjacencyList) {
|
|
213
|
+
await this.engine.router?.publishMessage(null, child, txn2);
|
|
214
|
+
}
|
|
215
|
+
//queue semaphore update + edge capture LAST (so result is at end)
|
|
216
|
+
await this.store.setStatusAndCollateGuid(delta, threshold, this.context.metadata.jid, appId, msgGuid, collator_1.CollatorService.WEIGHTS.GUID_SNAPSHOT, txn2);
|
|
217
|
+
const results = (await txn2.exec());
|
|
218
|
+
thresholdHit = this.resolveThresholdHit(results);
|
|
219
|
+
this.logger.debug('step-protocol-step2-complete', {
|
|
220
|
+
jid: this.context.metadata.jid,
|
|
221
|
+
aid: this.metadata.aid,
|
|
222
|
+
delta,
|
|
223
|
+
threshold,
|
|
224
|
+
thresholdHit,
|
|
225
|
+
lastResult: results[results.length - 1],
|
|
226
|
+
resultCount: results.length,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
//Step 2 already done; check GUID snapshot for edge
|
|
231
|
+
thresholdHit = collator_1.CollatorService.isGuidJobClosed(this.guidLedger);
|
|
232
|
+
}
|
|
233
|
+
//Step 3: Job completion tasks (edge hit OR emit/persist, skip if GUID 100M already set)
|
|
234
|
+
//When an activity marks the job done with an unrecoverable error
|
|
235
|
+
//(e.g., stopper after max retries), force completion even when the
|
|
236
|
+
//semaphore threshold isn't hit (the signaler's +1 contribution
|
|
237
|
+
//prevents threshold 0 from matching).
|
|
238
|
+
const isErrorTerminal = !thresholdHit
|
|
239
|
+
&& this.context.data?.done === true
|
|
240
|
+
&& !!this.context.data?.$error;
|
|
241
|
+
const needsCompletion = thresholdHit || this.shouldEmit() || this.shouldPersistJob() || isErrorTerminal;
|
|
242
|
+
if (needsCompletion && !collator_1.CollatorService.isGuidStep3Done(this.guidLedger)) {
|
|
243
|
+
const txn3 = this.store.transact();
|
|
244
|
+
const options = (thresholdHit || isErrorTerminal) ? {} : { emit: !this.shouldPersistJob() };
|
|
245
|
+
await this.engine.runJobCompletionTasks(this.context, options, txn3);
|
|
246
|
+
await collator_1.CollatorService.notarizeStep3(this, msgGuid, txn3);
|
|
247
|
+
const shouldFinalizeNow = (thresholdHit || isErrorTerminal) ? shouldFinalize : this.shouldPersistJob();
|
|
248
|
+
if (shouldFinalizeNow) {
|
|
249
|
+
await collator_1.CollatorService.notarizeFinalize(this, txn3);
|
|
250
|
+
}
|
|
251
|
+
await txn3.exec();
|
|
252
|
+
}
|
|
253
|
+
else if (needsCompletion) {
|
|
254
|
+
this.logger.debug('step-protocol-step3-skipped-already-done', {
|
|
255
|
+
jid: this.context.metadata.jid,
|
|
256
|
+
aid: this.metadata.aid,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
this.logger.debug('step-protocol-no-threshold', {
|
|
261
|
+
jid: this.context.metadata.jid,
|
|
262
|
+
aid: this.metadata.aid,
|
|
263
|
+
thresholdHit,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
return thresholdHit;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Extracts the thresholdHit value from transaction results.
|
|
270
|
+
* The setStatusAndCollateGuid result is the last item.
|
|
271
|
+
*/
|
|
272
|
+
resolveThresholdHit(results) {
|
|
273
|
+
const last = results[results.length - 1];
|
|
274
|
+
const value = Array.isArray(last) ? last[1] : last;
|
|
275
|
+
return Number(value) === 1;
|
|
197
276
|
}
|
|
277
|
+
/**
|
|
278
|
+
* Extracts the job status from the last result of a transaction.
|
|
279
|
+
* Used by subclass Leg1 process methods for telemetry.
|
|
280
|
+
*/
|
|
198
281
|
resolveStatus(multiResponse) {
|
|
199
282
|
const activityStatus = multiResponse[multiResponse.length - 1];
|
|
200
283
|
if (Array.isArray(activityStatus)) {
|
|
@@ -204,6 +287,127 @@ class Activity {
|
|
|
204
287
|
return Number(activityStatus);
|
|
205
288
|
}
|
|
206
289
|
}
|
|
290
|
+
/**
|
|
291
|
+
* Leg1 entry verification for Category B activities (Leg1-only with children).
|
|
292
|
+
* Returns true if this is a resume (Leg1 already completed on a prior attempt).
|
|
293
|
+
* On resume, loads the GUID ledger for step-level resume decisions.
|
|
294
|
+
*/
|
|
295
|
+
async verifyLeg1Entry() {
|
|
296
|
+
const msgGuid = this.context.metadata.guid;
|
|
297
|
+
this.setLeg(1);
|
|
298
|
+
await this.getState();
|
|
299
|
+
this.context.metadata.guid = msgGuid;
|
|
300
|
+
const threshold = this.mapStatusThreshold();
|
|
301
|
+
try {
|
|
302
|
+
collator_1.CollatorService.assertJobActive(this.context.metadata.js, this.context.metadata.jid, this.metadata.aid, threshold);
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
if (error instanceof errors_1.InactiveJobError && threshold > 0) {
|
|
306
|
+
//Dynamic Activation Control: threshold met, close the job
|
|
307
|
+
await collator_1.CollatorService.notarizeEntry(this);
|
|
308
|
+
if (this.context.metadata.js === threshold) {
|
|
309
|
+
//conclude job EXACTLY ONCE
|
|
310
|
+
const status = await this.setStatus(-threshold);
|
|
311
|
+
if (Number(status) === 0) {
|
|
312
|
+
await this.engine.runJobCompletionTasks(this.context);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
throw error;
|
|
317
|
+
}
|
|
318
|
+
try {
|
|
319
|
+
await collator_1.CollatorService.notarizeEntry(this);
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
322
|
+
catch (error) {
|
|
323
|
+
if (error instanceof errors_1.CollationError && error.fault === 'duplicate') {
|
|
324
|
+
if (this.config.cycle) {
|
|
325
|
+
//Cycle re-entry: Leg1 already complete from prior iteration.
|
|
326
|
+
//Increment Leg2 counter to derive the new dimensional index,
|
|
327
|
+
//so children run in a fresh dimensional plane.
|
|
328
|
+
const [activityLedger, guidLedger] = await collator_1.CollatorService.notarizeLeg2Entry(this, msgGuid);
|
|
329
|
+
this.adjacentIndex =
|
|
330
|
+
collator_1.CollatorService.getDimensionalIndex(activityLedger);
|
|
331
|
+
this.guidLedger = guidLedger;
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
//100B is set — Leg1 work already committed. Load GUID for step resume.
|
|
335
|
+
const guidValue = await this.store.collateSynthetic(this.context.metadata.jid, msgGuid, 0);
|
|
336
|
+
this.guidLedger = guidValue;
|
|
337
|
+
return true;
|
|
338
|
+
}
|
|
339
|
+
throw error;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Executes the 3-step Leg1 protocol for Category B activities
|
|
344
|
+
* (Leg1-only with children, e.g., Hook passthrough, Signal, Interrupt-another).
|
|
345
|
+
* Uses the incoming Leg1 message GUID as the GUID ledger key.
|
|
346
|
+
*
|
|
347
|
+
* Step A: setState + notarizeLeg1Completion + step1 markers (transaction 1)
|
|
348
|
+
* Step B: publish children + step2 markers + setStatusAndCollateGuid (transaction 2)
|
|
349
|
+
* Step C: if edge → runJobCompletionTasks + step3 markers + finalize (transaction 3)
|
|
350
|
+
*
|
|
351
|
+
* @returns true if this transition caused the job to complete
|
|
352
|
+
*/
|
|
353
|
+
async executeLeg1StepProtocol(delta) {
|
|
354
|
+
const msgGuid = this.context.metadata.guid;
|
|
355
|
+
const threshold = this.mapStatusThreshold();
|
|
356
|
+
const { id: appId } = await this.engine.getVID();
|
|
357
|
+
//Step A: Save work + Leg1 completion marker
|
|
358
|
+
if (!collator_1.CollatorService.isGuidStep1Done(this.guidLedger)) {
|
|
359
|
+
const txn1 = this.store.transact();
|
|
360
|
+
await this.setState(txn1);
|
|
361
|
+
if (this.adjacentIndex === 0) {
|
|
362
|
+
//First entry: mark Leg1 complete. On cycle re-entry
|
|
363
|
+
//(adjacentIndex > 0), Leg1 is already complete and the
|
|
364
|
+
//Leg2 counter was already incremented by notarizeLeg2Entry.
|
|
365
|
+
await collator_1.CollatorService.notarizeLeg1Completion(this, txn1);
|
|
366
|
+
}
|
|
367
|
+
await collator_1.CollatorService.notarizeStep1(this, msgGuid, txn1);
|
|
368
|
+
await txn1.exec();
|
|
369
|
+
}
|
|
370
|
+
//Step B: Spawn children + semaphore + edge capture
|
|
371
|
+
let thresholdHit = false;
|
|
372
|
+
if (!collator_1.CollatorService.isGuidStep2Done(this.guidLedger)) {
|
|
373
|
+
const txn2 = this.store.transact();
|
|
374
|
+
await collator_1.CollatorService.notarizeStep2(this, msgGuid, txn2);
|
|
375
|
+
for (const child of this.adjacencyList) {
|
|
376
|
+
await this.engine.router?.publishMessage(null, child, txn2);
|
|
377
|
+
}
|
|
378
|
+
await this.store.setStatusAndCollateGuid(delta, threshold, this.context.metadata.jid, appId, msgGuid, collator_1.CollatorService.WEIGHTS.GUID_SNAPSHOT, txn2);
|
|
379
|
+
const results = (await txn2.exec());
|
|
380
|
+
thresholdHit = this.resolveThresholdHit(results);
|
|
381
|
+
this.logger.debug('leg1-step-protocol-stepB-complete', {
|
|
382
|
+
jid: this.context.metadata.jid,
|
|
383
|
+
aid: this.metadata.aid,
|
|
384
|
+
delta,
|
|
385
|
+
threshold,
|
|
386
|
+
thresholdHit,
|
|
387
|
+
lastResult: results[results.length - 1],
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
thresholdHit = collator_1.CollatorService.isGuidJobClosed(this.guidLedger);
|
|
392
|
+
}
|
|
393
|
+
//Step C: Job completion tasks (edge hit OR emit/persist)
|
|
394
|
+
//When an activity marks the job done with an unrecoverable error
|
|
395
|
+
//(e.g., stopper after max retries), force completion even when the
|
|
396
|
+
//semaphore threshold isn't hit.
|
|
397
|
+
const isErrorTerminal = !thresholdHit
|
|
398
|
+
&& this.context.data?.done === true
|
|
399
|
+
&& !!this.context.data?.$error;
|
|
400
|
+
const needsCompletion = thresholdHit || this.shouldEmit() || this.shouldPersistJob() || isErrorTerminal;
|
|
401
|
+
if (needsCompletion && !collator_1.CollatorService.isGuidStep3Done(this.guidLedger)) {
|
|
402
|
+
const txn3 = this.store.transact();
|
|
403
|
+
const options = (thresholdHit || isErrorTerminal) ? {} : { emit: !this.shouldPersistJob() };
|
|
404
|
+
await this.engine.runJobCompletionTasks(this.context, options, txn3);
|
|
405
|
+
await collator_1.CollatorService.notarizeStep3(this, msgGuid, txn3);
|
|
406
|
+
await collator_1.CollatorService.notarizeFinalize(this, txn3);
|
|
407
|
+
await txn3.exec();
|
|
408
|
+
}
|
|
409
|
+
return thresholdHit;
|
|
410
|
+
}
|
|
207
411
|
mapJobData() {
|
|
208
412
|
if (this.config.job?.maps) {
|
|
209
413
|
const mapper = new mapper_1.MapperService((0, utils_1.deepCopy)(this.config.job.maps), this.context);
|
|
@@ -517,6 +721,10 @@ class Activity {
|
|
|
517
721
|
}
|
|
518
722
|
return false;
|
|
519
723
|
}
|
|
724
|
+
/**
|
|
725
|
+
* Transition method for Category C (Leg1-only, no children, no semaphore change)
|
|
726
|
+
* and Category D (Trigger) activities. NOT used by the Leg2 step protocol.
|
|
727
|
+
*/
|
|
520
728
|
async transition(adjacencyList, jobStatus) {
|
|
521
729
|
if (this.jobWasInterrupted(jobStatus)) {
|
|
522
730
|
return;
|
|
@@ -25,11 +25,11 @@ class Await extends activity_1.Activity {
|
|
|
25
25
|
telemetry = new telemetry_1.TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
|
|
26
26
|
telemetry.startActivitySpan(this.leg);
|
|
27
27
|
this.mapInputData();
|
|
28
|
-
//save state and
|
|
28
|
+
//save state and mark Leg1 complete
|
|
29
29
|
const transaction = this.store.transact();
|
|
30
30
|
//todo: await this.registerTimeout();
|
|
31
31
|
const messageId = await this.execActivity(transaction);
|
|
32
|
-
await collator_1.CollatorService.
|
|
32
|
+
await collator_1.CollatorService.notarizeLeg1Completion(this, transaction);
|
|
33
33
|
await this.setState(transaction);
|
|
34
34
|
await this.setStatus(0, transaction);
|
|
35
35
|
const multiResponse = (await transaction.exec());
|
|
@@ -38,7 +38,7 @@ class Cycle extends activity_1.Activity {
|
|
|
38
38
|
'app.job.jss': jobStatus,
|
|
39
39
|
});
|
|
40
40
|
//exit early (`Cycle` activities only execute Leg 1)
|
|
41
|
-
await collator_1.CollatorService.
|
|
41
|
+
await collator_1.CollatorService.notarizeLeg1Completion(this, transaction);
|
|
42
42
|
(await transaction.exec());
|
|
43
43
|
return this.context.metadata.aid;
|
|
44
44
|
}
|
|
@@ -13,6 +13,11 @@ declare class Hook extends Activity {
|
|
|
13
13
|
config: HookActivity;
|
|
14
14
|
constructor(config: ActivityType, data: ActivityData, metadata: ActivityMetadata, hook: ActivityData | null, engine: EngineService, context?: JobState);
|
|
15
15
|
process(): Promise<string>;
|
|
16
|
+
/**
|
|
17
|
+
* Static config check: does this activity have a hook or sleep config?
|
|
18
|
+
* Used for routing before context is loaded.
|
|
19
|
+
*/
|
|
20
|
+
isConfiguredAsHook(): boolean;
|
|
16
21
|
/**
|
|
17
22
|
* does this activity use a time-hook or web-hook
|
|
18
23
|
*/
|